From 7f1bd711f167885cac03f0a36f580e5e62394064 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 29 Sep 2025 14:33:58 +0200 Subject: [PATCH 01/37] =?UTF-8?q?feat(common):=20Establish=20epic/embed-os?= =?UTF-8?q?k-in-kmx=20branch=20=F0=9F=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relates-to: #12620 Relates-to: #12610 Relates-to: #12695 Relates-to: #13424 Relates-to: #12741 Relates-to: #9055 Relates-to: #9021 Build-bot: skip Test-bot: skip --- docs/epics.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/epics.md diff --git a/docs/epics.md b/docs/epics.md new file mode 100644 index 00000000000..6a7468b05d3 --- /dev/null +++ b/docs/epics.md @@ -0,0 +1,16 @@ +# Epics + +This document lists epics in the Keyman project. + + Name | Lead | Target release | Spec +-----------------------|-----------|----------------|-------------------- + epic/embed-osk-in-kmx | @mcdurdin | 19.0 | [embed-osk-in-kmx] + + +Note: when establishing a new epic branch, add the details of the epic to this +file, as a clean way to start an epic without unreviewed code changes in the +base epic PR. + +--- + +[embed-osk-in-kmx]: https://docs.google.com/document/d/15EjtSH7NAsGrdapfB3E7SlS6CqI-2zPy3PNyfUiAv54/edit?tab=t.0 From 3be434aa65a338fbcc5a4c726718b5c8377ecd95 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 Sep 2025 11:36:12 +0200 Subject: [PATCH 02/37] refactor(windows): consolidate legacy_kmx_file.h into kmx_file.h These two files were largely the same, with legacy_kmx_file.h trailing kmx_file.h in features. The primary difference was that legacy_kmx_file.h still had Windows-specific types, but the types in km_types.h made this a drop-in replacement. Test-bot: skip --- common/include/kmx_file.h | 25 +- common/windows/cpp/include/legacy_kmx_file.h | 424 ------------------- developer/src/kmanalyze/pch.h | 2 +- developer/src/kmdecomp/kmdecomp.cpp | 2 +- developer/src/kmdecomp/savekeyboard.cpp | 3 +- windows/src/engine/keyman32/keyman64.h | 8 +- windows/src/engine/keyman32/keymanengine.h | 2 +- windows/src/engine/kmtip/kmkey.cpp | 2 +- windows/src/engine/mcompile/pch.h | 2 +- 9 files changed, 28 insertions(+), 442 deletions(-) delete mode 100644 common/windows/cpp/include/legacy_kmx_file.h diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index 748ba32d920..ca37364815d 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -5,7 +5,7 @@ #pragma once -#include +#include "km_types.h" #ifdef KM_CORE_LIBRARY // TODO: move this to a common namespace keyman::common::kmx_file or similar in the future @@ -62,6 +62,9 @@ namespace kmx { #define BK_DEFAULT 0 #define BK_DEADKEY 1 +// Next character to delete is a Unicode surrogate pair +#define BK_SURROGATE 4 + // Different begin types #define BEGIN_ANSI 0 #define BEGIN_UNICODE 1 @@ -365,14 +368,14 @@ struct COMP_KEYBOARD_KMXPLUSINFO { * Only valid if comp_keyboard.dwFlags&KF_KMXPLUS */ struct COMP_KEYBOARD_EX { - COMP_KEYBOARD header; // 0000 see COMP_KEYBOARD - COMP_KEYBOARD_KMXPLUSINFO kmxplus; // 0040 see COMP_KEYBOARD_EXTRA + struct COMP_KEYBOARD header; // 0000 see COMP_KEYBOARD + struct COMP_KEYBOARD_KMXPLUSINFO kmxplus; // 0040 see COMP_KEYBOARD_EXTRA }; -typedef COMP_KEYBOARD *PCOMP_KEYBOARD; -typedef COMP_STORE *PCOMP_STORE; -typedef COMP_KEY *PCOMP_KEY; -typedef COMP_GROUP *PCOMP_GROUP; +typedef struct COMP_KEYBOARD *PCOMP_KEYBOARD; +typedef struct COMP_STORE *PCOMP_STORE; +typedef struct COMP_KEY *PCOMP_KEY; +typedef struct COMP_GROUP *PCOMP_GROUP; extern const int CODE__SIZE[]; #define CODE__SIZE_MAX 5 @@ -382,10 +385,10 @@ extern const int CODE__SIZE[]; #define KEYBOARDFILEGROUP_SIZE 24 #define KEYBOARDFILEKEY_SIZE 20 -static_assert(sizeof(COMP_STORE) == KEYBOARDFILESTORE_SIZE, "COMP_STORE must be KEYBOARDFILESTORE_SIZE bytes"); -static_assert(sizeof(COMP_KEY) == KEYBOARDFILEKEY_SIZE, "COMP_KEY must be KEYBOARDFILEKEY_SIZE bytes"); -static_assert(sizeof(COMP_GROUP) == KEYBOARDFILEGROUP_SIZE, "COMP_GROUP must be KEYBOARDFILEGROUP_SIZE bytes"); -static_assert(sizeof(COMP_KEYBOARD) == KEYBOARDFILEHEADER_SIZE, "COMP_KEYBOARD must be KEYBOARDFILEHEADER_SIZE bytes"); +static_assert(sizeof(struct COMP_STORE) == KEYBOARDFILESTORE_SIZE, "COMP_STORE must be KEYBOARDFILESTORE_SIZE bytes"); +static_assert(sizeof(struct COMP_KEY) == KEYBOARDFILEKEY_SIZE, "COMP_KEY must be KEYBOARDFILEKEY_SIZE bytes"); +static_assert(sizeof(struct COMP_GROUP) == KEYBOARDFILEGROUP_SIZE, "COMP_GROUP must be KEYBOARDFILEGROUP_SIZE bytes"); +static_assert(sizeof(struct COMP_KEYBOARD) == KEYBOARDFILEHEADER_SIZE, "COMP_KEYBOARD must be KEYBOARDFILEHEADER_SIZE bytes"); #ifdef KM_CORE_LIBRARY } // namespace kmx diff --git a/common/windows/cpp/include/legacy_kmx_file.h b/common/windows/cpp/include/legacy_kmx_file.h deleted file mode 100644 index d3bc3aed0f6..00000000000 --- a/common/windows/cpp/include/legacy_kmx_file.h +++ /dev/null @@ -1,424 +0,0 @@ -// TODO: merge and replace with kmx_file.h from core - -/* - Name: legacy_kmx_file - Copyright: Copyright (C) SIL International. - Documentation: - Description: Describes .kmx binary format. To be replaced with common/include/kmx_file.h - Create Date: 4 Jan 2007 - - Modified Date: 24 Aug 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 04 Jan 2007 - mcdurdin - Add CODE_NOTANY - 22 Mar 2010 - mcdurdin - Compiler tidyup - 25 May 2010 - mcdurdin - I1632 - Keyboard Options - 24 Oct 2013 - mcdurdin - I3933 - V9.0 - Keyman tray icon menu is not showing installed keyboards - 19 Mar 2014 - mcdurdin - I4140 - V9.0 - Add keyboard version information to keyboards - 16 Jun 2014 - mcdurdin - I4271 - V9.0 - Switch language for all applications is not working - 31 Dec 2014 - mcdurdin - I4548 - V9.0 - When Alt is down, release of Ctrl, Shift is not detectable within TIP in some languages - 24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project - 24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile - -*/ - -#ifndef _COMPILER_H -#define _COMPILER_H - - - -/* WM_UNICHAR */ - -#define WM_UNICHAR 0x0109 -#define UNICODE_NOCHAR 0xFFFF - -/* */ - -#define KEYMAN_LAYOUT_DEFAULT 0x000005FE - -#define KEYMANID_NONKEYMAN 0xFFFFFFFF -#define KEYMANID_IGNORE 0xFFFFFFFE -#define KEYMANID_INVALID 0xFFFFFFFD - -/* Shift flags for hotkeys (version 1.0) */ - -#define SHIFTFLAG 0x2000 -#define CTRLFLAG 0x4000 -#define ALTFLAG 0x8000 - -/* Miscellaneous flags and defines */ - -#define UM_DRAWICONS 0x01 -#define NUL '\0' - -#define MAXGROUPS 128 - -/* File version identifiers */ - -#define VERSION_30 0x00000300 -#define VERSION_31 0x00000301 -#define VERSION_32 0x00000302 -#define VERSION_40 0x00000400 -#define VERSION_50 0x00000500 -#define VERSION_501 0x00000501 -#define VERSION_60 0x00000600 -#define VERSION_70 0x00000700 -#define VERSION_80 0x00000800 -#define VERSION_90 0x00000900 -#define VERSION_100 0x00000A00 -#define VERSION_140 0x00000E00 -#define VERSION_150 0x00000F00 -#define VERSION_160 0x00001000 -#define VERSION_170 0x00001100 -#define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_170 - -/* - Special flag for WM_CHAR/WM_KEY???/WM_SYSKEY???: says that key has been - processed by Keyman. -*/ - -//#define KEYMAN_CHARFLAG 0x0000000L -#define KEYMAN_CHARFLAG 0x02000000L - -#define CHAR_TRANSTATE 0x00000001L // Flag for WM_CHAR: key is down, first repeat -#define KEYUP_TRANSTATE 0xC0000001L // Flag for WM_KEYUP: key is up, first repeat -#define KEYDOWN_TRANSTATE 0x00000001L // Flag for WM_KEYDOWN: key is down, first rpt -#define ALT_TRANSTATE 0x20000000L // Flag for WM_KEYBOARD messages: alt is down - -#define IDM_DISABLEKEY 0xFF00 - -#define WINDOWS_VERSION_3_1 0x030A -#define WINDOWS_VERSION_3_11 0x030B -#define WINDOWS_VERSION_4_0 0x035F -#define WINDOWS_VERSION_95 0x035F - -#define HKLM HKEY_LOCAL_MACHINE -#define HKCU HKEY_CURRENT_USER - -// -// DEBUGINFO states -// -#define KDS_KEYBOARD 0x0001 -#define KDS_PROGRAM 0x0002 -#define KDS_MESSAGE 0x0004 -#define KDS_INTERNAT 0x0008 - -#define KDS_CONTROL 0x8000 - -// -// Backspace flags -// - -// Delete a deadkey from context, do not pass on to app -#define BK_DEFAULT 0 -#define BK_DEADKEY 1 - -// User pressed backspace so clear deadkeys either side of next deleted character -#define BK_BACKSPACE 2 - -// Next character to delete is a Unicode surrogate pair -#define BK_SURROGATE 4 - -/* - A blank key (in a group without "using keys") cannot be '0' as that is - used for error testing and blanking out unused keys and you don't really - want that tested! -*/ - -#define BLANKKEY 0xFF // Blank key - -// Different begin types -#define BEGIN_ANSI 0 -#define BEGIN_UNICODE 1 -#define BEGIN_NEWCONTEXT 2 -#define BEGIN_POSTKEYSTROKE 3 - -//#define lpuch (LPBYTE) - -#define TSS_NONE 0 -#define TSS_BITMAP 1 -#define TSS_COPYRIGHT 2 -#define TSS_HOTKEY 3 -#define TSS_LANGUAGE 4 -#define TSS_LAYOUT 5 -#define TSS_MESSAGE 6 -#define TSS_NAME 7 -#define TSS_VERSION 8 -#define TSS_CAPSONONLY 9 -#define TSS_CAPSALWAYSOFF 10 -#define TSS_SHIFTFREESCAPS 11 -#define TSS_LANGUAGENAME 12 - -#define TSS_CALLDEFINITION 13 -#define TSS_CALLDEFINITION_LOADFAILED 14 - -#define TSS_ETHNOLOGUECODE 15 - -#define TSS_DEBUG_LINE 16 - -#define TSS_MNEMONIC 17 - -#define TSS_INCLUDECODES 18 - -#define TSS_OLDCHARPOSMATCHING 19 - -#define TSS_COMPILEDVERSION 20 -#define TSS_KEYMANCOPYRIGHT 21 - -#define TSS_CUSTOMKEYMANEDITION 22 -#define TSS_CUSTOMKEYMANEDITIONNAME 23 - -/* Keyman 7.0 system stores */ - -#define TSS__KEYMAN_60_MAX 23 - -#define TSS_VISUALKEYBOARD 24 -#define TSS_KMW_RTL 25 -#define TSS_KMW_HELPFILE 26 -#define TSS_KMW_HELPTEXT 27 -#define TSS_KMW_EMBEDJS 28 - -#define TSS_WINDOWSLANGUAGES 29 - -#define TSS__KEYMAN_70_MAX 29 - -/* Keyman 8.0 system stores */ - -#define TSS_COMPARISON 30 - -/* Keyman 9.0 system stores */ - -#define TSS_PLATFORM 31 -#define TSS_BASELAYOUT 32 -#define TSS_LAYER 33 - -#define TSS_PLATFORM_NOMATCH 0x8001 // Reserved for internal use - after platform statement is run, set to either TSS_PLATFORM_NOMATCH or TSS_PLATFORM_MATCH -#define TSS_PLATFORM_MATCH 0x8002 // Reserved for internal use - as the result will never change for the lifetime of the process. - -#define TSS_VKDICTIONARY 34 // Dictionary of virtual key names for v9 dynamic layouts -#define TSS_LAYOUTFILE 35 // Keyman 9 layer-based JSON OSK -#define TSS_KEYBOARDVERSION 36 // &keyboardversion system store // I4140 -#define TSS_KMW_EMBEDCSS 37 - -#define TSS_TARGETS 38 - -#define TSS_CASEDKEYS 39 - -#define TSS__KEYMAN_140_MAX 39 - -#define TSS_BEGIN_NEWCONTEXT 40 -#define TSS_BEGIN_POSTKEYSTROKE 41 - -#define TSS_NEWLAYER 42 -#define TSS_OLDLAYER 43 - -#define TSS__MAX 43 - -/* wm_keyman_control_internal message control codes */ - -#define KMCI_SELECTKEYBOARD 3 // I3933 -#define KMCI_SELECTKEYBOARD_TSF 4 // I3933 -#define KMCI_GETACTIVEKEYBOARD 5 // I3933 -#define KMCI_SETFOREGROUND 6 // I3933 -#define KMCI_SELECTKEYBOARD_BACKGROUND 7 // I4271 -#define KMCI_SELECTKEYBOARD_BACKGROUND_TSF 8 // I4271 - -#define FILEID_COMPILED 0x5354584B - -#define SZMAX_LANGUAGENAME 80 -#define SZMAX_KEYBOARDNAME 80 -#define SZMAX_COPYRIGHT 256 -#define SZMAX_MESSAGE 1024 - -#define UC_SENTINEL 0xFFFF -#define UC_SENTINEL_EXTENDEDEND 0x10 // was ((CODE_LASTCODE)+1)... what was I thinking? - -/* - * VK__MAX defines the highest virtual key code defined in the system = 0xFF. Custom VK codes start at 256 - */ -#define VK__MAX 255 - -#define CODE_ANY 0x01 -#define CODE_INDEX 0x02 -#define CODE_CONTEXT 0x03 -#define CODE_NUL 0x04 -#define CODE_USE 0x05 -#define CODE_RETURN 0x06 -#define CODE_BEEP 0x07 -#define CODE_DEADKEY 0x08 -// 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL -#define CODE_EXTENDED 0x0A -//#define CODE_EXTENDEDEND 0x0B deprecated -#define CODE_SWITCH 0x0C -#define CODE_KEY 0x0D -#define CODE_CLEARCONTEXT 0x0E -#define CODE_CALL 0x0F -// UC_SENTINEL_EXTENDEDEND 0x10 -#define CODE_CONTEXTEX 0x11 - -#define CODE_NOTANY 0x12 - -#define CODE_KEYMAN70_LASTCODE 0x12 - -#define CODE_SETOPT 0x13 -#define CODE_IFOPT 0x14 -#define CODE_SAVEOPT 0x15 -#define CODE_RESETOPT 0x16 - -#define CODE_KEYMAN80_LASTCODE 0x16 - -/* Keyman 9.0 codes */ - -#define CODE_IFSYSTEMSTORE 0x17 -#define CODE_SETSYSTEMSTORE 0x18 - -#define CODE_LASTCODE 0x18 - -#define KF_SHIFTFREESCAPS 0x0001 -#define KF_CAPSONONLY 0x0002 -#define KF_CAPSALWAYSOFF 0x0004 -#define KF_LOGICALLAYOUT 0x0008 -#define KF_AUTOMATICVERSION 0x0010 - -// 16.0: Support for LDML Keyboards in KMXPlus file format -#define KF_KMXPLUS 0x0020 - -#define HK_ALT 0x00010000 -#define HK_CTRL 0x00020000 -#define HK_SHIFT 0x00040000 - -#define HK_RALT_INVALID 0x00100000 -#define HK_RCTRL_INVALID 0x00200000 -#define HK_RSHIFT_INVALID 0x00400000 - -#define LCTRLFLAG 0x0001 // Left Control flag -#define RCTRLFLAG 0x0002 // Right Control flag -#define LALTFLAG 0x0004 // Left Alt flag -#define RALTFLAG 0x0008 // Right Alt flag -#define K_SHIFTFLAG 0x0010 // Either shift flag -#define K_CTRLFLAG 0x0020 // Either ctrl flag -#define K_ALTFLAG 0x0040 // Either alt flag -//#define K_METAFLAG 0x0080 // Either Meta-key flag (tentative). Not usable in keyboard rules; - // Used internally (currently, only by KMW) to ensure Meta-key - // shortcuts safely bypass rules - // Meta key = Command key on macOS, Windows key on Windows -#define CAPITALFLAG 0x0100 // Caps lock on -#define NOTCAPITALFLAG 0x0200 // Caps lock NOT on -#define NUMLOCKFLAG 0x0400 // Num lock on -#define NOTNUMLOCKFLAG 0x0800 // Num lock NOT on -#define SCROLLFLAG 0x1000 // Scroll lock on -#define NOTSCROLLFLAG 0x2000 // Scroll lock NOT on -#define ISVIRTUALKEY 0x4000 // It is a Virtual Key Sequence -#define VIRTUALCHARKEY 0x8000 // Keyman 6.0: Virtual Key Cap Sequence NOT YET - -#define K_MODIFIERFLAG 0x007F -#define K_NOTMODIFIERFLAG 0xFF00 // I4548 - -// Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the -// other modifier flag in layers, > 16 bit so not available here. -// See keys_mod_other in keyman_core_ldml.ts - -/* - These sanity checks help ensure we don't - break on-disk struct sizes when we cross - compilers, bitness and platforms. They must - correspond to the equivalent constants in - kmxfile.pas. For historical reasons, these - structures have the prefix COMP_ while - the pas versions are TKeyboardFile_, names - which correspond more closely to what the - structures are for. -*/ - -#define KEYBOARDFILEHEADER_SIZE 64 -#define KEYBOARDFILESTORE_SIZE 12 -#define KEYBOARDFILEGROUP_SIZE 24 -#define KEYBOARDFILEKEY_SIZE 20 - -struct COMP_STORE { - DWORD dwSystemID; - DWORD dpName; - DWORD dpString; - }; - -static_assert(sizeof(COMP_STORE) == KEYBOARDFILESTORE_SIZE, "COMP_STORE must be KEYBOARDFILESTORE_SIZE bytes"); - -struct COMP_KEY { - WORD Key; - WORD _reserved; - DWORD Line; - DWORD ShiftFlags; - DWORD dpOutput; - DWORD dpContext; - }; - -static_assert(sizeof(COMP_KEY) == KEYBOARDFILEKEY_SIZE, "COMP_KEY must be KEYBOARDFILEKEY_SIZE bytes"); - -struct COMP_GROUP { - DWORD dpName; - DWORD dpKeyArray; // [LPKEY] address of first item in key array - DWORD dpMatch; - DWORD dpNoMatch; - DWORD cxKeyArray; // in array entries - BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not - }; - -static_assert(sizeof(COMP_GROUP) == KEYBOARDFILEGROUP_SIZE, "COMP_GROUP must be KEYBOARDFILEGROUP_SIZE bytes"); - -struct COMP_KEYBOARD { - DWORD dwIdentifier; // 0000 Keyman compiled keyboard id - - DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 - - DWORD dwCheckSum; // 0008 As stored in keyboard. DEPRECATED as of 16.0 - DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts - DWORD IsRegistered; // 0010 - DWORD version; // 0014 keyboard version - - DWORD cxStoreArray; // 0018 in array entries - DWORD cxGroupArray; // 001C in array entries - - DWORD dpStoreArray; // 0020 [LPSTORE] address of first item in store array - DWORD dpGroupArray; // 0024 [LPGROUP] address of first item in group array - - DWORD StartGroup[2]; // 0028 index of starting groups [2 of them] - //DWORD StartGroupIndex; // StartGroup current index - - DWORD dwFlags; // 0030 Flags for the keyboard file - - DWORD dwHotKey; // 0034 standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) - - //DWORD dpName; // offset of name - //DWORD dpLanguageName; // offset of language name; - //DWORD dpCopyright; // offset of copyright - //DWORD dpMessage; // offset of message in Keyboard About box - - DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file - DWORD dwBitmapSize; // 003C size in bytes of the bitmaps - }; - -static_assert(sizeof(COMP_KEYBOARD) == KEYBOARDFILEHEADER_SIZE, "COMP_KEYBOARD must be KEYBOARDFILEHEADER_SIZE bytes"); - -typedef COMP_KEYBOARD *PCOMP_KEYBOARD; -typedef COMP_STORE *PCOMP_STORE; -typedef COMP_KEY *PCOMP_KEY; -typedef COMP_GROUP *PCOMP_GROUP; - - -typedef struct _COMPILER_OPTIONS { - DWORD dwSize; - BOOL ShouldAddCompilerVersion; -} COMPILER_OPTIONS; - -typedef COMPILER_OPTIONS *PCOMPILER_OPTIONS; - -typedef int (CALLBACK *CompilerMessageProc)(int line, DWORD dwMsgCode, LPSTR szText); - -#endif // _COMPILER_H - diff --git a/developer/src/kmanalyze/pch.h b/developer/src/kmanalyze/pch.h index 8a81c4235c2..a330cbeb9e9 100644 --- a/developer/src/kmanalyze/pch.h +++ b/developer/src/kmanalyze/pch.h @@ -16,7 +16,7 @@ #include #include #include "../../../common/windows/cpp/include/keymansentry.h" -#include "../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../common/include/kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/windows/cpp/include/unicode.h" #include "../../../common/windows/cpp/include/xstring.h" diff --git a/developer/src/kmdecomp/kmdecomp.cpp b/developer/src/kmdecomp/kmdecomp.cpp index c6a085fe2e0..32e0502e44d 100644 --- a/developer/src/kmdecomp/kmdecomp.cpp +++ b/developer/src/kmdecomp/kmdecomp.cpp @@ -23,7 +23,7 @@ #include "../../../common/windows/cpp/include/keymansentry.h" #include "../../../common/windows/cpp/include/keymanversion.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" -#include "../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../common/include/kmx_file.h" BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard, LPBYTE *lpBitmap, DWORD *cbBitmap); diff --git a/developer/src/kmdecomp/savekeyboard.cpp b/developer/src/kmdecomp/savekeyboard.cpp index e71da93dbdb..7ed3b21f070 100644 --- a/developer/src/kmdecomp/savekeyboard.cpp +++ b/developer/src/kmdecomp/savekeyboard.cpp @@ -28,7 +28,7 @@ #include -#include "../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../common/include/kmx_file.h" #include "../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "../../../common/windows/cpp/include/vkeys.h" @@ -93,6 +93,7 @@ const PWCHAR StoreTokens[] = { // I4652 L"", // TSS_BEGIN_POSTKEYSTROKE SSN__PREFIX L"NEWLAYER", SSN__PREFIX L"OLDLAYER", + SSN__PREFIX L"DISPLAYMAP", NULL }; diff --git a/windows/src/engine/keyman32/keyman64.h b/windows/src/engine/keyman32/keyman64.h index 4a42bb3abd3..6f8e62dba2b 100644 --- a/windows/src/engine/keyman32/keyman64.h +++ b/windows/src/engine/keyman32/keyman64.h @@ -75,7 +75,7 @@ #include #include #include -#include "../../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../../common/include/kmx_file.h" #include "../../../../common/windows/cpp/include/registry.h" #include "../../../../common/windows/cpp/include/unicode.h" #include "../../../../common/windows/cpp/include/xstring.h" @@ -124,6 +124,12 @@ #define EXTRAINFO_FLAG_SERIALIZED_USER_KEY_EVENT 0x4B4D0000 +// Reserved for hotkey remapping, aligns with HK_ defined in kmx_file.h +#define HK_RALT_INVALID 0x00100000 +#define HK_RCTRL_INVALID 0x00200000 +#define HK_RSHIFT_INVALID 0x00400000 + + /***************************************************************************/ // wm_keyman diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h index 058ea5cf172..a80a27bed77 100644 --- a/windows/src/engine/keyman32/keymanengine.h +++ b/windows/src/engine/keyman32/keymanengine.h @@ -38,7 +38,7 @@ #include #include #include -#include "../../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../../common/include/kmx_file.h" #include #include // for imx integration #include // for intermediate context diff --git a/windows/src/engine/kmtip/kmkey.cpp b/windows/src/engine/kmtip/kmkey.cpp index 609c98e4314..14a50478995 100644 --- a/windows/src/engine/kmtip/kmkey.cpp +++ b/windows/src/engine/kmtip/kmkey.cpp @@ -37,7 +37,7 @@ #include "pch.h" #include "kmtip.h" #include "editsess.h" -#include "../../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../../common/include/kmx_file.h" #include "../../../../common/windows/cpp/include/registry.h" #include "..\keymanmc\keymanmc.h" diff --git a/windows/src/engine/mcompile/pch.h b/windows/src/engine/mcompile/pch.h index 2de972b9f97..acbd5b6400f 100644 --- a/windows/src/engine/mcompile/pch.h +++ b/windows/src/engine/mcompile/pch.h @@ -12,6 +12,6 @@ #include "mc_kmxfile.h" #include "mc_syskbd.h" #include "../../../../common/windows/cpp/include/crc32.h" -#include "../../../../common/windows/cpp/include/legacy_kmx_file.h" +#include "../../../../common/include/kmx_file.h" #include "../../../../common/windows/cpp/include/xstring.h" #include "../../../../common/windows/cpp/include/keymansentry.h" From 31c8f09155b41ca1fbbe78865d4bf2f4ec886eb0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 Sep 2025 14:22:13 +0200 Subject: [PATCH 03/37] refactor(windows): consolidate mc_kmxfile.h Use legacy_kmx_memory.h instead of mc_kmxfile.h for mcompile. Removing the unused 'hbitmap' and replacing with the binary file dword data. Test-bot: skip --- .../windows/cpp/include/legacy_kmx_memory.h | 40 +++++------ windows/src/engine/mcompile/mc_kmxfile.h | 66 ------------------- windows/src/engine/mcompile/mcompile.h | 18 ++--- windows/src/engine/mcompile/pch.h | 2 +- 4 files changed, 29 insertions(+), 97 deletions(-) delete mode 100644 windows/src/engine/mcompile/mc_kmxfile.h diff --git a/common/windows/cpp/include/legacy_kmx_memory.h b/common/windows/cpp/include/legacy_kmx_memory.h index 771a4d57ca9..b0c03f271ea 100644 --- a/common/windows/cpp/include/legacy_kmx_memory.h +++ b/common/windows/cpp/include/legacy_kmx_memory.h @@ -21,41 +21,37 @@ typedef struct tagKEY typedef struct tagGROUP { PWSTR dpName; - LPKEY dpKeyArray; // [LPKEY] address of first item in key array + LPKEY dpKeyArray; // [LPKEY] address of first item in key array PWSTR dpMatch; PWSTR dpNoMatch; - DWORD cxKeyArray; // in array entries - BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not + DWORD cxKeyArray; // in array entries + BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not } GROUP, * LPGROUP; typedef struct tagKEYBOARD { - DWORD dwIdentifier; // Keyman compiled keyboard id + DWORD dwIdentifier; // Keyman compiled keyboard id - DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 + DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 - DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 - DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts - DWORD IsRegistered; // layout id, from same registry key - DWORD version; // keyboard version + DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 + DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + DWORD IsRegistered; // layout id, from same registry key + DWORD version; // keyboard version - DWORD cxStoreArray; // in array entries - DWORD cxGroupArray; // in array entries + DWORD cxStoreArray; // in array entries + DWORD cxGroupArray; // in array entries - LPSTORE dpStoreArray; // [LPSTORE] address of first item in store array, from start of file - LPGROUP dpGroupArray; // [LPGROUP] address of first item in group array, from start of file + LPSTORE dpStoreArray; // [LPSTORE] address of first item in store array, from start of file + LPGROUP dpGroupArray; // [LPGROUP] address of first item in group array, from start of file - DWORD StartGroup[2]; // index of starting groups [2 of them] + DWORD StartGroup[2]; // index of starting groups [2 of them] // Ansi=0, Unicode=1 - DWORD dwFlags; // Flags for the keyboard file + DWORD dwFlags; // Flags for the keyboard file - DWORD dwHotKey; // standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + DWORD dwHotKey; // standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) - //PWSTR dpName; // offset of name - //PWSTR dpLanguageName; // offset of language name; - //PWSTR dpCopyright; // offset of copyright - //PWSTR dpMessage; // offset of message in Keyboard About box - - HBITMAP hBitmap; // handle to the bitmap in the file; + DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file + DWORD dwBitmapSize; // 003C size in bytes of the bitmaps } KEYBOARD, * LPKEYBOARD; diff --git a/windows/src/engine/mcompile/mc_kmxfile.h b/windows/src/engine/mcompile/mc_kmxfile.h deleted file mode 100644 index 557ba968c73..00000000000 --- a/windows/src/engine/mcompile/mc_kmxfile.h +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#ifndef _KMXFILE_H -#define _KMXFILE_H - -typedef struct tagSTORE { - DWORD dwSystemID; - PWSTR dpName; - PWSTR dpString; -} STORE, *LPSTORE; - -typedef struct tagKEY { - WCHAR Key; - DWORD Line; - DWORD ShiftFlags; - PWSTR dpOutput; - PWSTR dpContext; -} KEY, *LPKEY; - - -typedef struct tagGROUP { - PWSTR dpName; - LPKEY dpKeyArray; // [LPKEY] address of first item in key array - PWSTR dpMatch; - PWSTR dpNoMatch; - DWORD cxKeyArray; // in array entries - BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not -} GROUP, *LPGROUP; - - -typedef struct tagKEYBOARD { - DWORD dwIdentifier; // Keyman compiled keyboard id - - DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 - - DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 - DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts - DWORD IsRegistered; // layout id, from same registry key - DWORD version; // keyboard version - - DWORD cxStoreArray; // in array entries - DWORD cxGroupArray; // in array entries - - LPSTORE dpStoreArray; // [LPSTORE] address of first item in store array, from start of file - LPGROUP dpGroupArray; // [LPGROUP] address of first item in group array, from start of file - - DWORD StartGroup[2]; // index of starting groups [2 of them] - // Ansi=0, Unicode=1 - - DWORD dwFlags; // Flags for the keyboard file - - DWORD dwHotKey; // standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) - - //PWSTR dpName; // offset of name - //PWSTR dpLanguageName; // offset of language name; - //PWSTR dpCopyright; // offset of copyright - //PWSTR dpMessage; // offset of message in Keyboard About box - - DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file - DWORD dwBitmapSize; // 003C size in bytes of the bitmaps - //HBITMAP hBitmap; // handle to the bitmap in the file; -} KEYBOARD, *LPKEYBOARD; - -BOOL LoadKeyboard(LPWSTR fileName, LPKEYBOARD *lpKeyboard); - -#endif diff --git a/windows/src/engine/mcompile/mcompile.h b/windows/src/engine/mcompile/mcompile.h index 68f1da807cc..46b2dce6bb7 100644 --- a/windows/src/engine/mcompile/mcompile.h +++ b/windows/src/engine/mcompile/mcompile.h @@ -1,20 +1,20 @@ /* Name: mcompile Copyright: Copyright (C) 2003-2017 SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 3 Aug 2014 Modified Date: 3 Aug 2014 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 03 Aug 2014 - mcdurdin - I4353 - V9.0 - mnemonic layout recompiler mixes up deadkey rules - + */ #include @@ -28,3 +28,5 @@ struct DeadkeyMapping { // I4353 }; extern std::vector FDeadkeys; // I4353 + +BOOL LoadKeyboard(LPWSTR fileName, LPKEYBOARD *lpKeyboard); diff --git a/windows/src/engine/mcompile/pch.h b/windows/src/engine/mcompile/pch.h index acbd5b6400f..eb7a71f7296 100644 --- a/windows/src/engine/mcompile/pch.h +++ b/windows/src/engine/mcompile/pch.h @@ -8,8 +8,8 @@ #include "targetver.h" #include +#include "../../../../common/windows/cpp/include/legacy_kmx_memory.h" #include "mcompile.h" -#include "mc_kmxfile.h" #include "mc_syskbd.h" #include "../../../../common/windows/cpp/include/crc32.h" #include "../../../../common/include/kmx_file.h" From 49846fea2dcc354d77e694a2f0dfb02d7b59a3d9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 Sep 2025 14:51:40 +0200 Subject: [PATCH 04/37] feat(common): add KF_KMXPLUSOSK and VERSION_190 to kmx_file.h Add version 19.0 flags and metadata relating to embed-osk-in-kmx to kmx_file.h and corresponding headers for other languages. Test-bot: skip --- common/include/kmx_file.h | 20 +++++++++++++++---- common/web/types/src/kmx/kmx.ts | 16 +++++++++++---- .../delphi/keyboards/kmxfileconsts.pas | 7 +++++-- docs/file-formats/kmx-file-format.md | 2 ++ .../KeymanEngine4Mac/KME/KMBinaryFileFormat.h | 7 +++++-- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index ca37364815d..ff6fda73996 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -52,8 +52,10 @@ namespace kmx { #define VERSION_160 0x00001000 #define VERSION_170 0x00001100 +#define VERSION_190 0x00001300 + #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_170 +#define VERSION_MAX VERSION_190 // // Backspace types @@ -270,14 +272,24 @@ namespace kmx { #define C_CODE_IFSYSTEMSTORE(store, val1, val2) U_UC_SENTINEL U_CODE_IFSYSTEMSTORE store val1 val2 #define C_CODE_SETSYSTEMSTORE(store, val) U_UC_SENTINEL U_CODE_SETSYSTEMSTORE store val +// +// COMP_KEYBOARD.dwFlags bitfield +// + #define KF_SHIFTFREESCAPS 0x0001 #define KF_CAPSONONLY 0x0002 #define KF_CAPSALWAYSOFF 0x0004 #define KF_LOGICALLAYOUT 0x0008 #define KF_AUTOMATICVERSION 0x0010 -// 16.0: Support for LDML Keyboards in KMXPlus file format -#define KF_KMXPLUS 0x0020 +/** 16.0+: A `COMP_KEYBOARD_KMXPLUSINFO` structure is present immediately after `COMP_KEYBOARD` */ +#define KF_KMXPLUS 0x0020 + +/** + * 19.0+: The `COMP_KEYBOARD_KMXPLUSINFO` structure contains only OSK, and not a + * LDML keyboard; KF_KMXPLUS should not be set + */ +#define KF_KMXPLUSOSK 0x0040 #define HK_ALT 0x00010000 #define HK_CTRL 0x00020000 @@ -365,7 +377,7 @@ struct COMP_KEYBOARD_KMXPLUSINFO { }; /** - * Only valid if comp_keyboard.dwFlags&KF_KMXPLUS + * Only valid if comp_keyboard.dwFlags&(KF_KMXPLUS|KF_KMXPLUSOSK) */ struct COMP_KEYBOARD_EX { struct COMP_KEYBOARD header; // 0000 see COMP_KEYBOARD diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts index 4f53e29fde1..002d0a1f16d 100644 --- a/common/web/types/src/kmx/kmx.ts +++ b/common/web/types/src/kmx/kmx.ts @@ -23,7 +23,8 @@ export enum KMX_Version { VERSION_140 = 0x00000E00, VERSION_150 = 0x00000F00, VERSION_160 = 0x00001000, - VERSION_170 = 0x00001100 + VERSION_170 = 0x00001100, + VERSION_190 = 0x00001300, }; @@ -164,9 +165,10 @@ export class KMXFile { public static readonly VERSION_150 = KMX_Version.VERSION_150; public static readonly VERSION_160 = KMX_Version.VERSION_160; public static readonly VERSION_170 = KMX_Version.VERSION_170; + public static readonly VERSION_190 = KMX_Version.VERSION_190; public static readonly VERSION_MIN = this.VERSION_50; - public static readonly VERSION_MAX = this.VERSION_170; + public static readonly VERSION_MAX = this.VERSION_190; // // Backspace types @@ -337,8 +339,14 @@ export class KMXFile { public static readonly KF_LOGICALLAYOUT = 0x0008; public static readonly KF_AUTOMATICVERSION = 0x0010; - // 16.0: Support for LDML Keyboards in KMXPlus file format - public static readonly KF_KMXPLUS = 0x0020; + /** 16.0+: A `COMP_KEYBOARD_KMXPLUSINFO` structure is present immediately after `COMP_KEYBOARD` */ + public static readonly KF_KMXPLUS = 0x0020; + + /** + * 19.0+: The `COMP_KEYBOARD_KMXPLUSINFO` structure contains only OSK, and not a + * LDML keyboard; KF_KMXPLUS should not be set + */ + public static readonly KF_KMXPLUSOSK = 0x0040; public static readonly HK_ALT = 0x00010000; public static readonly HK_CTRL = 0x00020000; diff --git a/common/windows/delphi/keyboards/kmxfileconsts.pas b/common/windows/delphi/keyboards/kmxfileconsts.pas index 8e1955aaa6a..7f901f90efa 100644 --- a/common/windows/delphi/keyboards/kmxfileconsts.pas +++ b/common/windows/delphi/keyboards/kmxfileconsts.pas @@ -99,9 +99,10 @@ interface VERSION_150 = $00000F00; VERSION_160 = $00001000; VERSION_170 = $00001100; + VERSION_190 = $00001300; VERSION_MIN = VERSION_50; - VERSION_MAX = VERSION_170; + VERSION_MAX = VERSION_190; VERSION_MASK_MINOR = $00FF; VERSION_MASK_MAJOR = $FF00; @@ -216,7 +217,9 @@ interface TSS_NEWLAYER = 42; TSS_OLDLAYER = 43; - TSS__MAX = 43; + TSS_DISPLAYMAP = 44; + + TSS__MAX = 44; type TSystemStore = (ssNone = 0, ssBitmap = 1, ssCopyright = 2, ssHotkey = 3, ssLanguage = 4, ssLayout = 5, ssMessage = 6, diff --git a/docs/file-formats/kmx-file-format.md b/docs/file-formats/kmx-file-format.md index e13575ae4a7..931dbadc3d2 100644 --- a/docs/file-formats/kmx-file-format.md +++ b/docs/file-formats/kmx-file-format.md @@ -79,6 +79,7 @@ the `KMX_Version` enum: | `0x00000F00` | `VERSION_150` | Keyman 15.0 | | `0x00001000` | `VERSION_160` | Keyman 16.0 | | `0x00001100` | `VERSION_170` | Keyman 17.0 | +| `0x00001300` | `VERSION_190` | Keyman 19.0 | ### `StartGroup` @@ -100,6 +101,7 @@ The following flags are defined for .kmx keyboards: | `0x00000008` | `KF_LOGICALLAYOUT` | Unused, should never be set | | `0x00000010` | `KF_AUTOMATICVERSION` | The compiler determined the minimum version of Keyman automatically (see [`&Version`]) | | `0x00000020` | `KF_KMXPLUS` | 16.0+: A `COMP_KEYBOARD_KMXPLUSINFO` structure is present immediately after `COMP_KEYBOARD` | +| `0x00000040` | `KF_KMXPLUSOSK` | 19.0+: The `COMP_KEYBOARD_KMXPLUSINFO` structure contains only OSK, and not a LDML keyboard | ### `dwHotKey`: keyboard hotkeys diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h index 3951d5831d4..90dce7da5a0 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h @@ -52,8 +52,9 @@ struct COMP_KEYBOARD { #define VERSION_150 0x00000F00 #define VERSION_160 0x00001000 #define VERSION_170 0x00001100 +#define VERSION_190 0x00001300 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_170 +#define VERSION_MAX VERSION_190 struct COMP_STORE { DWORD dwSystemID; @@ -119,8 +120,10 @@ struct COMP_STORE { #define TSS_NEWLAYER 42 #define TSS_OLDLAYER 43 #define TSS__KEYMAN_150_MAX 43 +/* Keyman 17.0 system stores */ +#define TSS_DISPLAYMAP 44 -#define TSS__MAX 43 +#define TSS__MAX 44 /* KVK file format definitions */ From c17ea6722a911eefc112836135daf37eca170998 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 2 Oct 2025 13:24:43 +0200 Subject: [PATCH 05/37] docs(common): make KF_KMXPLUSOSK flag independent of KF_KMXPLUS --- common/include/kmx_file.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index ff6fda73996..d0be382048d 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -286,8 +286,8 @@ namespace kmx { #define KF_KMXPLUS 0x0020 /** - * 19.0+: The `COMP_KEYBOARD_KMXPLUSINFO` structure contains only OSK, and not a - * LDML keyboard; KF_KMXPLUS should not be set + * 19.0+: The `COMP_KEYBOARD_KMXPLUSINFO` structure contains a v19 embedded OSK; + * may be used with or without KF_KMXPLUS. */ #define KF_KMXPLUSOSK 0x0040 From 4d436e4bcfb33d63e8ef2df2665c8d6d37b4c98d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 13 Oct 2025 14:20:59 +0200 Subject: [PATCH 06/37] Update mcompile.h Build-bot: skip --- windows/src/engine/mcompile/mcompile.h | 1 - 1 file changed, 1 deletion(-) diff --git a/windows/src/engine/mcompile/mcompile.h b/windows/src/engine/mcompile/mcompile.h index 46b2dce6bb7..be9c39a8ce7 100644 --- a/windows/src/engine/mcompile/mcompile.h +++ b/windows/src/engine/mcompile/mcompile.h @@ -14,7 +14,6 @@ Todo: Notes: History: 03 Aug 2014 - mcdurdin - I4353 - V9.0 - mnemonic layout recompiler mixes up deadkey rules - */ #include From 9406e16294dca40a8b53b3029a9e17f58bccee0a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 15 Oct 2025 10:21:17 +0200 Subject: [PATCH 07/37] refactor(common): split KMX+ binary format from in-memory representation Split kmx-plus.ts into two files, so that the in-memory parsed representation of the file can be maintained separately from the binary structures, which need to maintain version metadata. This refactor makes no functional changes. Test-bot: skip Build-bot: build --- .../types/src/kmx/kmx-plus/kmx-plus-file.ts | 356 ++++++++++++++++++ common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 356 +----------------- 2 files changed, 359 insertions(+), 353 deletions(-) create mode 100644 common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts new file mode 100644 index 00000000000..2644247323a --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts @@ -0,0 +1,356 @@ +import * as KMX from '../kmx.js'; +import * as r from 'restructure'; +import KMXFile = KMX.KMXFile; + +export class KMXPlusFileFormat extends KMXFile { + + /* KMXPlus file structures */ + + public readonly COMP_PLUS_SECT_ITEM: any; + public readonly COMP_PLUS_SECT: any; + + // COMP_PLUS_BKSP == COMP_PLUS_TRAN + public readonly COMP_PLUS_BKSP_ITEM: any; + public readonly COMP_PLUS_BKSP: any; + + public readonly COMP_PLUS_DISP_ITEM: any; + public readonly COMP_PLUS_DISP: any; + + public readonly COMP_PLUS_ELEM_ELEMENT: any; + public readonly COMP_PLUS_ELEM_STRING: any; + public readonly COMP_PLUS_ELEM: any; + + // COMP_PLUS_KEYS is now COMP_PLUS_KEYS_KMAP + + public readonly COMP_PLUS_LAYR_ENTRY: any; + public readonly COMP_PLUS_LAYR_KEY: any; + public readonly COMP_PLUS_LAYR_LIST: any; + public readonly COMP_PLUS_LAYR_ROW: any; + public readonly COMP_PLUS_LAYR: any; + + public readonly COMP_PLUS_KEYS_FLICK: any; + public readonly COMP_PLUS_KEYS_FLICKS: any; + public readonly COMP_PLUS_KEYS_KEY: any; + public readonly COMP_PLUS_KEYS_KMAP: any; + public readonly COMP_PLUS_KEYS: any; + + public readonly COMP_PLUS_LIST_LIST: any; + public readonly COMP_PLUS_LIST_INDEX: any; + public readonly COMP_PLUS_LIST: any; + + public readonly COMP_PLUS_LOCA_ITEM: any; + public readonly COMP_PLUS_LOCA: any; + + public readonly COMP_PLUS_META: any; + + public readonly COMP_PLUS_STRS_ITEM: any; + public readonly COMP_PLUS_STRS: any; + + public readonly COMP_PLUS_TRAN_GROUP: any; + public readonly COMP_PLUS_TRAN_TRANSFORM: any; + public readonly COMP_PLUS_TRAN_REORDER: any; + public readonly COMP_PLUS_TRAN: any; + + public readonly COMP_PLUS_USET_USET: any; + public readonly COMP_PLUS_USET_RANGE: any; + public readonly COMP_PLUS_USET: any; + + public readonly COMP_PLUS_VKEY_ITEM: any; + public readonly COMP_PLUS_VKEY: any; + + public readonly COMP_PLUS_VARS: any; + public readonly COMP_PLUS_VARS_ITEM: any; + + constructor() { + super(); + // Binary-correct structures matching kmx_plus.h + + // helpers + const STR_REF = r.uint32le; + const ELEM_REF = r.uint32le; + const LIST_REF = r.uint32le; + const STR_OR_CHAR32 = r.uint32le; + const CHAR32 = r.uint32le; + const STR_OR_CHAR32_OR_USET = r.uint32le; + const IDENT = r.uint32le; + // 'sect' + + this.COMP_PLUS_SECT_ITEM = new r.Struct({ + sect: r.uint32le, + offset: r.uint32le //? new r.VoidPointer(r.uint32le, {type: 'global'}) + }); + + this.COMP_PLUS_SECT = new r.Struct({ + ident: IDENT, + size: r.uint32le, + total: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_SECT_ITEM, 'count') + }); + + // 'bksp' - see 'tran' + + // 'disp' + this.COMP_PLUS_DISP_ITEM = new r.Struct({ + to: STR_REF, + id: STR_REF, + display: STR_REF, + }); + + this.COMP_PLUS_DISP = new r.Struct({ + ident: IDENT, + size: r.uint32le, + count: r.uint32le, + baseCharacter: CHAR32, + items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'), + }); + + // 'elem' + + this.COMP_PLUS_ELEM_ELEMENT = new r.Struct({ + element: STR_OR_CHAR32_OR_USET, + flags: r.uint32le + }); + + this.COMP_PLUS_ELEM_STRING = new r.Struct({ + offset: r.uint32le, + length: r.uint32le + }); + + this.COMP_PLUS_ELEM = new r.Struct({ + ident: IDENT, + size: r.uint32le, + count: r.uint32le, + strings: new r.Array(this.COMP_PLUS_ELEM_STRING, 'count') + // + variable subtable: Element data (see KMXPlusBuilder.emitElements()) + }); + + // 'finl' - see 'tran' + + // 'keys' - see 'keys.kmap' + + // 'layr' + + this.COMP_PLUS_LAYR_ENTRY = new r.Struct({ + id: r.uint32le, // str + mod: r.uint32le, // bitfield + row: r.uint32le, // index into rows + count: r.uint32le, + }); + + this.COMP_PLUS_LAYR_KEY = new r.Struct({ + key: r.uint32le, // str: key id + }); + + this.COMP_PLUS_LAYR_LIST = new r.Struct({ + hardware: STR_REF, // str: hardware name + layer: r.uint32le, // index into layers + count: r.uint32le, + minDeviceWidth: r.uint32le, // integer: millimeters + }); + + this.COMP_PLUS_LAYR_ROW = new r.Struct({ + key: r.uint32le, + count: r.uint32le, + }); + + this.COMP_PLUS_LAYR = new r.Struct({ + ident: IDENT, + size: r.uint32le, + listCount: r.uint32le, + layerCount: r.uint32le, + rowCount: r.uint32le, + keyCount: r.uint32le, + lists: new r.Array(this.COMP_PLUS_LAYR_LIST, 'listCount'), + layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'), + rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'), + keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'), + }); + + // 'keys' + + this.COMP_PLUS_KEYS_FLICK = new r.Struct({ + directions: LIST_REF, // list + to: STR_OR_CHAR32, // str | codepoint + }); + + this.COMP_PLUS_KEYS_FLICKS = new r.Struct({ + count: r.uint32le, + flick: r.uint32le, + id: STR_REF, // str + }); + + this.COMP_PLUS_KEYS_KEY = new r.Struct({ + to: STR_OR_CHAR32, // str | codepoint + flags: r.uint32le, + id: STR_REF, // str + switch: STR_REF, // str + width: r.uint32le, // width*10 ( 1 = 0.1 keys) + longPress: LIST_REF, // list index + longPressDefault: STR_REF, // str + multiTap: LIST_REF, // list index + flicks: r.uint32le, // index into flicks table + }); + + this.COMP_PLUS_KEYS_KMAP = new r.Struct({ + vkey: r.uint32le, + mod: r.uint32le, + key: r.uint32le, // index into 'keys' subtable + }); + + this.COMP_PLUS_KEYS = new r.Struct({ + ident: IDENT, + size: r.uint32le, + keyCount: r.uint32le, + flicksCount: r.uint32le, + flickCount: r.uint32le, + kmapCount: r.uint32le, + keys: new r.Array(this.COMP_PLUS_KEYS_KEY, 'keyCount'), + flicks: new r.Array(this.COMP_PLUS_KEYS_FLICKS, 'flicksCount'), + flick: new r.Array(this.COMP_PLUS_KEYS_FLICK, 'flickCount'), + kmap: new r.Array(this.COMP_PLUS_KEYS_KMAP, 'kmapCount'), + }); + + // 'list' + + this.COMP_PLUS_LIST_LIST = new r.Struct({ + index: r.uint32le, + count: r.uint32le, + }); + + this.COMP_PLUS_LIST_INDEX = new r.Struct({ + str: STR_REF, // str + }); + + this.COMP_PLUS_LIST = new r.Struct({ + ident: IDENT, + size: r.uint32le, + listCount: r.uint32le, + indexCount: r.uint32le, + lists: new r.Array(this.COMP_PLUS_LIST_LIST, 'listCount'), + indices: new r.Array(this.COMP_PLUS_LIST_INDEX, 'indexCount'), + }); + + // 'loca' + + this.COMP_PLUS_LOCA_ITEM = r.uint32le; //str + + this.COMP_PLUS_LOCA = new r.Struct({ + ident: IDENT, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_LOCA_ITEM, 'count') + }); + + // 'meta' + + this.COMP_PLUS_META = new r.Struct({ + ident: IDENT, + size: r.uint32le, + author: STR_REF, //str + conform: STR_REF, //str + layout: STR_REF, //str + name: STR_REF, //str + indicator: STR_REF, //str + version: STR_REF, //str + settings: r.uint32le, //new r.Bitfield(r.uint32le, ['normalizationDisabled']) + }); + + // 'name' is gone + + // 'ordr' now part of 'tran' + + // 'strs' + + this.COMP_PLUS_STRS_ITEM = new r.Struct({ + // While we use length which is number of utf-16 code units excluding null terminator, + // we always write a null terminator, so we can get restructure to do that for us here + offset: r.uint32le, //? new r.Pointer(r.uint32le, new r.String(null, 'utf16le')), + length: r.uint32le + }); + + this.COMP_PLUS_STRS = new r.Struct({ + ident: IDENT, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_STRS_ITEM, 'count') + // + variable subtable: String data (see KMXPlusBuilder.emitStrings()) + }); + + // 'tran' + + this.COMP_PLUS_TRAN_GROUP = new r.Struct({ + type: r.uint32le, //type of group + count: r.uint32le, //number of items + index: r.uint32le, //index into subtable + }); + + this.COMP_PLUS_TRAN_TRANSFORM = new r.Struct({ + from: STR_REF, //str + to: STR_REF, //str + mapFrom: ELEM_REF, //elem + mapTo: ELEM_REF //elem + }); + + this.COMP_PLUS_TRAN_REORDER = new r.Struct({ + elements: ELEM_REF, //elem + before: ELEM_REF, //elem + }); + + this.COMP_PLUS_TRAN = new r.Struct({ + ident: IDENT, + size: r.uint32le, + groupCount: r.uint32le, + transformCount: r.uint32le, + reorderCount: r.uint32le, + groups: new r.Array(this.COMP_PLUS_TRAN_GROUP, 'groupCount'), + transforms: new r.Array(this.COMP_PLUS_TRAN_TRANSFORM, 'transformCount'), + reorders: new r.Array(this.COMP_PLUS_TRAN_REORDER, 'reorderCount'), + }); + + // 'uset' + + this.COMP_PLUS_USET_USET = new r.Struct({ + range: r.uint32le, + count: r.uint32le, + pattern: STR_REF, // str + }); + + this.COMP_PLUS_USET_RANGE = new r.Struct({ + start: CHAR32, + end: CHAR32, + }); + + this.COMP_PLUS_USET = new r.Struct({ + ident: IDENT, + size: r.uint32le, + usetCount: r.uint32le, + rangeCount: r.uint32le, + usets: new r.Array(this.COMP_PLUS_USET_USET, 'usetCount'), + ranges: new r.Array(this.COMP_PLUS_USET_RANGE, 'rangeCount'), + }); + + // 'vars' + + this.COMP_PLUS_VARS_ITEM = new r.Struct({ + type: r.uint32le, + id: STR_REF, // str + value: STR_REF, // str + elem: ELEM_REF, + }); + + this.COMP_PLUS_VARS = new r.Struct({ + ident: IDENT, + size: r.uint32le, + markers: LIST_REF, + varCount: r.uint32le, + varEntries: new r.Array(this.COMP_PLUS_VARS_ITEM, 'varCount'), + }); + + // 'vkey' is removed + + // Aliases + + this.COMP_PLUS_BKSP = this.COMP_PLUS_TRAN; + } +} diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 4fd5dcd6320..e46ca58ced6 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -1,9 +1,7 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; -import * as r from 'restructure'; import { ElementString } from './element-string.js'; import { ListItem } from '../../ldml-keyboard/string-list.js'; import * as util from '../../util/util.js'; -import * as KMX from '../kmx.js'; import { UnicodeSetParser, UnicodeSet } from '../../ldml-keyboard/unicodeset-parser-api.js'; import { VariableParser } from '../../ldml-keyboard/pattern-parser.js'; import { MarkerParser } from '../../ldml-keyboard/pattern-parser.js'; @@ -12,7 +10,7 @@ import isOneChar = util.isOneChar; import toOneChar = util.toOneChar; import unescapeString = util.unescapeString; import escapeStringForRegex = util.escapeStringForRegex; -import KMXFile = KMX.KMXFile; +import { KMXPlusFileFormat } from './kmx-plus-file.js'; // Implementation of file structures from /core/src/ldml/C7043_ldml.md // Writer in kmx-builder.ts @@ -629,356 +627,8 @@ export interface KMXPlusData { vars?: Vars; }; -export class KMXPlusFile extends KMXFile { - - /* KMXPlus file structures */ - - public readonly COMP_PLUS_SECT_ITEM: any; - public readonly COMP_PLUS_SECT: any; - - // COMP_PLUS_BKSP == COMP_PLUS_TRAN - public readonly COMP_PLUS_BKSP_ITEM: any; - public readonly COMP_PLUS_BKSP: any; - - public readonly COMP_PLUS_DISP_ITEM: any; - public readonly COMP_PLUS_DISP: any; - - public readonly COMP_PLUS_ELEM_ELEMENT: any; - public readonly COMP_PLUS_ELEM_STRING: any; - public readonly COMP_PLUS_ELEM: any; - - // COMP_PLUS_KEYS is now COMP_PLUS_KEYS_KMAP - - public readonly COMP_PLUS_LAYR_ENTRY: any; - public readonly COMP_PLUS_LAYR_KEY: any; - public readonly COMP_PLUS_LAYR_LIST: any; - public readonly COMP_PLUS_LAYR_ROW: any; - public readonly COMP_PLUS_LAYR: any; - - public readonly COMP_PLUS_KEYS_FLICK: any; - public readonly COMP_PLUS_KEYS_FLICKS: any; - public readonly COMP_PLUS_KEYS_KEY: any; - public readonly COMP_PLUS_KEYS_KMAP: any; - public readonly COMP_PLUS_KEYS: any; - - public readonly COMP_PLUS_LIST_LIST: any; - public readonly COMP_PLUS_LIST_INDEX: any; - public readonly COMP_PLUS_LIST: any; - - public readonly COMP_PLUS_LOCA_ITEM: any; - public readonly COMP_PLUS_LOCA: any; - - public readonly COMP_PLUS_META: any; - - public readonly COMP_PLUS_STRS_ITEM: any; - public readonly COMP_PLUS_STRS: any; - - public readonly COMP_PLUS_TRAN_GROUP: any; - public readonly COMP_PLUS_TRAN_TRANSFORM: any; - public readonly COMP_PLUS_TRAN_REORDER: any; - public readonly COMP_PLUS_TRAN: any; - - public readonly COMP_PLUS_USET_USET: any; - public readonly COMP_PLUS_USET_RANGE: any; - public readonly COMP_PLUS_USET: any; - - public readonly COMP_PLUS_VKEY_ITEM: any; - public readonly COMP_PLUS_VKEY: any; - - public readonly COMP_PLUS_VARS: any; - public readonly COMP_PLUS_VARS_ITEM: any; +export class KMXPlusFile extends KMXPlusFileFormat { /* File in-memory data */ - public kmxplus: KMXPlusData = { }; - - constructor() { - super(); - // Binary-correct structures matching kmx_plus.h - - // helpers - const STR_REF = r.uint32le; - const ELEM_REF = r.uint32le; - const LIST_REF = r.uint32le; - const STR_OR_CHAR32 = r.uint32le; - const CHAR32 = r.uint32le; - const STR_OR_CHAR32_OR_USET = r.uint32le; - const IDENT = r.uint32le; - // 'sect' - - this.COMP_PLUS_SECT_ITEM = new r.Struct({ - sect: r.uint32le, - offset: r.uint32le //? new r.VoidPointer(r.uint32le, {type: 'global'}) - }); - - this.COMP_PLUS_SECT = new r.Struct({ - ident: IDENT, - size: r.uint32le, - total: r.uint32le, - count: r.uint32le, - items: new r.Array(this.COMP_PLUS_SECT_ITEM, 'count') - }); - - // 'bksp' - see 'tran' - - // 'disp' - this.COMP_PLUS_DISP_ITEM = new r.Struct({ - to: STR_REF, - id: STR_REF, - display: STR_REF, - }); - - this.COMP_PLUS_DISP = new r.Struct({ - ident: IDENT, - size: r.uint32le, - count: r.uint32le, - baseCharacter: CHAR32, - items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'), - }); - - // 'elem' - - this.COMP_PLUS_ELEM_ELEMENT = new r.Struct({ - element: STR_OR_CHAR32_OR_USET, - flags: r.uint32le - }); - - this.COMP_PLUS_ELEM_STRING = new r.Struct({ - offset: r.uint32le, - length: r.uint32le - }); - - this.COMP_PLUS_ELEM = new r.Struct({ - ident: IDENT, - size: r.uint32le, - count: r.uint32le, - strings: new r.Array(this.COMP_PLUS_ELEM_STRING, 'count') - // + variable subtable: Element data (see KMXPlusBuilder.emitElements()) - }); - - // 'finl' - see 'tran' - - // 'keys' - see 'keys.kmap' - - // 'layr' - - this.COMP_PLUS_LAYR_ENTRY = new r.Struct({ - id: r.uint32le, // str - mod: r.uint32le, // bitfield - row: r.uint32le, // index into rows - count: r.uint32le, - }); - - this.COMP_PLUS_LAYR_KEY = new r.Struct({ - key: r.uint32le, // str: key id - }); - - this.COMP_PLUS_LAYR_LIST = new r.Struct({ - hardware: STR_REF, // str: hardware name - layer: r.uint32le, // index into layers - count: r.uint32le, - minDeviceWidth: r.uint32le, // integer: millimeters - }); - - this.COMP_PLUS_LAYR_ROW = new r.Struct({ - key: r.uint32le, - count: r.uint32le, - }); - - this.COMP_PLUS_LAYR = new r.Struct({ - ident: IDENT, - size: r.uint32le, - listCount: r.uint32le, - layerCount: r.uint32le, - rowCount: r.uint32le, - keyCount: r.uint32le, - lists: new r.Array(this.COMP_PLUS_LAYR_LIST, 'listCount'), - layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'), - rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'), - keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'), - }); - - this.COMP_PLUS_KEYS_FLICK = new r.Struct({ - directions: LIST_REF, // list - to: STR_OR_CHAR32, // str | codepoint - }); - - this.COMP_PLUS_KEYS_FLICKS = new r.Struct({ - count: r.uint32le, - flick: r.uint32le, - id: STR_REF, // str - }); - - this.COMP_PLUS_KEYS_KEY = new r.Struct({ - to: STR_OR_CHAR32, // str | codepoint - flags: r.uint32le, - id: STR_REF, // str - switch: STR_REF, // str - width: r.uint32le, // width*10 ( 1 = 0.1 keys) - longPress: LIST_REF, // list index - longPressDefault: STR_REF, // str - multiTap: LIST_REF, // list index - flicks: r.uint32le, // index into flicks table - }); - - this.COMP_PLUS_KEYS_KMAP = new r.Struct({ - vkey: r.uint32le, - mod: r.uint32le, - key: r.uint32le, // index into 'keys' subtable - }); - - this.COMP_PLUS_KEYS = new r.Struct({ - ident: IDENT, - size: r.uint32le, - keyCount: r.uint32le, - flicksCount: r.uint32le, - flickCount: r.uint32le, - kmapCount: r.uint32le, - keys: new r.Array(this.COMP_PLUS_KEYS_KEY, 'keyCount'), - flicks: new r.Array(this.COMP_PLUS_KEYS_FLICKS, 'flicksCount'), - flick: new r.Array(this.COMP_PLUS_KEYS_FLICK, 'flickCount'), - kmap: new r.Array(this.COMP_PLUS_KEYS_KMAP, 'kmapCount'), - }); - - // 'list' - - this.COMP_PLUS_LIST_LIST = new r.Struct({ - index: r.uint32le, - count: r.uint32le, - }); - - this.COMP_PLUS_LIST_INDEX = new r.Struct({ - str: STR_REF, // str - }); - - this.COMP_PLUS_LIST = new r.Struct({ - ident: IDENT, - size: r.uint32le, - listCount: r.uint32le, - indexCount: r.uint32le, - lists: new r.Array(this.COMP_PLUS_LIST_LIST, 'listCount'), - indices: new r.Array(this.COMP_PLUS_LIST_INDEX, 'indexCount'), - }); - - // 'loca' - - this.COMP_PLUS_LOCA_ITEM = r.uint32le; //str - - this.COMP_PLUS_LOCA = new r.Struct({ - ident: IDENT, - size: r.uint32le, - count: r.uint32le, - items: new r.Array(this.COMP_PLUS_LOCA_ITEM, 'count') - }); - - // 'meta' - - this.COMP_PLUS_META = new r.Struct({ - ident: IDENT, - size: r.uint32le, - author: STR_REF, //str - conform: STR_REF, //str - layout: STR_REF, //str - name: STR_REF, //str - indicator: STR_REF, //str - version: STR_REF, //str - settings: r.uint32le, //new r.Bitfield(r.uint32le, ['normalizationDisabled']) - }); - - // 'name' is gone - - // 'ordr' now part of 'tran' - - // 'strs' - - this.COMP_PLUS_STRS_ITEM = new r.Struct({ - // While we use length which is number of utf-16 code units excluding null terminator, - // we always write a null terminator, so we can get restructure to do that for us here - offset: r.uint32le, //? new r.Pointer(r.uint32le, new r.String(null, 'utf16le')), - length: r.uint32le - }); - - this.COMP_PLUS_STRS = new r.Struct({ - ident: IDENT, - size: r.uint32le, - count: r.uint32le, - items: new r.Array(this.COMP_PLUS_STRS_ITEM, 'count') - // + variable subtable: String data (see KMXPlusBuilder.emitStrings()) - }); - - // 'tran' - - this.COMP_PLUS_TRAN_GROUP = new r.Struct({ - type: r.uint32le, //type of group - count: r.uint32le, //number of items - index: r.uint32le, //index into subtable - }); - - this.COMP_PLUS_TRAN_TRANSFORM = new r.Struct({ - from: STR_REF, //str - to: STR_REF, //str - mapFrom: ELEM_REF, //elem - mapTo: ELEM_REF //elem - }); - - this.COMP_PLUS_TRAN_REORDER = new r.Struct({ - elements: ELEM_REF, //elem - before: ELEM_REF, //elem - }); - - this.COMP_PLUS_TRAN = new r.Struct({ - ident: IDENT, - size: r.uint32le, - groupCount: r.uint32le, - transformCount: r.uint32le, - reorderCount: r.uint32le, - groups: new r.Array(this.COMP_PLUS_TRAN_GROUP, 'groupCount'), - transforms: new r.Array(this.COMP_PLUS_TRAN_TRANSFORM, 'transformCount'), - reorders: new r.Array(this.COMP_PLUS_TRAN_REORDER, 'reorderCount'), - }); - - // 'uset' - this.COMP_PLUS_USET_USET = new r.Struct({ - range: r.uint32le, - count: r.uint32le, - pattern: STR_REF, // str - }); - - this.COMP_PLUS_USET_RANGE = new r.Struct({ - start: CHAR32, - end: CHAR32, - }); - - this.COMP_PLUS_USET = new r.Struct({ - ident: IDENT, - size: r.uint32le, - usetCount: r.uint32le, - rangeCount: r.uint32le, - usets: new r.Array(this.COMP_PLUS_USET_USET, 'usetCount'), - ranges: new r.Array(this.COMP_PLUS_USET_RANGE, 'rangeCount'), - }); - - // 'vars' - - this.COMP_PLUS_VARS_ITEM = new r.Struct({ - type: r.uint32le, - id: STR_REF, // str - value: STR_REF, // str - elem: ELEM_REF, - }); - - this.COMP_PLUS_VARS = new r.Struct({ - ident: IDENT, - size: r.uint32le, - markers: LIST_REF, - varCount: r.uint32le, - varEntries: new r.Array(this.COMP_PLUS_VARS_ITEM, 'varCount'), - }); - - // 'vkey' is removed - - // Aliases - - this.COMP_PLUS_BKSP = this.COMP_PLUS_TRAN; - } -} +}; From db5aa518c01359296c1ee773d00a5e2bc0cf63ba Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 16 Oct 2025 12:37:44 +0200 Subject: [PATCH 08/37] change(developer): add unit test to verify special key cap definitions Special key caps such as '*ZWNJ*' are defined in a number of places in the source. In theory, we could DRY these definitions out, but that would add a number of unhelpful dependencies or take considerable time to implement. So, for now I opted to write a unit test to compare the definitions as found in the following files, treating web's definition as primary: * web/src/engine/osk/src/specialCharacters.ts * developer/src/tike/xml/layoutbuilder/constants.js * developer/src/kmc-kmn/kmw-compiler/constants.ts * developer/docs/help/reference/file-types/keyman-touch-layout.md * core/include/ldml/keyman_core_ldml.ts Note that the keyman_core_ldml.ts file changes are not included in this commit, and hence the unit test for it is currently skipped. This will be enabled in a subsequent commit along with other KMX+ changes to support epic/embed-osk-in-kmx. Test-bot: skip --- ...ard-layout-for-amharic-the-nitty-gritty.md | 337 +----------------- .../file-types/keyman-touch-layout.md | 13 +- developer/src/kmc-kmn/.gitignore | 3 + developer/src/kmc-kmn/build.sh | 11 + .../src/kmc-kmn/src/kmw-compiler/constants.ts | 11 +- .../src/kmc-kmn/test/kmw/constants.tests.ts | 152 ++++++++ developer/src/kmc-kmn/test/tsconfig.json | 2 + .../src/tike/xml/layoutbuilder/constants.js | 9 +- web/src/engine/osk/src/specialCharacters.ts | 9 +- 9 files changed, 206 insertions(+), 341 deletions(-) create mode 100644 developer/src/kmc-kmn/test/kmw/constants.tests.ts diff --git a/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md index f33ea20c343..9d5c20e5263 100644 --- a/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md +++ b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md @@ -131,7 +131,7 @@ Character Keys"). Typically, you would use only the "common" virtual key - `U_####[_####]` is used as a shortcut for a key that will output those Unicode values, if no rule matches it. This is similar to the overloaded - behaviour for `K_` ids. Thus `####` must be valid Unicode characters. + behaviour for `K_` ids. Thus `####` must be valid Unicode characters. The square bracket characters `[` and `]` indicate an optional portion of the sequence and are not to be included in the shortcut. E.g. `U_0259` would generate a schwa if no rule matches. It is still valid to @@ -226,338 +226,9 @@ A number of special text labels are recognized as identifying special purpose keys, such as Shift, Backspace, Enter, etc., for which icons are more appropriately used than a text label. A special font including these icons is included with Keyman and automatically embedded and used in any web page using -Keyman. The list of icons in the font will probably be extended in future, but -for now the following special labels are recognized: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Text StringKey CapKey Purpose
`*Shift*`Select Shift layer (inactive). Use on the Shift key to indicate that it switches to the shift layer.
`*Shifted*`Select Shift layer (active). Use on the Shift key on the shift layer to switch back to the default layer.
`*ShiftLock*`Switch to Caps layer (inactive). Not commonly used; generally double-tap on Shift key is used to access the - caps layer.
`*ShiftedLock*`Switch to Caps layer (active). Use on the Shift key on the caps layer to switch back to the default layer. -
`*Enter*` or Return or Enter key (shape determined by writing system direction)
`*LTREnter*`Return or Enter key (left-to-right script shape)
`*RTLEnter*`Return or Enter key (right-to-left script shape)
`*BkSp*` or Backspace key (shape determined by writing system direction)
`*LTRBkSp*`Backspace key (left-to-right script shape)
`*RTLBkSp*`Backspace key (right-to-left script shape)
`*Menu*`Globe key; display the language menu. Use on the `K_LOPT` key.
`*Hide*`Hide the on screen keyboard. Use on the `K_ROPT` key.
`*ABC*`Select alphabetic layer (Uppercase)
`*abc*`Select alphabetic layer (Lowercase)
`*123*`Select the numeric layer
`*Symbol*`Select the symbol layer
`*Currency*`Select the currency symbol layer
`*ZWNJ*` (iOS) or  (Android)Zero Width Non Joiner (shape determined by current platform)
`*ZWNJiOS*`Zero Width Non Joiner (iOS style shape)
`*ZWNJAndroid*`Zero Width Non Joiner (Android style shape)
`*ZWNJGeneric*`Zero Width Non Joiner (not platform-specific)
`*Sp*`Regular space
`*NBSp*`No-Break Space
`*NarNBSp*`Narrow No-Break Space
`*EnQ*`En Quad
`*EmQ*`Em Quad
`*EnSp*`En Space
`*EmSp*`Em Space
`*PunctSp*`Punctuation Space
`*ThSp*`Thin Space
`*HSp*`Hair Space
`*ZWSp*`Zero Width Space
`*ZWJ*`Zero Width Joiner
`*WJ*`Word Joiner
`*CGJ*`Combining Grapheme Joiner
`*LTRM*`Left-to-right Mark
`*RTLM*`Right-to-left Mark
`*SH*`Soft Hyphen
`*HTab*`Horizontal Tabulation
- -The following additional symbols are also available, but intended for working -with legacy desktop layouts, and not recommended for general use: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Text StringKey CapKey Purpose
`*Tab*`Move to next input element in tab order
`*TabLeft*`Move to previous input element in tab order
`*Caps*`Select caps layer (legacy)
`*AltGr*`Select AltGr (Right-Alt) layer (desktop layout compatibility)
`*Alt*`Select Alt layer (desktop layout compatibility)
`*Ctrl*`Select Ctrl layer (desktop layout compatibility)
`*LAlt*`Select Left-Alt layer (desktop layout compatibility)
`*RAlt*`Select Right-Alt layer (desktop layout compatibility)
`*LCtrl*`Select Left-Ctrl layer (desktop layout compatibility)
`*RCtrl*`Select Right-Ctrl layer (desktop layout compatibility)
`*LAltCtrl*`Select Left-Alt-Ctrl layer (desktop layout compatibility)
`*RAltCtrl*`Select Right-Alt-Ctrl layer (desktop layout compatibility)
`*LAltCtrlShift*`Select Left-Alt-Ctrl-Shift layer (desktop layout compatibility)
`*RAltCtrlShift*`Select Right-Alt-Ctrl-Shift layer (desktop layout compatibility)
`*AltShift*`Select Alt-Shift layer (desktop layout compatibility)
`*CtrlShift*`Select Ctrl-Shift layer (desktop layout compatibility)
`*AltCtrlShift*`Select Alt-Ctrl-Shift layer (desktop layout compatibility)
`*LAltShift*`Select Left-Alt-Shift layer (desktop layout compatibility)
`*RAltShift*`Select Right-Alt-Shift layer (desktop layout compatibility)
`*LCtrlShift*`Select Left-Ctrl-Shift layer (desktop layout compatibility)
`*RCtrlShift*`Select Right-Ctrl-Shift layer (desktop layout compatibility)
+Keyman. The list of icons in the font may be extended in future. See +the list of special characters in the +[.keyman-touch-layout reference](../../reference/file-types/keyman-touch-layout). ### Key type diff --git a/developer/docs/help/reference/file-types/keyman-touch-layout.md b/developer/docs/help/reference/file-types/keyman-touch-layout.md index 9f9bbd6fc3f..e689c0aa41f 100644 --- a/developer/docs/help/reference/file-types/keyman-touch-layout.md +++ b/developer/docs/help/reference/file-types/keyman-touch-layout.md @@ -97,7 +97,7 @@ Character Keys"). Typically, you would use only the "common" virtual key - `U_####[_####]` is used as a shortcut for a key that will output those Unicode values, if no rule matches it. This is similar to the overloaded - behaviour for `K_` ids. Thus `####` must be valid Unicode characters. + behaviour for `K_` ids. Thus `####` must be valid Unicode characters. The square bracket characters `[` and `]` indicate an optional portion of the sequence and are not to be included in the shortcut. E.g. `U_0259` would generate a schwa if no rule matches. It is still valid to @@ -192,9 +192,10 @@ A number of special text labels are recognized as identifying special purpose keys, such as Shift, Backspace, Enter, etc., for which icons are more appropriately used than a text label. A special font including these icons is included with Keyman and automatically embedded and used in any web page using -Keyman. The list of icons in the font will probably be extended in future, but -for now the following special labels are recognized: +Keyman. The list of icons in the font may be extended in future. +The special labels are listed below: + @@ -293,8 +294,9 @@ for now the following special labels are recognized: - - + + @@ -524,6 +526,7 @@ with legacy desktop layouts, and not recommended for general use:
`*ZWNJ*` (iOS) or  (Android)Zero Width Non Joiner (shape determined by current platform)Zero Width Non Joiner (shape determined by current platform, + will be on Android)
`*ZWNJiOS*`
+ ### Key type diff --git a/developer/src/kmc-kmn/.gitignore b/developer/src/kmc-kmn/.gitignore index 6a608d487b8..76fb645213e 100644 --- a/developer/src/kmc-kmn/.gitignore +++ b/developer/src/kmc-kmn/.gitignore @@ -1,3 +1,6 @@ # WASM interfaces from kmcmplib are copied here so we can avoid having # to make tsc aware of debug vs release paths src/import/ + +# We copy some files from other modules for comparison purposes here +test/kmw/_imported_* \ No newline at end of file diff --git a/developer/src/kmc-kmn/build.sh b/developer/src/kmc-kmn/build.sh index 7d94f880dfa..d1cc6816639 100755 --- a/developer/src/kmc-kmn/build.sh +++ b/developer/src/kmc-kmn/build.sh @@ -65,6 +65,17 @@ function do_build() { function do_test() { copy_deps + + # We want to compare the key cap values from both KMW and Developer and ensure + # that all three are in sync. We'll copy the relevant source files and patch + # them in directly. Builder's constants.js is not an ES6 module, so we hackily + # patch that here. + echo 'export const builder = {specialCharacters:{}}' > ./test/kmw/_imported_constants.js + # shellcheck disable=SC2016 + echo 'function $(v) {v()}' >> ./test/kmw/_imported_constants.js + cat "${KEYMAN_ROOT}/developer/src/tike/xml/layoutbuilder/constants.js" >> ./test/kmw/_imported_constants.js + cp "${KEYMAN_ROOT}/web/src/engine/osk/src/specialCharacters.ts" ./test/kmw/_imported_specialCharacters.ts + typescript_run_eslint_mocha_tests 80 } diff --git a/developer/src/kmc-kmn/src/kmw-compiler/constants.ts b/developer/src/kmc-kmn/src/kmw-compiler/constants.ts index 1c41df145b8..a678dc87269 100644 --- a/developer/src/kmc-kmn/src/kmw-compiler/constants.ts +++ b/developer/src/kmc-kmn/src/kmw-compiler/constants.ts @@ -7,7 +7,16 @@ export enum TRequiredKey { export const CRequiredKeys: TRequiredKey[] = [TRequiredKey.K_LOPT, TRequiredKey.K_BKSP, TRequiredKey.K_ENTER]; // I4447 -// See also builder.js: specialCharacters; web/source/osk/oskKey.ts: specialCharacters +// Defines the various 'special' modifier/control/non-printing keys on keyboards. +// +// `CSpecialText*` must be kept in sync with: +// * /web/src/engine/osk/src/specialCharacters.ts +// * /developer/src/tike/xml/layoutBuilder/constants.js +// * /core/include/ldml/keyman_core_ldml.ts +// +// More information, and unit test, in /developer/src/kmc-kmn/tests/kmw/constants.tests.ts +// +// Note that this mapping here is version-mapped for verification purposes. export const CSpecialText10: string = '*Shift*\0*Enter*\0*Tab*\0*BkSp*\0*Menu*\0*Hide*\0*Alt*\0*Ctrl*\0*Caps*\0' + '*ABC*\0*abc*\0*123*\0*Symbol*\0*Currency*\0*Shifted*\0*AltGr*\0*TabLeft*\0', diff --git a/developer/src/kmc-kmn/test/kmw/constants.tests.ts b/developer/src/kmc-kmn/test/kmw/constants.tests.ts new file mode 100644 index 00000000000..d9acdb7d356 --- /dev/null +++ b/developer/src/kmc-kmn/test/kmw/constants.tests.ts @@ -0,0 +1,152 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-10-16 + * + * Key cap special values (such as "*Shift*") are defined in multiple modules; + * this data is not currently in a common module, as it would create unhelpful + * dependencies or require significant refactoring. So, instead, for now we just + * verify that the values line up. Note that the _imported_ files are copied in + * by build.sh before running the tests in order to avoid pathing issues. + * + * This is testing that the values in these files are equivalent: + * developer/src/tike/xml/layoutbuilder/constants.js + * web/src/engine/osk/src/specialCharacters.ts + * developer/src/kmc-kmn/kmw-compiler/constants.ts + * core/include/ldml/keyman_core_ldml.ts + * developer/docs/help/reference/file-types/keyman-touch-layout.md + */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import 'mocha'; +import {assert} from 'chai'; + +import keymanWebSpecialCharacters from "./_imported_specialCharacters.js"; +import { CSpecialText17, CSpecialText14, CSpecialText10, CSpecialText17ZWNJ } from "../../src/kmw-compiler/constants.js"; +import { builder } from "./_imported_constants.js"; +import { constants as coreConstants } from "@keymanapp/ldml-keyboard-constants"; + +/** Verify key cap constants across 4 modules: KMW treated as primary */ +describe('Key cap special text values from KeymanWeb', function() { + + it('should match key cap special text in Developer Touch Layout Builder', function() { + // The key cap special text objects in these two files should be exactly equal: + // developer/src/tike/xml/layoutbuilder/constants.js + // web/src/engine/osk/src/specialCharacters.ts + assert.deepEqual(builder.specialCharacters, keymanWebSpecialCharacters); + }); + + it('should match key cap special text in Developer kmc-kmn KMW compiler', function() { + // These two files should have the same strings for key caps: + // developer/src/kmc-kmn/kmw-compiler/constants.ts + // web/src/engine/osk/src/specialCharacters.ts + + // No values to compare here - just key names + + // TODO: the following key cap strings are not verified in the compiler, + // why? It also appears that the compiler does not verify when an + // unrecognized key cap string is used + const specialCharactersPatch = Object.keys(keymanWebSpecialCharacters).filter(e => + e !== "*LAlt*" && + e !== "*RAlt*" && + e !== "*LCtrl*" && + e !== "*RCtrl*" && + e !== "*LAltCtrl*" && + e !== "*RAltCtrl*" && + e !== "*LAltCtrlShift*" && + e !== "*RAltCtrlShift*" && + e !== "*AltShift*" && + e !== "*CtrlShift*" && + e !== "*AltCtrlShift*" && + e !== "*LAltShift*" && + e !== "*RAltShift*" && + e !== "*LCtrlShift*" && + e !== "*RCtrlShift*" + ).sort(); + + const compilerSpecialCharacters = [ + ...CSpecialText10.split('\0'), + ...CSpecialText14.split('\0'), + ...CSpecialText17.split('\0'), + CSpecialText17ZWNJ, + ].filter(e => e !== "") // remove blanks coming from the splitting + .sort(); + + assert.deepEqual(compilerSpecialCharacters, specialCharactersPatch); + }); + + // TODO-EMBED-OSK-IN-KMX: enable once we have complete the Core LDML headers + it.skip('should match key cap special text in Core constants', function() { + // These two files should have the same constant values for key caps: + // core/include/ldml/keyman_core_ldml.ts + // web/src/engine/osk/src/specialCharacters.ts + + // The Core constants and KeymanWeb constants vary a little: + // + // 1. We need to special case *ABC* and *abc* because they have identical + // values but we are case-insensitive in our LDML definitions + const specialCharactersPatch: any = {...keymanWebSpecialCharacters}; + specialCharactersPatch['*abc_lower*'] = specialCharactersPatch['*abc*']; + delete specialCharactersPatch['*abc*']; + + specialCharactersPatch['*ABC_upper*'] = specialCharactersPatch['*ABC*']; + delete specialCharactersPatch['*ABC*']; + + // 2. Map all the "*Name*" key names to "dis2_key_cap_name"... + const dis2_key_cap_expected: any = {}; + for(const key of Object.keys(specialCharactersPatch)) { + const newKey = key.replace(/^\*(.+)\*$/, 'dis2_key_cap_$1').toLowerCase(); + dis2_key_cap_expected[newKey] = specialCharactersPatch[key]; + } + + // 3. We only want to compare the dis2_key_cap_ values from the + // coreConstants object + const coreConstantsFiltered: any = {}; + for(const key of Object.keys(coreConstants)) { + if(key.match(/^dis2_key_cap_/)) { + coreConstantsFiltered[key] = (coreConstants)[key]; + } + } + + assert.deepEqual(coreConstantsFiltered, dis2_key_cap_expected); + }); + + it('should be documented correctly', function() { + // These two files should have the same constant values for key caps: + // developer/docs/help/reference/file-types/keyman-touch-layout.md + // web/src/engine/osk/src/specialCharacters.ts + const helpFile = + path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../../../docs/help/reference/file-types/keyman-touch-layout.md'); + const lines = fs.readFileSync(helpFile, 'utf-8').replaceAll(/\r\n/g, '\n').split('\n'); + + // Find the relevant section in the file between start:special_key_caps and + // end:special_key_caps + const line0 = lines.findIndex(line => line.includes('start:special_key_caps')); + assert.notEqual(line0, -1); + const line1 = lines.findIndex(line => line.includes('end:special_key_caps')); + assert.notEqual(line1, -1); + + const content = lines.slice(line0+1, line1); + + const markdownConstants: any = {}; + + // Iterate over cells in the relevant section of the file and build an + // array of values, assuming the format of the markdown doesn't change + let nextCap: string = null; + for(const line of content) { + const mCap = line.match(/`(\*.+\*)`<\/td>/i); + if(mCap) { + nextCap = mCap[1]; + continue; + } + const mVal = line.match(/(.+);<\/td>/); + if(mVal) { + markdownConstants[nextCap] = parseInt(mVal[1], 16); + } + } + + assert.deepEqual(markdownConstants, keymanWebSpecialCharacters); + }); +}); diff --git a/developer/src/kmc-kmn/test/tsconfig.json b/developer/src/kmc-kmn/test/tsconfig.json index dee653dfac4..ffdcf60ae75 100644 --- a/developer/src/kmc-kmn/test/tsconfig.json +++ b/developer/src/kmc-kmn/test/tsconfig.json @@ -10,6 +10,8 @@ }, "include": [ "**/*.tests.ts", + "./kmw/_imported_constants.js", + "./kmw/_imported_specialCharacters.ts", "./helpers/index.ts", "./kmw/util.ts" ], diff --git a/developer/src/tike/xml/layoutbuilder/constants.js b/developer/src/tike/xml/layoutbuilder/constants.js index 117020e5d51..7680725d402 100644 --- a/developer/src/tike/xml/layoutbuilder/constants.js +++ b/developer/src/tike/xml/layoutbuilder/constants.js @@ -552,7 +552,14 @@ $(function() { this.lookupKeyNames.sort(); // Defines the PUA code mapping for the various 'special' modifier/control/non-printing keys on keyboards. - // This is lifted directly from specialCharacters.ts and must be kept in sync. See also CompileKeymanWeb.pas: CSpecialText10 + // This is lifted directly from web/.../specialCharacters.ts and must be kept in sync. + // + // `specialCharacters` must be kept in sync with the same values in: + // * /web/src/engine/osk/src/specialCharacters.ts + // * /developer/src/kmc-kmn/src/kmw-compiler/constants.ts + // * /core/include/ldml/keyman_core_ldml.ts + // + // More information, and unit test, in /developer/src/kmc-kmn/tests/kmw/constants.tests.ts this.specialCharacters = { '*Shift*': 8, '*Enter*': 5, diff --git a/web/src/engine/osk/src/specialCharacters.ts b/web/src/engine/osk/src/specialCharacters.ts index c13e63715e5..4efac09233b 100644 --- a/web/src/engine/osk/src/specialCharacters.ts +++ b/web/src/engine/osk/src/specialCharacters.ts @@ -1,5 +1,12 @@ // Defines the PUA code mapping for the various 'special' modifier/control/non-printing keys on keyboards. -// `specialCharacters` must be kept in sync with the same variable in constants.js. See also CompileKeymanWeb.pas: CSpecialText10 +// +// `specialCharacters` must be kept in sync with the same values in: +// * /developer/src/tike/xml/layoutBuilder/constants.js +// * /developer/src/kmc-kmn/src/kmw-compiler/constants.ts +// * /core/include/ldml/keyman_core_ldml.ts +// +// More information, and unit test, in /developer/src/kmc-kmn/tests/kmw/constants.tests.ts + const specialCharacters = { '*Shift*': 8, '*Enter*': 5, From b4bb07ecc462ecc1b85d45e1a2dccae3301b7d29 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 17 Oct 2025 08:46:53 +0200 Subject: [PATCH 09/37] refactor(common): consolidate header struct to kmx-plus-file.ts Consolidate the header structure in KMX+ to make it easier to add a version value to the header for v19+ KMX+ files; also DRYs out the header definitions a little. --- .../types/src/kmx/kmx-plus/kmx-plus-file.ts | 46 +++++++++---------- .../types/kmx/kmx-plus-builder/build-disp.ts | 6 ++- .../types/kmx/kmx-plus-builder/build-elem.ts | 8 ++-- .../types/kmx/kmx-plus-builder/build-keys.ts | 8 ++-- .../types/kmx/kmx-plus-builder/build-layr.ts | 8 ++-- .../types/kmx/kmx-plus-builder/build-list.ts | 8 ++-- .../types/kmx/kmx-plus-builder/build-loca.ts | 6 ++- .../types/kmx/kmx-plus-builder/build-meta.ts | 6 ++- .../types/kmx/kmx-plus-builder/build-sect.ts | 6 ++- .../types/kmx/kmx-plus-builder/build-strs.ts | 8 ++-- .../types/kmx/kmx-plus-builder/build-tran.ts | 8 ++-- .../types/kmx/kmx-plus-builder/build-uset.ts | 10 ++-- .../types/kmx/kmx-plus-builder/build-vars.ts | 8 ++-- .../kmx/kmx-plus-builder/builder-section.ts | 6 ++- .../kmx/kmx-plus-builder/kmx-plus-builder.ts | 12 ++--- 15 files changed, 89 insertions(+), 65 deletions(-) diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts index 2644247323a..97e764f755d 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts @@ -61,6 +61,8 @@ export class KMXPlusFileFormat extends KMXFile { public readonly COMP_PLUS_VARS: any; public readonly COMP_PLUS_VARS_ITEM: any; + private readonly COMP_PLUS_SectionHeader: any; + constructor() { super(); // Binary-correct structures matching kmx_plus.h @@ -73,6 +75,14 @@ export class KMXPlusFileFormat extends KMXFile { const CHAR32 = r.uint32le; const STR_OR_CHAR32_OR_USET = r.uint32le; const IDENT = r.uint32le; + + // Section header - version dependent + + this.COMP_PLUS_SectionHeader = new r.Struct({ + ident: IDENT, + size: r.uint32le, + }); + // 'sect' this.COMP_PLUS_SECT_ITEM = new r.Struct({ @@ -81,8 +91,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_SECT = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, total: r.uint32le, count: r.uint32le, items: new r.Array(this.COMP_PLUS_SECT_ITEM, 'count') @@ -98,8 +107,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_DISP = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, count: r.uint32le, baseCharacter: CHAR32, items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'), @@ -118,8 +126,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_ELEM = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, count: r.uint32le, strings: new r.Array(this.COMP_PLUS_ELEM_STRING, 'count') // + variable subtable: Element data (see KMXPlusBuilder.emitElements()) @@ -155,8 +162,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_LAYR = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, listCount: r.uint32le, layerCount: r.uint32le, rowCount: r.uint32le, @@ -199,8 +205,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_KEYS = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, keyCount: r.uint32le, flicksCount: r.uint32le, flickCount: r.uint32le, @@ -223,8 +228,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_LIST = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, listCount: r.uint32le, indexCount: r.uint32le, lists: new r.Array(this.COMP_PLUS_LIST_LIST, 'listCount'), @@ -236,8 +240,7 @@ export class KMXPlusFileFormat extends KMXFile { this.COMP_PLUS_LOCA_ITEM = r.uint32le; //str this.COMP_PLUS_LOCA = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, count: r.uint32le, items: new r.Array(this.COMP_PLUS_LOCA_ITEM, 'count') }); @@ -245,8 +248,7 @@ export class KMXPlusFileFormat extends KMXFile { // 'meta' this.COMP_PLUS_META = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, author: STR_REF, //str conform: STR_REF, //str layout: STR_REF, //str @@ -270,8 +272,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_STRS = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, count: r.uint32le, items: new r.Array(this.COMP_PLUS_STRS_ITEM, 'count') // + variable subtable: String data (see KMXPlusBuilder.emitStrings()) @@ -298,8 +299,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_TRAN = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, groupCount: r.uint32le, transformCount: r.uint32le, reorderCount: r.uint32le, @@ -322,8 +322,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_USET = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, usetCount: r.uint32le, rangeCount: r.uint32le, usets: new r.Array(this.COMP_PLUS_USET_USET, 'usetCount'), @@ -340,8 +339,7 @@ export class KMXPlusFileFormat extends KMXFile { }); this.COMP_PLUS_VARS = new r.Struct({ - ident: IDENT, - size: r.uint32le, + header: this.COMP_PLUS_SectionHeader, markers: LIST_REF, varCount: r.uint32le, varEntries: new r.Array(this.COMP_PLUS_VARS_ITEM, 'varCount'), diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts index 6984aa2cfa1..86e4b33367c 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts @@ -30,8 +30,10 @@ export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD } const disp: BUILDER_DISP = { - ident: constants.hex_section_id(constants.section.disp), - size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length, + header: { + ident: constants.hex_section_id(constants.section.disp), + size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length, + }, _offset: 0, count: kmxplus.disp.disps.length, baseCharacter: build_strs_index(sect_strs, kmxplus.disp.baseCharacter), diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts index 8e352d191f5..b556cfcbbd9 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts @@ -48,8 +48,10 @@ function binaryElemCompare(a: BUILDER_ELEM_STRING, b: BUILDER_ELEM_STRING): numb export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset: BUILDER_USET): BUILDER_ELEM { const result: BUILDER_ELEM = { - ident: constants.hex_section_id(constants.section.elem), - size: 0, // finalized below + header: { + ident: constants.hex_section_id(constants.section.elem), + size: 0, // finalized below + }, _offset: 0, count: source_elem.strings.length, strings: [], // finalized below @@ -98,7 +100,7 @@ export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset } } - result.size = offset; + result.header.size = offset; return result; } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts index fd48ec49ec9..620a1397ece 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts @@ -76,8 +76,10 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l } const keys: BUILDER_KEYS = { - ident: constants.hex_section_id(constants.section.keys), - size: 0, + header: { + ident: constants.hex_section_id(constants.section.keys), + size: 0, + }, keyCount: kmxplus.keys.keys.length, flicksCount: kmxplus.keys.flicks.length, flickCount: 0, @@ -169,7 +171,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l (constants.length_keys_flick_element * keys.flickCount) + (constants.length_keys_flick_list * keys.flicksCount) + (constants.length_keys_kmap * keys.kmapCount); - keys.size = offset; + keys.header.size = offset; return keys; } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts index 07249368f4e..1efe3207688 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts @@ -72,8 +72,10 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l } const layr: BUILDER_LAYR = { - ident: constants.hex_section_id(constants.section.layr), - size: constants.length_layr, + header: { + ident: constants.hex_section_id(constants.section.layr), + size: constants.length_layr, + }, _offset: 0, listCount: kmxplus.layr.lists.length, layerCount: 0, // calculated below @@ -155,6 +157,6 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l (constants.length_layr_entry * layr.layerCount) + (constants.length_layr_row * layr.rowCount) + (constants.length_layr_key * layr.keyCount); - layr.size = offset; + layr.header.size = offset; return layr; } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts index 759f6498f4f..787b246b719 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts @@ -44,8 +44,10 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_ } const result: BUILDER_LIST = { - ident: constants.hex_section_id(constants.section.list), - size: 0, + header: { + ident: constants.hex_section_id(constants.section.list), + size: 0, + }, _offset: 0, listCount: source_list.lists.length, indexCount: 0, @@ -77,7 +79,7 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_ const offset = constants.length_list + (constants.length_list_item * result.listCount) + (constants.length_list_index * result.indexCount); - result.size = offset; + result.header.size = offset; return result; } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts index 4a81fcd9abd..7ddd944590c 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts @@ -20,8 +20,10 @@ export interface BUILDER_LOCA extends BUILDER_SECTION { export function build_loca(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_LOCA { const loca: BUILDER_LOCA = { - ident: constants.hex_section_id(constants.section.loca), - size: constants.length_loca + constants.length_loca_item * kmxplus.loca.locales.length, + header: { + ident: constants.hex_section_id(constants.section.loca), + size: constants.length_loca + constants.length_loca_item * kmxplus.loca.locales.length, + }, _offset: 0, count: kmxplus.loca.locales.length, items: [] diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts index 208051356eb..3f232fc23cc 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts @@ -25,8 +25,10 @@ export interface BUILDER_META extends BUILDER_SECTION { export function build_meta(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_META { return { - ident: constants.hex_section_id(constants.section.meta), - size: constants.length_meta, + header: { + ident: constants.hex_section_id(constants.section.meta), + size: constants.length_meta, + }, _offset: 0, author: build_strs_index(sect_strs, kmxplus.meta.author), conform: build_strs_index(sect_strs, kmxplus.meta.conform), diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts index b97555d6de0..df20071e7ee 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts @@ -21,8 +21,10 @@ export interface BUILDER_SECT extends BUILDER_SECTION { export function build_sect(): BUILDER_SECT { return { - ident: constants.hex_section_id(constants.section.sect), - size: 0, // finalized later + header: { + ident: constants.hex_section_id(constants.section.sect), + size: 0, // finalized later + }, _offset: 0, total: 0, // finalized later count: 0, // finalized later diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts index 89d733f22d3..0ad5b6b2ed4 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts @@ -30,8 +30,10 @@ export interface BUILDER_STRS extends BUILDER_SECTION { export function build_strs(source_strs: Strs): BUILDER_STRS { const result: BUILDER_STRS = { - ident: constants.hex_section_id(constants.section.strs), - size: 0, // finalized later + header: { + ident: constants.hex_section_id(constants.section.strs), + size: 0, // finalized later + }, _offset: 0, count: source_strs.strings.length, items: [], // filled below @@ -46,7 +48,7 @@ export function build_strs(source_strs: Strs): BUILDER_STRS { item.offset = offset; offset += item.length * 2 + 2; /* UTF-16 code units + sizeof null terminator */ } - result.size = offset; + result.header.size = offset; return result; } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts index 2473651b889..aa8bea55ae8 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts @@ -48,8 +48,10 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se } const tran: BUILDER_TRAN = { - ident: constants.hex_section_id(source_tran.id), - size: 0, // need to compute total transforms + reorders + header: { + ident: constants.hex_section_id(source_tran.id), + size: 0, // need to compute total transforms + reorders + }, _offset: 0, groupCount: source_tran.groups.length, transformCount: 0, @@ -94,7 +96,7 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se tran.transformCount = tran.transforms.length; tran.reorderCount = tran.reorders.length; - tran.size = constants.length_tran + + tran.header.size = constants.length_tran + (constants.length_tran_group * source_tran.groups.length) + (constants.length_tran_transform * tran.transforms.length) + (constants.length_tran_reorder * tran.reorders.length); diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts index 1edd4d339fc..2b2d17a904a 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts @@ -57,10 +57,12 @@ export function build_uset(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS ) : BUI }); const uset: BUILDER_USET = { - ident: constants.hex_section_id(constants.section.uset), - size: constants.length_uset + - (constants.length_uset_uset * usets.length) + - (constants.length_uset_range * ranges.length), + header: { + ident: constants.hex_section_id(constants.section.uset), + size: constants.length_uset + + (constants.length_uset_uset * usets.length) + + (constants.length_uset_range * ranges.length), + }, usetCount: usets.length, rangeCount: ranges.length, usets, diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts index 92acbba9be1..8ccc38694df 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts @@ -46,9 +46,11 @@ export function build_vars(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_e }); const vars: BUILDER_VARS = { - ident: constants.hex_section_id(constants.section.vars), - size: constants.length_vars + - (constants.length_vars_item * kmxplus.vars.totalCount()), + header: { + ident: constants.hex_section_id(constants.section.vars), + size: constants.length_vars + + (constants.length_vars_item * kmxplus.vars.totalCount()), + }, _offset: 0, markers: build_list_index(sect_list, kmxplus.vars.markers), varCount: kmxplus.vars.totalCount(), diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts index a6d81fd25e8..c4d8a761932 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts @@ -4,8 +4,10 @@ export type BUILDER_IDENT = number; export type BUILDER_U32CHAR = number; export interface BUILDER_SECTION { - ident: BUILDER_IDENT; - size: number; + header: { + ident: BUILDER_IDENT; + size: number; + }; _offset: number; // used only for building the output } ; diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts index acc732c932a..49889025b6f 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts @@ -115,9 +115,9 @@ export default class KMXPlusBuilder { } }); - this.sect.sect.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count; + this.sect.sect.header.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count; - let offset = this.sect.sect.size; + let offset = this.sect.sect.header.size; // Note: in order! Everyone's here except 'sect' which is at offset 0 offset = this.finalize_sect_item(this.sect.bksp, offset); offset = this.finalize_sect_item(this.sect.disp, offset); @@ -141,16 +141,16 @@ export default class KMXPlusBuilder { return offset; } sect._offset = offset; - this.sect.sect.items.push({sect: sect.ident, offset: offset}); - return offset + sect.size; + this.sect.sect.items.push({sect: sect.header.ident, offset: offset}); + return offset + sect.header.size; } private emitSection(file: Uint8Array, comp: any, sect: BUILDER_SECTION) { if(sect) { const buf = comp.toBuffer(sect); - if (buf.length > sect.size) { + if (buf.length > sect.header.size) { // buf.length may be < sect.size if there is a variable part (i.e. elem) - throw new RangeError(`Internal Error: Section ${constants.str_section_id(sect.ident)} claimed size ${sect.size} but produced buffer of size ${buf.length}.`); + throw new RangeError(`Internal Error: Section ${constants.str_section_id(sect.header.ident)} claimed size ${sect.header.size} but produced buffer of size ${buf.length}.`); } file.set(buf, sect._offset); } From 693c741884c78f05f9d455fd65770e5c264c6b8d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 17 Oct 2025 11:23:09 +0200 Subject: [PATCH 10/37] feat(common): add version header to KMX+ Typescript modules Add support for header.version to KMX+ files, with differentiation between v17 and v19 in the compiler, and associated tests. This does not yet make the target version accessible to authors using `kmc`, but the interfaces from `kmc-ldml` are available. A v17 KMX+ file will have: * comp_header.version = VERSION_17 * initial 'sect' section in KMX+ data * no sect.header.version field in any section A v19+ KMX+ file will have: * comp_header.version = VERSION_19 * initial 'sec2' section in KMX+ data * sect.header.version field for all sections * each section 4 bytes larger to accommodate * 'sec2' must have sect.header.version = KMXPlusVersion.Version19 * currently all other sections have sect.header.version = Version17 --- .../types/src/kmx/kmx-plus/kmx-plus-file.ts | 23 +- core/include/ldml/keyman_core_ldml.h | 3 + core/include/ldml/keyman_core_ldml.ts | 31 + developer/src/common/web/utils/package.json | 1 + .../web/utils/src/types/kmx/kmx-builder.ts | 8 +- .../types/kmx/kmx-plus-builder/build-elem.ts | 6 +- .../types/kmx/kmx-plus-builder/build-sect.ts | 8 +- .../types/kmx/kmx-plus-builder/build-strs.ts | 6 +- .../kmx/kmx-plus-builder/builder-section.ts | 3 + .../kmx/kmx-plus-builder/kmx-plus-builder.ts | 17 +- developer/src/common/web/utils/tsconfig.json | 4 + .../src/kmc-ldml/src/compiler/compiler.ts | 12 +- .../src/compiler/ldml-compiler-options.ts | 4 + .../src/kmc-ldml/test/compiler-e2e.tests.ts | 70 +- .../test/fixtures/{basic.txt => basic-17.txt} | 0 .../src/kmc-ldml/test/fixtures/basic-19.txt | 684 ++++++++++++++++++ 16 files changed, 829 insertions(+), 51 deletions(-) rename developer/src/kmc-ldml/test/fixtures/{basic.txt => basic-17.txt} (100%) create mode 100644 developer/src/kmc-ldml/test/fixtures/basic-19.txt diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts index 97e764f755d..a1da3a3cfbd 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts @@ -1,6 +1,7 @@ import * as KMX from '../kmx.js'; import * as r from 'restructure'; import KMXFile = KMX.KMXFile; +import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; export class KMXPlusFileFormat extends KMXFile { @@ -63,10 +64,14 @@ export class KMXPlusFileFormat extends KMXFile { private readonly COMP_PLUS_SectionHeader: any; - constructor() { + constructor(public version: KMXPlusVersion) { super(); // Binary-correct structures matching kmx_plus.h + if(![KMXPlusVersion.Version17, KMXPlusVersion.Version19].includes(version)) { + throw new Error(`Support for version ${version} not implemented`); + } + // helpers const STR_REF = r.uint32le; const ELEM_REF = r.uint32le; @@ -78,10 +83,18 @@ export class KMXPlusFileFormat extends KMXFile { // Section header - version dependent - this.COMP_PLUS_SectionHeader = new r.Struct({ - ident: IDENT, - size: r.uint32le, - }); + if(version == KMXPlusVersion.Version17) { + this.COMP_PLUS_SectionHeader = new r.Struct({ + ident: IDENT, + size: r.uint32le, + }); + } else { + this.COMP_PLUS_SectionHeader = new r.Struct({ + ident: IDENT, + size: r.uint32le, + version: r.uint32le, + }); + } // 'sect' diff --git a/core/include/ldml/keyman_core_ldml.h b/core/include/ldml/keyman_core_ldml.h index f521cb356fc..d1d148da2cb 100644 --- a/core/include/ldml/keyman_core_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -45,6 +45,8 @@ #define LDML_KEYS_MOD_NONE 0x0 #define LDML_KEYS_MOD_OTHER 0x10000 #define LDML_KEYS_MOD_SHIFT 0x10 +#define LDML_KMXPLUS_VERSION_17 0x1100 +#define LDML_KMXPLUS_VERSION_19 0x1300 #define LDML_LAYR_LIST_HARDWARE_TOUCH "touch" #define LDML_LAYR_MAX_MINDEVICEWIDTH 0x3E7 #define LDML_LAYR_MIN_MINDEVICEWIDTH 0x1 @@ -120,6 +122,7 @@ #define LDML_SECTIONNAME_USET "uset" #define LDML_SECTIONID_VARS 0x73726176 /* "vars" */ #define LDML_SECTIONNAME_VARS "vars" +#define LDML_SECTION_SEC2 "sec2" #define LDML_TRAN_FLAGS_ERROR 0x1 #define LDML_TRAN_GROUP_TYPE_REORDER 0x1 #define LDML_TRAN_GROUP_TYPE_TRANSFORM 0x0 diff --git a/core/include/ldml/keyman_core_ldml.ts b/core/include/ldml/keyman_core_ldml.ts index c975c94b71d..1711bb97006 100644 --- a/core/include/ldml/keyman_core_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -43,6 +43,11 @@ type SectionMap = { [id in SectionIdent]: SectionIdent; } +export enum KMXPlusVersion { + Version17 = 0x1100, // == KMXFile.VERSION_170, + Version19 = 0x1300, // == KMXFile.VERSION_190, +}; + // TODO-LDML: namespace com.keyman.core.ldml { /** * Constants for the KMXPlus data format @@ -81,6 +86,18 @@ class Constants { */ readonly length_header = 8; + /** + * Version number 17 for KMX+ file format, initial release version, + * corresponds to Keyman 17.0 + */ + readonly kmxplus_version_17: KMXPlusVersion = KMXPlusVersion.Version17; + + /** + * Version number 19 for KMX+ file format, new SEC2 section and version + * header, corresponds to Keyman 19.0 + */ + readonly kmxplus_version_19: KMXPlusVersion = KMXPlusVersion.Version19; + /* ------------------------------------------------------------------ * sect section ------------------------------------------------------------------ */ @@ -528,6 +545,9 @@ class Constants { vars: 'vars', }; + // v19+: special case for 'sect' override with 'sec2' + readonly section_sec2 = 'sec2'; + /** * Use to convert 4-char string into hex * @param id section id such as 'sect' @@ -580,6 +600,17 @@ class Constants { treatAsLatest(version: string): boolean { return cldrTreatAsLatest.has(version); } + + /** + * Difference in section header size from default v17 size + */ + headerSizeDelta(version: KMXPlusVersion): number { + if(version == KMXPlusVersion.Version17) { + return 0; + } + + return 4; /* KMXPlusVersion.Version19, additional version uint32le field */ + } }; /** There's no data or DTD change in 45, 46, 46.1, 47 so map them all to 46 at present. */ diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 089800c92f1..4a0fb4fbbc9 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -10,6 +10,7 @@ ], "dependencies": { "@keymanapp/common-types": "*", + "@keymanapp/ldml-keyboard-constants": "*", "eventemitter3": "^5.0.0", "fast-xml-parser": "^5.2.2", "path-browserify": "^1.0.1", diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts index a8ce169d303..c19fdb368d9 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts @@ -38,7 +38,7 @@ export default class KMXBuilder { return [base, base + string.length * 2 + 2]; // include trailing zero } - private build() { + private build(version: number) { this.base_keyboard = 0; this.base_kmxplus = 0; @@ -46,7 +46,7 @@ export default class KMXBuilder { this.comp_header = { dwIdentifier: KMXFile.FILEID_COMPILED, - dwFileVersion: KMXFile.VERSION_170, + dwFileVersion: version, dwCheckSum: 0, // Deprecated in Keyman 16.0 KeyboardID: 0, IsRegistered: 1, @@ -183,8 +183,8 @@ export default class KMXBuilder { } } - compile(): Uint8Array { - const fileSize = this.build(); + compile(version: number = KMXFile.VERSION_170): Uint8Array { + const fileSize = this.build(version); const file: Uint8Array = new Uint8Array(fileSize); diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts index b556cfcbbd9..0844db0f275 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts @@ -1,4 +1,4 @@ -import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { constants, KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus, LdmlKeyboardTypes } from "@keymanapp/common-types"; import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js"; import { BUILDER_SECTION, BUILDER_U32CHAR } from "./builder-section.js"; @@ -46,7 +46,7 @@ function binaryElemCompare(a: BUILDER_ELEM_STRING, b: BUILDER_ELEM_STRING): numb return 0; } -export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset: BUILDER_USET): BUILDER_ELEM { +export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset: BUILDER_USET, version: KMXPlusVersion): BUILDER_ELEM { const result: BUILDER_ELEM = { header: { ident: constants.hex_section_id(constants.section.elem), @@ -95,7 +95,7 @@ export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset // no length gets a zero offset item.offset = 0; } else { - item.offset = offset; + item.offset = offset + constants.headerSizeDelta(version); offset += item.length * constants.length_elem_item_element; } } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts index df20071e7ee..2ab11a10404 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts @@ -1,4 +1,4 @@ -import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { constants, KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { BUILDER_SECTION } from "./builder-section.js"; /* ------------------------------------------------------------------ @@ -19,11 +19,13 @@ export interface BUILDER_SECT extends BUILDER_SECTION { items: BUILDER_SECT_ITEM[]; }; -export function build_sect(): BUILDER_SECT { +export function build_sect(version: KMXPlusVersion): BUILDER_SECT { return { header: { - ident: constants.hex_section_id(constants.section.sect), + // v19+ uses ident "sec2" to indicate that we have a `version` header field + ident: constants.hex_section_id(version == KMXPlusVersion.Version17 ? constants.section.sect : constants.section_sec2), size: 0, // finalized later + version, }, _offset: 0, total: 0, // finalized later diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts index 0ad5b6b2ed4..822f22f168a 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts @@ -1,4 +1,4 @@ -import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { constants, KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus } from "@keymanapp/common-types"; import { BUILDER_SECTION } from "./builder-section.js"; @@ -28,7 +28,7 @@ export interface BUILDER_STRS extends BUILDER_SECTION { items: BUILDER_STRS_ITEM[]; }; -export function build_strs(source_strs: Strs): BUILDER_STRS { +export function build_strs(source_strs: Strs, version: KMXPlusVersion): BUILDER_STRS { const result: BUILDER_STRS = { header: { ident: constants.hex_section_id(constants.section.strs), @@ -45,7 +45,7 @@ export function build_strs(source_strs: Strs): BUILDER_STRS { let offset = constants.length_strs + constants.length_strs_item * result.count; // TODO: consider padding for(const item of result.items) { - item.offset = offset; + item.offset = offset + constants.headerSizeDelta(version); offset += item.length * 2 + 2; /* UTF-16 code units + sizeof null terminator */ } result.header.size = offset; diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts index c4d8a761932..272e17ecb3b 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts @@ -1,3 +1,5 @@ +import { KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; + /** for a 4-byte section identity */ export type BUILDER_IDENT = number; /** for a single UTF-32 character (Unicode codepoint) */ @@ -7,6 +9,7 @@ export interface BUILDER_SECTION { header: { ident: BUILDER_IDENT; size: number; + version?: KMXPlusVersion; // v19+ }; _offset: number; // used only for building the output } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts index 49889025b6f..f5b2b867b06 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts @@ -1,6 +1,6 @@ import * as r from 'restructure'; import { KMXPlus } from "@keymanapp/common-types"; -import { constants, SectionIdent } from '@keymanapp/ldml-keyboard-constants'; +import { constants, KMXPlusVersion, SectionIdent } from '@keymanapp/ldml-keyboard-constants'; import { BUILDER_SECTION } from './builder-section.js'; import { BUILDER_SECT, build_sect } from './build-sect.js'; import { BUILDER_DISP, build_disp } from './build-disp.js'; @@ -79,10 +79,10 @@ export default class KMXPlusBuilder { // We must prepare the strs, list, and elem sections early so that other sections can // reference them. However, they will be emitted in alpha order. - this.sect.strs = build_strs(this.file.kmxplus.strs); + this.sect.strs = build_strs(this.file.kmxplus.strs, this.file.version); this.sect.list = build_list(this.file.kmxplus.list, this.sect.strs); this.sect.uset = build_uset(this.file.kmxplus, this.sect.strs); - this.sect.elem = build_elem(this.file.kmxplus.elem, this.sect.strs, this.sect.uset); + this.sect.elem = build_elem(this.file.kmxplus.elem, this.sect.strs, this.sect.uset, this.file.version); const build_bksp = build_tran; @@ -98,7 +98,7 @@ export default class KMXPlusBuilder { // Finalize the sect (index) section - this.sect.sect = build_sect(); + this.sect.sect = build_sect(this.file.version); this.finalize_sect(); // must be done last return this.sect.sect.total; } @@ -115,7 +115,7 @@ export default class KMXPlusBuilder { } }); - this.sect.sect.header.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count; + this.sect.sect.header.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count + constants.headerSizeDelta(this.file.version); let offset = this.sect.sect.header.size; // Note: in order! Everyone's here except 'sect' which is at offset 0 @@ -140,6 +140,11 @@ export default class KMXPlusBuilder { // Don't include null sections return offset; } + // in order to avoid changing size calculation in every section builder, + // we adjust the size here before writing. We'd need to pass version info + // into each builder, otherwise, and make this call in each header, which + // we could choose to do in the future if it makes other things easier. + sect.header.size += constants.headerSizeDelta(this.file.version); sect._offset = offset; this.sect.sect.items.push({sect: sect.header.ident, offset: offset}); return offset + sect.header.size; @@ -147,6 +152,8 @@ export default class KMXPlusBuilder { private emitSection(file: Uint8Array, comp: any, sect: BUILDER_SECTION) { if(sect) { + sect.header.version = sect.header.version ?? KMXPlusVersion.Version17; + const buf = comp.toBuffer(sect); if (buf.length > sect.header.size) { // buf.length may be < sect.size if there is a variable part (i.e. elem) diff --git a/developer/src/common/web/utils/tsconfig.json b/developer/src/common/web/utils/tsconfig.json index 362ec3a9720..8eceb5d8f7e 100644 --- a/developer/src/common/web/utils/tsconfig.json +++ b/developer/src/common/web/utils/tsconfig.json @@ -13,4 +13,8 @@ "src/**/*.ts", "src/ttfmeta/lib/*.js", ], + "references": [ + { "path": "../../../../../common/web/types/" }, + { "path": "../../../../../core/include/ldml/tsconfig.json"}, + ] } diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index d66631e0b56..d5fc7f97bba 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -27,7 +27,7 @@ import { StrsCompiler, ElemCompiler, ListCompiler, UsetCompiler } from './empty- import LDMLKeyboardXMLSourceFile = LDMLKeyboard.LDMLKeyboardXMLSourceFile; import KMXPlusFile = KMXPlus.KMXPlusFile; import DependencySections = KMXPlus.DependencySections; -import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; +import { KMXPlusVersion, SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; import { KmnCompiler } from '@keymanapp/kmc-kmn'; import { KMXPlusMetadataCompiler } from './metadata-compiler.js'; import { LdmlKeyboardVisualKeyboardCompiler } from './visual-keyboard-compiler.js'; @@ -127,6 +127,8 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { */ async run(inputFilename: string, outputFilename?: string): Promise { + this.options.version = this.options.version ?? KMXPlusVersion.Version17; + const compilerOptions: LdmlCompilerOptions = { ...defaultCompilerOptions, ...this.options, @@ -165,7 +167,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { }); } - const kmxBinary = kmxBuilder.compile(); + const kmxBinary = kmxBuilder.compile(this.options.version); const kvkWriter = new KvkFileWriter(); const kvkBinary = vkData ? kvkWriter.write(vkData) : null; @@ -357,10 +359,14 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { * @returns KMXPlusFile intermediate file */ public async compile(source: LDMLKeyboardXMLSourceFile, postValidate?: boolean): Promise { + // TODO-EMBED-OSK-IN-KMX: add a unitTestEndpoints prop and make this private + // unit tests may not have set a version number; so do it again here + this.options.version = this.options.version ?? KMXPlusVersion.Version17; + const sections = this.buildSections(source); let passed = true; - const kmx = new KMXPlusFile(); + const kmx = new KMXPlusFile(this.options.version); for (const section of sections) { if (!section.validate()) { diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts index 1b4f72113c1..f2b920389cb 100644 --- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts +++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts @@ -1,4 +1,7 @@ import { CompilerOptions, LDMLKeyboardXMLSourceFileReaderOptions } from "@keymanapp/developer-utils"; +import { KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; + +export type LdmlKeyboardVersion = KMXPlusVersion; /** * @public @@ -9,4 +12,5 @@ export interface LdmlCompilerOptions extends CompilerOptions { * Paths and other options required for reading .xml files */ readerOptions: LDMLKeyboardXMLSourceFileReaderOptions; + version?: LdmlKeyboardVersion; // used in v19+ }; diff --git a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts index 7c05d39d227..70f0a30a926 100644 --- a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts +++ b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts @@ -1,3 +1,4 @@ +import * as fs from 'node:fs'; import 'mocha'; import {assert} from 'chai'; import hextobin from '@keymanapp/hextobin'; @@ -8,40 +9,59 @@ import { kmxToXml } from '../src/util/serialize.js'; import { writeFileSync } from 'node:fs'; import { LdmlCompilerMessages } from '../src/main.js'; import { util } from '@keymanapp/common-types'; +import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; + +const debug=false; /** Overall compiler tests */ describe('compiler-tests', function() { this.slow(500); // 0.5 sec -- json schema validation takes a while - before(function() { + beforeEach(function() { compilerTestCallbacks.clear(); }); - it('should-build-fixtures', async function() { - this.timeout(4000); - // Let's build basic.xml - // It should match basic.kmx (built from basic.txt) - - const inputFilename = makePathToFixture('basic.xml'); - const binaryFilename = makePathToFixture('basic.txt'); - - // Compare output - const expected = await hextobin(binaryFilename, undefined, {silent:true}); - - // now compare it to use with run() - // Let's build basic.xml - // It should match basic.kmx (built from basic.txt) - const k = new LdmlKeyboardCompiler(); - await k.init(compilerTestCallbacks, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false }); - - const { artifacts } = await k.run(inputFilename, "basic-xml.kmx"); // need the exact name passed to build-fixtures - assert.isNotNull(artifacts); - const { kmx, kvk } = artifacts; - assert.isNotNull(kmx); - assert.deepEqual(kmx?.data, expected); + afterEach(function() { + if (this.currentTest.state !== 'passed') { + compilerTestCallbacks.printMessages(); + } + }); - // TODO-LDML: compare the .kvk file to something else? - assert.isNotNull(kvk?.data); + [ + [17, KMXPlusVersion.Version17], + [19, KMXPlusVersion.Version19], + ].forEach(([vernum, version]) => { + it(`should-build-fixtures for ${vernum}`, async function() { + this.timeout(4000); + // Let's build basic.xml + // It should match basic.kmx (built from basic.txt) + + const inputFilename = makePathToFixture('basic.xml'); + const binaryFilename = makePathToFixture(`basic-${vernum}.txt`); + + // Compare output + const expected = await hextobin(binaryFilename, undefined, {silent:true}); + + // now compare it to use with run() + // Let's build basic.xml + // It should match basic-vv.kmx (built from basic.txt) + const k = new LdmlKeyboardCompiler(); + await k.init(compilerTestCallbacks, { ...compilerTestOptions, version, saveDebug: true, shouldAddCompilerVersion: false }); + + const { artifacts } = await k.run(inputFilename, "basic-xml.kmx"); // need the exact name passed to build-fixtures + assert.isNotNull(artifacts); + const { kmx, kvk } = artifacts; + assert.isNotNull(kmx); + assert.isNotNull(kmx.data); + if(debug) { + fs.writeFileSync(makePathToFixture(`basic-${vernum}-actual.kmx`), kmx.data); + fs.writeFileSync(makePathToFixture(`basic-${vernum}-expected.kmx`), expected); + } + assert.deepEqual(kmx.data, expected); + + // TODO-LDML: compare the .kvk file to something else? + assert.isNotNull(kvk?.data); + }); }); it('should-validate-on-run compiling sections/strs/invalid-illegal.xml', async function() { diff --git a/developer/src/kmc-ldml/test/fixtures/basic.txt b/developer/src/kmc-ldml/test/fixtures/basic-17.txt similarity index 100% rename from developer/src/kmc-ldml/test/fixtures/basic.txt rename to developer/src/kmc-ldml/test/fixtures/basic-17.txt diff --git a/developer/src/kmc-ldml/test/fixtures/basic-19.txt b/developer/src/kmc-ldml/test/fixtures/basic-19.txt new file mode 100644 index 00000000000..8ffb67f25ff --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/basic-19.txt @@ -0,0 +1,684 @@ +# +# Keyman is copyright (C) SIL International. MIT License. +# +# basic-v19.txt describes the expected output of running kmc against basic.xml +# with v19 schema. It is used in the end-to-end test test-compiler-e2e.ts. +# +# Any changes to the compiler or basic.xml will likely result in changes to the compiled file. +# While structural differences should be updated manually in this file to ensure that we are +# getting the expected result for the e2e test, the checksum can be safely retrieved from the +# updated compilation result. The following may be helpful for working with this file when +# updating the binary format: +# +# cd developer/src/kmc +# ./build.sh configure build # if needed +# ./build.sh build-fixtures +# +# This will compile both the .xml and the .txt to build/test/fixtures and also emit the +# checksum for basic-xml.kmx so you can patch that into this file. +# +# For the format of this file, see “hextobin.ts” +# +# P.S. surprisingly, the Dart language highlighter in VSCode does a helpful job on this file. + +block(kmxheader) # struct COMP_KEYBOARD { + 4b 58 54 53 # KMX_DWORD dwIdentifier; // 0000 Keyman compiled keyboard id + + 00 13 00 00 # KMX_DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 + + 00 00 00 00 # KMX_DWORD dwCheckSum; // 0008 deprecated in 16.0, always 0 + 00 00 00 00 # KMX_DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + 01 00 00 00 # KMX_DWORD IsRegistered; // 0010 + 00 00 00 00 # KMX_DWORD version; // 0014 keyboard version + + sizeof(stores,12) # KMX_DWORD cxStoreArray; // 0018 in array entries + 00 00 00 00 # KMX_DWORD cxGroupArray; // 001C in array entries + + offset(stores) # KMX_DWORD dpStoreArray; // 0020 [LPSTORE] address of first item in store array + 00 00 00 00 # KMX_DWORD dpGroupArray; // 0024 [LPGROUP] address of first item in group array + + ff ff ff ff # KMX_DWORD StartGroup[2]; // 0028 index of starting groups [2 of them] + ff ff ff ff # + + 20 00 00 00 # KMX_DWORD dwFlags; // 0030 Flags for the keyboard file + + 00 00 00 00 # KMX_DWORD dwHotKey; // 0034 standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + + 00 00 00 00 # KMX_DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file + 00 00 00 00 # KMX_DWORD dwBitmapSize; // 003C size in bytes of the bitmaps + # }; + +block(kmxplusinfo) # struct COMP_KEYBOARD_KMXPLUSINFO { + offset(sec2) # KMX_DWORD dpKMXPlus; // 0040 offset of KMXPlus data from BOF, header is first + diff(sec2,eof) # KMX_DWORD dwKMXPlusSize; // 0044 size in bytes of entire KMXPlus data + # }; + +block(stores) # struct COMP_STORE { + 07 00 00 00 # KMX_DWORD dwSystemID; - TSS_NAME + offset(store_name_name) # KMX_DWORD dpName; + offset(store_name_string) # KMX_DWORD dpString; + # }; + # excluding this so we don’t have compiled version changes + # 14 00 00 00 # KMX_DWORD dwSystemID; - TSS_COMPILEDVERSION + # offset(store_compiledversion_name) # KMX_DWORD dpName; + # offset(store_compiledversion_string) # KMX_DWORD dpString; + # }; + 24 00 00 00 # KMX_DWORD dwSystemID; - TSS_KEYBOARDVERSION + offset(store_keyboardversion_name) # KMX_DWORD dpName; + offset(store_keyboardversion_string) # KMX_DWORD dpString; + # }; + 26 00 00 00 # KMX_DWORD dwSystemID; - TSS_TARGETS + offset(store_targets_name) # KMX_DWORD dpName; + offset(store_targets_string) # KMX_DWORD dpString; + # }; + + 18 00 00 00 # TSS_VISUALKEYBOARD + 00 00 00 00 # KMX_DWORD dpName; + offset(store_vk_path_string) # KMX_DWORD dpString; + +block(store_name_name) + 26 00 4e 00 41 00 4d 00 45 00 00 00 # '&NAME' +block(store_name_string) + 54 00 65 00 73 00 74 00 4b 00 62 00 64 00 00 00 # 'TestKbd' + +block(store_keyboardversion_name) + 26 00 4b 00 45 00 59 00 42 00 4f 00 41 00 52 00 + 44 00 56 00 45 00 52 00 53 00 49 00 4f 00 4e 00 + 00 00 # '&KEYBOARDVERSION' +block(store_keyboardversion_string) + 31 00 2e 00 30 00 2e 00 30 00 00 00 # '1.0.0' + +block(store_targets_name) + 26 00 54 00 41 00 52 00 47 00 45 00 54 00 53 00 00 00 # '&TARGETS' +block(store_targets_string) + 64 00 65 00 73 00 6b 00 74 00 6f 00 70 00 00 00 # 'desktop' +block(store_vk_path_string) + 62 00 61 00 73 00 69 00 63 00 2d 00 78 00 6d 00 6c 00 2e 00 6b 00 76 00 6b 00 00 00 # 'basic-xml.kvk' + +block(sec2) # struct COMP_KMXPLUS_SEC2 { + 73 65 63 32 # KMX_DWORD header.ident; // 0000 Section name + diff(sec2,endsec2) # KMX_DWORD header.size; // 0004 Section length + 00 13 00 00 # KMX_DWORD header.version; // 0008 Section version, 19 = 0x13 + diff(sec2,eof) # KMX_DWORD total; // 000C KMXPlus entire length + sizeof(sectitems,8) # KMX_DWORD count; // 0010 number of section headers + # }; + # Next sections are sec2 entries + # KMX_DWORD sec2; // 0010+ Section identity + # KMX_DWORD offset; // 0014+ Section offset relative to dpKMXPlus of section + +block(sectitems) + 62 6b 73 70 + diff(sec2,bksp) + + 64 69 73 70 + diff(sec2,disp) + + 65 6c 65 6d + diff(sec2,elem) + + 6b 65 79 73 + diff(sec2,keys) + + 6c 61 79 72 + diff(sec2,layr) + + 6c 69 73 74 + diff(sec2,list) + + 6c 6f 63 61 + diff(sec2,loca) + + 6d 65 74 61 + diff(sec2,meta) + + 73 74 72 73 + diff(sec2,strs) + + 74 72 61 6e + diff(sec2,tran) + + 75 73 65 74 + diff(sec2,uset) + + 76 61 72 73 + diff(sec2,vars) + +block(endsec2) + +# ---------------------------------------------------------------------------------------------------- +# bksp +# ---------------------------------------------------------------------------------------------------- + +block(bksp) + # TODO-LDML: see 'tran' for a more programmatic way to calculate the fields + 62 6b 73 70 + sizeof(bksp) + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 01 00 00 00 # KMX_DWORD groupCount + 01 00 00 00 # KMX_DWORD transformCount + 00 00 00 00 # KMX_DWORD reorderCount + + # group 0 + 00 00 00 00 # KMX_DWORD type = transform + 01 00 00 00 # KMX_DWORD count = 1 + 00 00 00 00 # KMX_DWORD index = 0 + + # transforms 0 + index(strNull,strElemTranFrom1b,2) # KMX_DWORD str from ^e ## << ?? + index(strNull,strNull,2) # KMX_DWORD str to 0 + index(strNull,strNull,2) # KMX_DWORD str mapFrom 0 + index(strNull,strNull,2) # KMX_DWORD str mapTo 0 + + +# ---------------------------------------------------------------------------------------------------- +# disp +# ---------------------------------------------------------------------------------------------------- + +block(disp) # struct COMP_KMXPLUS_DISP { + 64 69 73 70 # KMX_DWORD header.ident; // 0000 Section name - disp + sizeof(disp) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 02 00 00 00 # KMX_DWORD count; // 000C number of entries + index(strNull,strElemBkspFrom2,2) # KMX_DWORD baseCharacter // 0010 baseCharacter = 'e' + # }; + + # entry 0 + index(strNull,strA,2) # KMX_DWORD to // 000C baseCharacter = 'a' + index(strNull,strNull,2) # KMX_DWORD id 0 + index(strNull,strElemTranFrom1,2) # KMX_DWORD display // 000C baseCharacter = '^' + + # entry 1 + index(strNull,strNull,2) # KMX_DWORD to // 000C baseCharacter = 'a' + index(strNull,strElemBkspFrom2,2) # KMX_DWORD id 'e' + index(strNull,strElemTranFrom1b,2) # KMX_DWORD display '^e' + + + +# ---------------------------------------------------------------------------------------------------- +# elem +# ---------------------------------------------------------------------------------------------------- + +block(elem) # struct COMP_KMXPLUS_ELEM { + 65 6c 65 6d # KMX_DWORD header.ident; // 0000 Section name - elem + diff(elem,endelem) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + index(elemNull,endelem) # KMX_DWORD count; // 000C number of entries + # }; + +#strings + #elem #0 null element + 00 00 00 00 # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemNull,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + #elem #1 count=03 + #elem #2 count=03 + #elem #3 count=01 + + + diff(elem,elemSet) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemSet,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemOrdrFrom) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemOrdrFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemOrdrBefore) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemOrdrBefore,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + +# now the elements +block(elemNull) + +block(elemSet) + 61 00 00 00 # KMX_DWORD element; 'a' + 00 00 00 00 # KMX_DWORD flags; + 62 00 00 00 # KMX_DWORD element; 'b' + 00 00 00 00 # KMX_DWORD flags; + 63 00 00 00 # KMX_DWORD element; 'c' + 00 00 00 00 # KMX_DWORD flags; + +block(elemOrdrFrom) + # from="\u{1A60}[\u1A75-\u1A79]\u{1A45}" order="10 55 10" + 60 1a 00 00 # KMX_DWORD element; '᩠' // str: output string or UTF-32LE codepoint + 00 00 0A 00 # KMX_DWORD flags; // flag and order values - cp + 00 00 00 00 # TODO-LDML: uset #0 # KMX_DWORD element; [uset] // str: output string or UTF-32LE codepoint + 02 00 37 00 # KMX_DWORD flags; // flag and order values - unicodeset + 45 1A 00 00 # KMX_DWORD element; 'ᩅ' // str: output string or UTF-32LE codepoint + 00 00 0A 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(elemOrdrBefore) + # before="\u{1A6B}" + 6b 1a 00 00 # KMX_DWORD element; 'ᩫ' // str: output string or UTF-32LE codepoint + 00 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(endelem) + +# ---------------------------------------------------------------------------------------------------- +# keys +# ---------------------------------------------------------------------------------------------------- + +block(keys) # struct COMP_KMXPLUS_KEYS { + 6b 65 79 73 # KMX_DWORD header.ident; // 0000 Section name - keys + sizeof(keys) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 05 00 00 00 # KMX_DWORD keyCount + 01 00 00 00 # KMX_DWORD flicksCount + 00 00 00 00 # KMX_DWORD flickCount + 30 00 00 00 # KMX_DWORD kmapCount; // 0008 number of kmap - #48, one per key + # keys + # (#0000) a + 61 00 00 00 # 'a' + 00 00 00 00 # KMX_DWORD flags = 0 + index(strNull,strA,2) # KMXPLUS_STR 'a' (key id) + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST longPress + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks + # (#0001) e + 65 00 00 00 # 'e' + 00 00 00 00 # KMX_DWORD flags = 0 + index(strNull,strElemBkspFrom2,2) # KMXPLUS_STR 'e' (key id) + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST longPress + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks + # (#0002) gap (reserved) + 00 00 00 00 # nothing + 03 00 00 00 # KMX_DWORD flags = extend|gap + index(strNull,strGapReserved,2) # KMXPLUS_STR 'gap (reserved)' (key id) + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 (full width) + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST longPress + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks + # (#0003) hmaqtugha + 27 01 00 00 # UTF-32 'U+0127' + 00 00 00 00 # KMX_DWORD (flags: none) + index(strNull,strHmaqtugha,2) # KMXPLUS_STR 'hmaqtugha' + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 + 02 00 00 00 # TODO: index(listNull,indexAe,4) # LIST longPress 'a e' + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks 0 + # (#0004) that + index(strNull,strKeys,2) # KMXPLUS_STR '...' + 01 00 00 00 # KMX_DWORD flags = extend + index(strNull,strThat,2) # KMXPLUS_STR 'that' + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST longPress + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks 0 + # flicks + # flicks 0 - null + 00 00 00 00 # KMX_DWORD count + 00 00 00 00 # KMX_DWORD flick + 00 00 00 00 # KMX_STR id + # flick + # Right now there aren’t any flick elements. + #00 00 00 00 # LIST directions + #00 00 00 01 # flags + #00 00 00 00 # str: to + + # kmapdata: + # note that 'a' and 'e' are omitted, as they aren't on the layers (just from gestures) + + # moving these to 1-liners so they can be sorted! the structure is as follows: + ## 31 00 00 00 # KMX_DWORD vkey + ## 00 00 00 00 # KMX_DWORD modifiers (none) + ## 04 00 00 00 # KMX_DWORD key index (that) + +# following lines generated with this snippet: +# +# /** from scanCodes-implied.xml */ +# const scans = ("29 02 03 04 05 06 07 08 09 0A 0B 0C 0D" + " 10 11 12 13 14 15 16 17 18 19 1A 1B 2B" + " 1E 1F 20 21 22 23 24 25 26 27 28" + " 2C 2D 2E 2F 30 31 32 33 34 35").split(/ /); +# +# for (let s of scans) { +# const n = Number.parseInt(s, 16); +# const v = CLDRScanToUSVirtualKeyCodes[n]; // virtual-key-constants.ts +# if (!v) { +# console.error(`! ${s}`); +# } else { +# console.log(` ${Number(v).toString(16)} 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved)`); +# } +# } + +# - vkey - - modifier - - key id - + 20 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 30 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 31 00 00 00 00 00 00 00 04 00 00 00 # "that" + 32 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 33 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 34 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 35 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 36 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 37 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 38 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 39 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 41 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 42 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 43 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 44 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 45 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 46 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 47 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 48 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 49 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 4a 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 4b 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 4c 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 4d 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 4e 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 4f 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 50 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 51 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 52 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 53 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 54 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 55 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 56 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 57 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 58 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 59 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + 5a 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + ba 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + bb 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + bc 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + bd 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + be 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + bf 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + c0 00 00 00 00 00 00 00 03 00 00 00 # "hmaqtugha" + db 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + dc 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + dd 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + de 00 00 00 00 00 00 00 02 00 00 00 # gap (reserved) + +# ---------------------------------------------------------------------------------------------------- +# layr +# ---------------------------------------------------------------------------------------------------- + +block(layr) # struct COMP_KMXPLUS_LAYR { + 6c 61 79 72 # KMX_DWORD header.ident; // 0000 Section name - layr + sizeof(layr) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 01 00 00 00 # KMX_DWORD listCount + 01 00 00 00 # KMX_DWORD layerCount + 01 00 00 00 # KMX_DWORD rowCount + 02 00 00 00 # KMX_DWORD keyCount + # list 0 + index(strNull,strUs,2) # KMXPLUS_STR hardware = 'us' + 00 00 00 00 # KMX_DWORD layer; + 01 00 00 00 # count + 7B 00 00 00 # KMX_DWORD minDeviceWidth; // 123 + # layers 0 + 00 00 00 00 # KMXPLUS_STR id; + 00 00 00 00 # KMX_DWORD mod + 00 00 00 00 # KMX_DWORD row index + 01 00 00 00 # KMX_DWORD count + # rows 0 + 00 00 00 00 # KMX_DWORD key index + 02 00 00 00 # KMX_DWORD count + # keys + index(strNull,strHmaqtugha,2) # KMXPLUS_STR locale; // 'hmaqtugha' + index(strNull,strThat,2) # KMXPLUS_STR locale; // 'that' + +# ---------------------------------------------------------------------------------------------------- +# list +# ---------------------------------------------------------------------------------------------------- + +# TODO-LDML: lots of comment-out ahead. Need to revisit. + +block(list) # struct COMP_KMXPLUS_LAYR_LIST { + 6c 69 73 74 # KMX_DWORD header.ident; // 0000 Section name - list + diff(list,endList) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 03 00 00 00 # KMX_DWORD listCount (should be 2) + 03 00 00 00 # KMX_DWORD indexCount (should be 2) + # list #0 the null list + block(listNull) + 00 00 00 00 #index(indexNull,indexNull,2) # KMX_DWORD list index (0) + 00 00 00 00 # KMX_DWORD lists[0].count + block(listA) + 00 00 00 00 # first index + 01 00 00 00 #count + block(listAe) + 01 00 00 00 # index(indexAe,indexNull,2) # KMX_DWORD list index (also 0) + 02 00 00 00 # KMX_DWORD count + block(endLists) + # indices + #block(indexNull) + # No null index + # index(strNull,strNull,2) # KMXPLUS_STR string index + block(indexA) + index(strNull,strA,2) # a + block(indexAe) + index(strNull,strA,2) # KMXPLUS_STR a + index(strNull,strElemBkspFrom2,2) # KMXPLUS_STR e + block(endIndices) + block(endList) + +# ---------------------------------------------------------------------------------------------------- +# loca +# ---------------------------------------------------------------------------------------------------- + +block(loca) # struct COMP_KMXPLUS_LOCA { + 6c 6f 63 61 # KMX_DWORD header.ident; // 0000 Section name - loca + sizeof(loca) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 01 00 00 00 # KMX_DWORD count; // 000C number of locales + index(strNull,strLocale,2) # KMXPLUS_STR locale; // 0010+ locale string entry = 'mt' + # }; +# ---------------------------------------------------------------------------------------------------- +# meta +# ---------------------------------------------------------------------------------------------------- + +block(meta) # struct COMP_KMXPLUS_META { + 6d 65 74 61 # KMX_DWORD header.ident; // 0000 Section name - meta + sizeof(meta) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + index(strNull,strAuthor,2) # KMXPLUS_STR author; + index(strNull,strConformsTo,2) # KMXPLUS_STR conform; + index(strNull,strLayout,2) # KMXPLUS_STR layout; + index(strNull,strName,2) # KMXPLUS_STR name; + index(strNull,strIndicator,2) # KMXPLUS_STR indicator; + index(strNull,strVersion,2) # KMXPLUS_STR version; + 00 00 00 00 # KMX_DWORD settings; + # }; + +# ---------------------------------------------------------------------------------------------------- +# strs +# ---------------------------------------------------------------------------------------------------- + +block(strs) # struct COMP_KMXPLUS_STRS { + 73 74 72 73 # KMX_DWORD header.ident; // 0000 Section name - strs + diff(strs,endstrs) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + index(strNull,endstrs,2) # KMX_DWORD count; // 000C count of str entries + # }; + + # Next sections are string entries + # KMX_DWORD offset; // 0010+ offset from this blob + # KMX_DWORD length; // 0014+ str length (UTF-16LE units) + + diff(strs,strNull) sizeof(strNull,2) + diff(strs,strVersion) sizeof(strVersion,2) + diff(strs,strConformsTo) sizeof(strConformsTo,2) + diff(strs,strName) sizeof(strName,2) + diff(strs,strFromSet) sizeof(strFromSet,2) + diff(strs,strUSet) sizeof(strUSet,2) + diff(strs,strAmarker) sizeof(strAmarker,2) + diff(strs,strSentinel0001r) sizeof(strSentinel0001r,2) + diff(strs,strElemTranFrom1) sizeof(strElemTranFrom1,2) + diff(strs,strElemTranFrom1a) sizeof(strElemTranFrom1a,2) + diff(strs,strElemTranFrom1b) sizeof(strElemTranFrom1b,2) + diff(strs,strA) sizeof(strA,2) + diff(strs,strSet) sizeof(strSet,2) + diff(strs,strSet2) sizeof(strSet2,2) + diff(strs,strTranTo) sizeof(strTranTo,2) + diff(strs,strElemBkspFrom2) sizeof(strElemBkspFrom2,2) + diff(strs,strGapReserved) sizeof(strGapReserved,2) + diff(strs,strHmaqtugha) sizeof(strHmaqtugha,2) + diff(strs,strLocale) sizeof(strLocale,2) + diff(strs,strLayout) sizeof(strLayout,2) + diff(strs,strAuthor) sizeof(strAuthor,2) + diff(strs,strThat) sizeof(strThat,2) + diff(strs,strUs) sizeof(strUs,2) + diff(strs,strVse) sizeof(strVse,2) + diff(strs,strVst) sizeof(strVst,2) + diff(strs,strVus) sizeof(strVus,2) + diff(strs,strKeys) sizeof(strKeys,2) + diff(strs,strIndicator) sizeof(strIndicator,2) + diff(strs,strSentinel0001) sizeof(strSentinel0001,2) + + # String table -- block(x) is used to store the null u16char at end of each string + # without interfering with sizeof() calculation above + +#str #00 + block(strNull) block(x) 00 00 # the zero-length string + block(strVersion) 31 00 2e 00 30 00 2e 00 30 00 block(x) 00 00 # '1.0.0' + block(strConformsTo) 34 00 35 00 block(x) 00 00 # '45' + block(strName) 54 00 65 00 73 00 74 00 4b 00 62 00 64 00 block(x) 00 00 # 'TestKbd' + block(strFromSet) 5B 00 5C 00 75 00 31 00 41 00 37 00 35 00 2D 00 5C 00 75 00 31 00 41 00 37 00 39 00 5D 00 block(x) 00 00 # [\u1a75-\u1a79] + block(strUSet) 5b 00 61 00 62 00 63 00 5d 00 block(x) 00 00 # '[abc]' + block(strAmarker) 5C 00 6D 00 7B 00 61 00 7D 00 block(x) 00 00 # '\m{a}' + block(strSentinel0001r) 5c 00 75 00 66 00 66 00 66 00 66 00 5c 00 75 00 30 00 30 00 30 00 38 00 5C 00 75 00 30 00 30 00 30 00 31 00 block(x) 00 00 # UC_SENTINEL CODE_DEADKEY \u0001 (regex form) + block(strElemTranFrom1) 5E 00 block(x) 00 00 # '^' + block(strElemTranFrom1a) 5E 00 61 00 block(x) 00 00 # '^a' + block(strElemTranFrom1b) 5E 00 65 00 block(x) 00 00 # '^e' + block(strA) 61 00 block(x) 00 00 # 'a' + block(strSet) 61 00 20 00 62 00 20 00 63 00 block(x) 00 00 # 'a b c' +#str #0A + block(strSet2) 61 00 62 00 63 00 block(x) 00 00 # 'abc' + block(strTranTo) 61 00 02 03 block(x) 00 00 # 'â' (U+0061 U+0302) + block(strElemBkspFrom2) 65 00 block(x) 00 00 # 'e' + block(strGapReserved) 67 00 61 00 70 00 20 00 28 00 72 00 65 00 73 00 65 00 72 00 76 00 65 00 64 00 29 00 block(x) 00 00 # 'gap (reserved)' + block(strHmaqtugha) 68 00 6d 00 61 00 71 00 74 00 75 00 67 00 68 00 61 00 block(x) 00 00 # 'hmaqtugha' +#str #10 + block(strLocale) 6d 00 74 00 block(x) 00 00 # 'mt' + block(strLayout) 71 00 77 00 65 00 72 00 74 00 79 00 block(x) 00 00 # 'qwerty' + block(strAuthor) 73 00 72 00 6c 00 32 00 39 00 35 00 block(x) 00 00 # 'srl295' + block(strThat) 74 00 68 00 61 00 74 00 block(x) 00 00 # 'that' + block(strUs) 75 00 73 00 block(x) 00 00 # 'us' (layout) + block(strVse) 76 00 73 00 65 00 block(x) 00 00 # 'vse' + block(strVst) 76 00 73 00 74 00 block(x) 00 00 # 'vst' + block(strVus) 76 00 75 00 73 00 block(x) 00 00 # 'vus' +#str #1a + block(strKeys) 90 17 b6 17 block(x) 00 00 # 'ថា' + # + block(strIndicator) 3d d8 40 de block(x) 00 00 # '🙀' + block(strSentinel0001) FF FF 08 00 01 00 block(x) 00 00 # UC_SENTINEL CODE_DEADKEY U+0001 + + +block(endstrs) # end of strs block + +# ---------------------------------------------------------------------------------------------------- +# tran +# ---------------------------------------------------------------------------------------------------- + +block(tran) # struct COMP_KMXPLUS_TRAN { + 74 72 61 6e # KMX_DWORD header.ident; // 0000 Section name - tran + diff(tran,tranEnd) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + diff(tranGroupStart,tranTransformStart,12) # KMX_DWORD groupCount; + diff(tranTransformStart,tranReorderStart,16) # KMX_DWORD transformCount; + diff(tranReorderStart,tranEnd,8) # KMX_DWORD reorderCount; + + block(tranGroupStart) # COMP_KMXPLUS_TRAN_GROUP + # group 0 + 00 00 00 00 # KMX_DWORD type = transform + 02 00 00 00 # KMX_DWORD count + diff(tranTransformStart,tranTransform0,16) # KMX_DWORD index + + # group 1 + 00 00 00 00 # KMX_DWORD type = transform + 01 00 00 00 # KMX_DWORD count + diff(tranTransformStart,tranTransform2,16) # KMX_DWORD index + + # group 2 + 01 00 00 00 # KMX_DWORD type = reorder + 01 00 00 00 # KMX_DWORD count + diff(tranReorderStart,tranReorder0,8) # KMX_DWORD index + + # transforms + block(tranTransformStart) # COMP_KMXPLUS_TRAN_TRANSFORM + block(tranTransform0) + index(strNull,strElemTranFrom1a,2) # KMXPLUS_STR from; + index(strNull,strTranTo,2) # KMXPLUS_STR to; + index(strNull,strNull,2) # mapFrom + index(strNull,strNull,2) # mapTo + + block(tranTransform1) + index(strNull,strA,2) # KMXPLUS_STR from; 'a' + index(strNull,strSentinel0001,2) # KMXPLUS_STR to; \m{a} (plain form) + index(strNull,strNull,2) # mapFrom + index(strNull,strNull,2) # mapTo + + block(tranTransform2) # Next group + index(strNull,strSentinel0001r,2) # KMXPLUS_STR from; (\m{a}) (regex form) + index(strNull,strNull,2) # KMXPLUS_STR to; (none) + index(strNull,strNull,2) # mapFrom + index(strNull,strNull,2) # mapTo + + # reorders + block(tranReorderStart) # COMP_KMXPLUS_TRAN_REORDER + block(tranReorder0) + index(elemNull,elemOrdrFrom) # KMXPLUS_ELEM elements; + index(elemNull,elemOrdrBefore) # KMXPLUS_ELEM before; + +block(tranEnd) + +block(uset) + 75 73 65 74 # KMX_DWORD header.ident; // 0000 Section name - uset + diff(uset,usetEnd) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 01 00 00 00 # lists + 01 00 00 00 # elements + # lists + 00 00 00 00 # first list + 01 00 00 00 # size: 1 + index(strNull,strFromSet,2) # str + # ranges + # range @0 + 75 1A 00 00 # start + 79 1A 00 00 # end + block(usetEnd) + +block(vars) # struct COMP_KMXPLUS_VARS { + 76 61 72 73 # KMX_DWORD header.ident; // 0000 Section name - vars + diff(vars,varsEnd) # KMX_DWORD header.size; // 0004 Section length + 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 01 00 00 00 # KMX_DWORD markers - list 1 ['a'] + diff(varsBegin,varsEnd,16) # KMX_DWORD varCount + + block(varsBegin) + # var 0 + 00 00 00 00 # KMX_DWORD type = str + index(strNull,strA,2) # KMXPLUS_STR id 'a' + index(strNull,strAmarker,2) # KMXPLUS_STR value '\m{a}' + 00 00 00 00 # KMXPLUS_ELEM + + # var 1 + 01 00 00 00 # KMX_DWORD type = set + index(strNull,strVse,2) # KMXPLUS_STR id 'vse' + index(strNull,strSet,2) # KMXPLUS_STR value 'a b c' + 01 00 00 00 # KMXPLUS_ELEM elem 'a b c' see 'elemSet' + + # var 2 + 00 00 00 00 # KMX_DWORD type = string + index(strNull,strVst,2) # KMXPLUS_STR id 'vst' + index(strNull,strSet2,2) # KMXPLUS_STR value 'abc' + 00 00 00 00 # KMXPLUS_ELEM elem + + # var 3 + 02 00 00 00 # KMX_DWORD type = string + index(strNull,strVus,2) # KMXPLUS_STR id 'vus' + index(strNull,strUSet,2) # KMXPLUS_STR value '[abc]' + 00 00 00 00 # KMXPLUS_ELEM elem +block(varsEnd) + +block(eof) # end of file From 57e0e6941db9382f7f0a07fa21cf57c7b0c2c4f3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 20 Oct 2025 17:39:25 +0200 Subject: [PATCH 11/37] refactor(core): add support for header.version to Core LDML This substantial refactor reorganizes the header data for sections in the Core LDML processor. The change was substantial because of assumptions made about the binary layout of sections. In order to make the code easier to maintain, safer, and more resilient to future changes, I opted to make a consistent helper for each section, and copy header data so that it could be transparently reused. The principal changes are: 1. Support the version field in the COMP_KMXPLUS_HEADER struct, and split it into COMP_KMXPLUS_HEADER_17 and COMP_KMXPLUS_HEADER_19 versions. 2. Establish a corresponding COMP_KMXPLUS_XXXX_Helper clas for each section. 3. Refactor the majority of rawData accesses into using helper functions, which reduces direct pointer manipulation and adds extra boundary checks. A special-case exists for BKSP - it is identical to TRAN, except for its section ident. In order to avoid a complicated pattern for handling it, I have special-cased it in one place, adding an overloaded `get_section_from_sect` function for COMP_KMXPLUS_BKSP. An opportunity exists to refactor a bit further - reduce direct access to the binary data (through the COMP_KMXPLUS_XXXX structs, and instead promote use of the COMP_KMXPLUS_XXXX_Helper classes). This indirection would reduce duplication of data access and make it cleaner when we start doing multi-version code. Next commit will add unit tests for v19 format files. --- core/include/ldml/keyman_core_ldml.h | 6 +- core/include/ldml/keyman_core_ldml.ts | 9 +- core/src/kmx/kmx_plus.cpp | 820 ++++++++++++------ core/src/kmx/kmx_plus.h | 272 ++++-- core/src/ldml/ldml_processor.cpp | 2 +- core/src/ldml/ldml_transforms.cpp | 14 +- core/tests/unit/ldml/kmx_plus.tests.cpp | 50 +- core/tests/unit/ldml/ldml_test_source.cpp | 4 +- .../types/kmx/kmx-plus-builder/build-sect.ts | 2 +- 9 files changed, 781 insertions(+), 398 deletions(-) diff --git a/core/include/ldml/keyman_core_ldml.h b/core/include/ldml/keyman_core_ldml.h index d1d148da2cb..c080d56b53b 100644 --- a/core/include/ldml/keyman_core_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -59,7 +59,8 @@ #define LDML_LENGTH_ELEM_ITEM_ELEMENT 0x8 #define LDML_LENGTH_FINL 0x8 #define LDML_LENGTH_FINL_ITEM 0x10 -#define LDML_LENGTH_HEADER 0x8 +#define LDML_LENGTH_HEADER_17 0x8 +#define LDML_LENGTH_HEADER_19 0xC #define LDML_LENGTH_KEYS 0x18 #define LDML_LENGTH_KEYS_FLICK_ELEMENT 0x8 #define LDML_LENGTH_KEYS_FLICK_LIST 0xC @@ -122,7 +123,8 @@ #define LDML_SECTIONNAME_USET "uset" #define LDML_SECTIONID_VARS 0x73726176 /* "vars" */ #define LDML_SECTIONNAME_VARS "vars" -#define LDML_SECTION_SEC2 "sec2" +#define LDML_SECTIONID_SEC2 0x32636573 +#define LDML_SECTIONNAME_SEC2 "sec2" #define LDML_TRAN_FLAGS_ERROR 0x1 #define LDML_TRAN_GROUP_TYPE_REORDER 0x1 #define LDML_TRAN_GROUP_TYPE_TRANSFORM 0x0 diff --git a/core/include/ldml/keyman_core_ldml.ts b/core/include/ldml/keyman_core_ldml.ts index 1711bb97006..b6c6fe76930 100644 --- a/core/include/ldml/keyman_core_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -84,7 +84,11 @@ class Constants { /** * Length of a raw section header, in bytes */ - readonly length_header = 8; + readonly length_header_17 = 8; + /** + * Length of a raw section header, in bytes + */ + readonly length_header_19 = 12; /** * Version number 17 for KMX+ file format, initial release version, @@ -546,7 +550,8 @@ class Constants { }; // v19+: special case for 'sect' override with 'sec2' - readonly section_sec2 = 'sec2'; + readonly sectionname_sec2 = 'sec2'; + readonly sectionid_sec2 = 0x32636573; /** * Use to convert 4-char string into hex diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp index f9aafb9d663..234e91c1459 100644 --- a/core/src/kmx/kmx_plus.cpp +++ b/core/src/kmx/kmx_plus.cpp @@ -92,70 +92,129 @@ validate_section_name(KMX_DWORD ident) { } /** - * @brief cast to a COMP_KMXPLUS_HEADER from bytes + * @brief fill a COMP_KMXPLUS_HEADER from bytes * * @param data * @param length * @param ident - * @return const kmx::COMP_KMXPLUS_HEADER* + * @param out + * @return true on succcess */ -static const kmx::COMP_KMXPLUS_HEADER * -header_from_bytes(const uint8_t *data, KMX_DWORD length, uint32_t ident) { +bool +header_from_bytes(const uint8_t *data, KMX_DWORD length, KMX_DWORD fileVersion, uint32_t ident, kmx::COMP_KMXPLUS_HEADER &out) { if (!data) { DebugLog("!data"); assert(false); - return nullptr; + return false; } - if (length < LDML_LENGTH_HEADER) { - DebugLog("length < LDML_LENGTH_HEADER"); - assert(false); - return nullptr; + if(fileVersion == LDML_KMXPLUS_VERSION_17) { + const COMP_KMXPLUS_HEADER_17 *all = reinterpret_cast(data); + if (!all->valid(length)) { + DebugLog("header failed validation"); + assert(false); + return false; + } + + if (all->ident != ident) { + DebugLog("header had wrong section id"); + assert(false); + return false; + } + + out.set(LDML_KMXPLUS_VERSION_17, all->ident, all->size); + } else { + const COMP_KMXPLUS_HEADER_19 *all = reinterpret_cast(data); + if (!all->valid(length)) { + DebugLog("header failed validation"); + assert(false); + return false; + } + + if(ident == LDML_SECTIONID_SECT) { + if(all->ident != LDML_SECTIONID_SEC2) { + DebugLog("header had SECT but expected SEC2"); + assert(false); + return false; + } + } else if (all->ident != ident) { + DebugLog("header had wrong section id"); + assert(false); + return false; + } + + out.set(LDML_KMXPLUS_VERSION_19, all->ident, all->size, all->version); } - const COMP_KMXPLUS_HEADER *all = reinterpret_cast(data); - if (!all->valid(length)) { - DebugLog("header failed validation"); + + return true; +} + +/** + * @brief Determines the overall version of the file based on the first four bytes: + * 'sect' = v17 file + * 'sec2' = v19+ file + * other = invalid file + * A v19+ file will include an additional version field for each section. + * @param data + * @param length + * @return KMX_DWORD a LDML_KMXPLUS_VERSION_ value, or 0 on error + */ +KMX_DWORD determine_file_version_from_bytes(const uint8_t* data, KMX_DWORD length) { + if(length < sizeof(COMP_KMXPLUS_HEADER_17)) { + DebugLog("data is too short"); assert(false); - return nullptr; + return 0; } - if (all->ident != ident) { - DebugLog("header had wrong section id"); - assert(false); - return nullptr; + COMP_KMXPLUS_HEADER_17 const* header = reinterpret_cast(data); + if(header->ident == COMP_KMXPLUS_SECT::IDENT) { + return LDML_KMXPLUS_VERSION_17; } - return all; + if(header->ident == COMP_KMXPLUS_SECT::IDENT_V19) { + return LDML_KMXPLUS_VERSION_19; + } + DebugLog("initial section is unrecognized"); + assert(false); + return 0; } - /** * @brief Accessor for a section based on bytes * * @tparam T * @param data input pointer to the section to be validated. If nullptr, 'sect' will be nullptr, but will return true (valid). - * @param length length of input data. This function will not read past the end of this length. + * @param fileLength length of input data. This function will not read past the end of this length. * @param out on exit, will be set to the new section. On any failure, null data, or invalidity, will be nullptr. * @return true if section is missing (nullptr) or valid * @return const T* */ -template bool section_from_bytes(const uint8_t* data, KMX_DWORD length, const T*& out) { +template bool section_from_bytes(const uint8_t* data, KMX_DWORD fileLength, KMX_DWORD fileVersion, COMP_KMXPLUS_HEADER& header, const T*& out) { out = nullptr; // null unless valid. if (data == nullptr) { // missing section - not an error. DebugLog("data was null (missing section)"); return true; - } else if (length < sizeof(T)) { // Does not include dynamic data. First check. - DebugLog("length < sizeof(section)"); + } else if (fileLength < sizeof(T)) { // Does not include dynamic data. First check. + DebugLog("fileLength < sizeof(section)"); assert(false); return false; } - // verify the kmx+ header - const COMP_KMXPLUS_HEADER *header = header_from_bytes(data, length, T::IDENT); + + // verify the kmx+ section header + if(!header_from_bytes(data, fileLength, fileVersion, T::IDENT, header)) { + // asserted and logged in header_from_bytes + return false; + } + + // subtract the size of the header so validation will not be affected by the + // change of header size in LDML_KMXPLUS_VERSION_19 + header.size -= header.headerSize(); + // now it is safe to cast this to a T - const T *section = reinterpret_cast(header); + const T *section = reinterpret_cast(data + header.headerSize()); if (section == nullptr) { // should not happen. DebugLog("reinterpret_cast<> failed."); assert(false); return false; - } else if (!section->valid(length)) { + } else if (!section->valid(header, fileLength)) { return false; // validation failed. } else { out = section; @@ -163,34 +222,155 @@ template bool section_from_bytes(const uint8_t* data, KMX_DWORD length } } +/** + * @brief This is a special override just for COMP_KMXPLUS_BKSP, which is identical to + * COMP_KMXPLUS_TRAN, except for ident. So we leverage TRAN functionality to + * process, but in order to keep the distinction bubbling too high, this is the + * only place we differentiate. + * @param sect this is the pointer to the 'sect' section table. Should not be null! + * @param out on exit, will be set to the requested section, or nullptr if missing or invalid + * @returns true if section is valid or missing, false only if invalid + */ +bool get_section_from_sect(const COMP_KMXPLUS_SECT* sect, const COMP_KMXPLUS_HEADER& sectHeader, COMP_KMXPLUS_Section_Helper* helper, const COMP_KMXPLUS_BKSP*& out) { + out = nullptr; + + KMX_DWORD entryLength; + const uint8_t *rawbytes = sect->get(sectHeader, COMP_KMXPLUS_BKSP::IDENT, entryLength); + if (rawbytes == nullptr) { + // just missing, not invalid + return true; + } + + if(!section_from_bytes(rawbytes, entryLength, sectHeader.fileVersion(), helper->header, out)) { + return false; + } + + if(!out) { + assert(false); + DebugLog("unexpected out == nullptr"); + return false; + } + + if(!helper->set(out)) { + return false; + } + + return true; +} + /** * @param sect this is the pointer to the 'sect' section table. Should not be null! * @param out on exit, will be set to the requested section, or nullptr if missing or invalid * @returns true if section is valid or missing, false only if invalid */ template -bool get_section_from_sect(const COMP_KMXPLUS_SECT* sect, const T*& out) { +bool get_section_from_sect(const COMP_KMXPLUS_SECT* sect, const COMP_KMXPLUS_HEADER& sectHeader, COMP_KMXPLUS_Section_Helper* helper, const T*& out) { + out = nullptr; + KMX_DWORD entryLength; - const uint8_t *rawbytes = sect->get(T::IDENT, entryLength); + const uint8_t *rawbytes = sect->get(sectHeader, T::IDENT, entryLength); if (rawbytes == nullptr) { // just missing, not invalid - out = nullptr; return true; - } else { - return section_from_bytes(rawbytes, entryLength, out); } + + if(!section_from_bytes(rawbytes, entryLength, sectHeader.fileVersion(), helper->header, out)) { + return false; + } + + if(!out) { + DebugLog("unexpected out == nullptr"); + assert(false); + return false; + } + + if(!helper->set(out)) { + return false; + } + + return true; +} + +inline bool is_block_valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD offset, KMX_DWORD size) { + if(offset < header.headerSize()) { + DebugLog("[%x] offset(%d) < header.headerSize(%d)", header.ident, offset, header.headerSize()); + assert(false); + return false; + } + if(offset - header.headerSize() > header.size) { + DebugLog("[%x] offset(%d) > header.size(%d)", header.ident, offset, header.size); + assert(false); + return false; + } + if(offset + size - header.headerSize() > header.size) { + DebugLog("[%x] offset(%d) + size(%d) > header.size(%d)", header.ident, offset, size, header.size); + assert(false); + return false; + } + return true; +} + +/** + * @brief Get file data at byte offset; cannot retrieve the initial SECT/SEC2 header + * + * @tparam U type of target data + * @param base start of SECT data, i.e. immediately after SECT header + * @param fileOffset offset in bytes from start of file + * @param fileLength + * @return U* + */ +template +const U* get_file_data_at_offset(const COMP_KMXPLUS_SECT *base, COMP_KMXPLUS_HEADER const &header, KMX_DWORD fileOffset, KMX_DWORD fileLength) { + if(fileOffset < header.headerSize()) { + // This is retrieving data based on the SECT/SEC2 table base, which points + // at the first byte after the SECT/SEC2 header. + DebugLog("Attempted to retrieve SECT/SEC2 header, which is not allowed here"); + assert(false); + return nullptr; + } + if(fileOffset >= fileLength) { + DebugLog("Attempted to retrieve data outside file boundaries"); + assert(false); + return nullptr; + } + fileOffset -= header.headerSize(); + const uint8_t* thisptr = reinterpret_cast(base); + return reinterpret_cast(thisptr+fileOffset); +} + +/** + * @brief Get the section data at offset, casting to desired type + * + * @tparam T + * @tparam U + * @param base start of the section data, i.e. immedaitely after section header + * @param header header data for the section + * @param offset offset in bytes from the start of the section data + * @param size size of the data to return, for validation + * @return U + */ +template +U get_section_data_at_offset(const T *base, COMP_KMXPLUS_HEADER const &header, KMX_DWORD offset, KMX_DWORD size) { + if(!is_block_valid(header, offset, size)) { + return nullptr; + } + + offset -= header.headerSize(); + const uint8_t* thisptr = reinterpret_cast(base); + U start = reinterpret_cast(thisptr+offset); + return start; } bool -COMP_KMXPLUS_HEADER::valid(KMX_DWORD length) const { +COMP_KMXPLUS_HEADER_17::valid(KMX_DWORD length) const { DebugLog("%c%c%c%c: (%X) size 0x%X\n", DEBUG_IDENT(ident), ident, size); - if (size < LDML_LENGTH_HEADER) { - DebugLog("size < LDML_LENGTH_HEADER"); + if (size < LDML_LENGTH_HEADER_17) { + DebugLog("size %d < LDML_LENGTH_HEADER_17 %d", size, LDML_LENGTH_HEADER_17); assert(false); return false; } if (size > length) { - DebugLog("size > length"); + DebugLog("size %d > length %d", size, length); assert(false); return false; } @@ -202,12 +382,30 @@ COMP_KMXPLUS_HEADER::valid(KMX_DWORD length) const { } bool -COMP_KMXPLUS_LOCA::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { - DebugLog("header.size < expected size"); +COMP_KMXPLUS_HEADER_19::valid(KMX_DWORD length) const { + DebugLog("%c%c%c%c: (%X) size 0x%X\n", DEBUG_IDENT(ident), ident, size); + if (size < LDML_LENGTH_HEADER_19) { + DebugLog("size %d < LDML_LENGTH_HEADER %d", size, LDML_LENGTH_HEADER_19); + assert(false); + return false; + } + if (size > length) { + DebugLog("size %d > length %d", size, length); assert(false); return false; } + if (!validate_section_name(ident)) { + return false; + } + DebugLog(" (header OK)"); // newline after section name + return true; +} + +bool +COMP_KMXPLUS_LOCA::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), sizeof(*this)+(sizeof(entries[0])*count))) { + return false; + } for(KMX_DWORD i=0; i header.size) { - DebugLog("#0x%X: expected end of string past header.size", i); - assert(false); + const KMX_DWORD offset = entries[i].offset; + const KMX_DWORD length = entries[i].length; + const KMX_WCHAR* start = get_section_data_at_offset(this, header, offset, length); + if(!start) { return false; } - const uint8_t* thisptr = reinterpret_cast(this); - const KMX_WCHAR* start = reinterpret_cast(thisptr+offset); if(start[length] != 0) { DebugLog("#0x%X: String of length 0x%x not null terminated", i, length); assert(start[length] == 0); @@ -330,7 +523,7 @@ bool COMP_KMXPLUS_STRS::valid_string(const KMX_WCHAR* start, KMX_DWORD length) { // else OK (good marker) } else if(!Uni_IsValid(ch)) { DebugLog("String of length 0x%x @ 0x%x: Char U+%04X is illegal char", length, n, ch); - return false; + return false; } // else OK (other char) } return true; @@ -349,12 +542,10 @@ std::u16string COMP_KMXPLUS_STRS::str_from_char(KMX_DWORD v) { bool -COMP_KMXPLUS_SECT::valid(KMX_DWORD length) const { +COMP_KMXPLUS_SECT::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD fileLength) const { DebugLog("sect: total 0x%X\n", total); DebugLog("sect: count 0x%X\n", count); - if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { - DebugLog("header.size < expected size"); - assert(false); + if(!is_block_valid(header, header.headerSize(), sizeof(*this)+(sizeof(entries[0])*count))) { return false; } @@ -371,11 +562,12 @@ COMP_KMXPLUS_SECT::valid(KMX_DWORD length) const { overall_valid = false; continue; } - const uint8_t* data = reinterpret_cast(this); - const uint8_t* entrydata = data + entry.offset; - KMX_DWORD entrylength = length - entry.offset; + + const uint8_t* entrydata = get_file_data_at_offset(this, header, entry.offset, fileLength); + KMX_DWORD entryMaxLength = fileLength - entry.offset; // just validate header - if(header_from_bytes(entrydata, entrylength, entry.sect) == nullptr) { + COMP_KMXPLUS_HEADER localHeader; + if(!header_from_bytes(entrydata, entryMaxLength, header.version, entry.sect, localHeader)) { DebugLog("Invalid header %X", entry.sect); assert(false); overall_valid = false; @@ -394,20 +586,13 @@ KMX_DWORD COMP_KMXPLUS_SECT::find(KMX_DWORD ident) const { return 0; } -const uint8_t *COMP_KMXPLUS_SECT::get(KMX_DWORD ident, KMX_DWORD &entryLength) const { +const uint8_t *COMP_KMXPLUS_SECT::get(COMP_KMXPLUS_HEADER const& header, KMX_DWORD ident, KMX_DWORD &entryLength) const { entryLength = 0; // the section table is also the beginning of the file. - const uint8_t *rawbytes = reinterpret_cast(this); - if (rawbytes == nullptr) { - // should not get here - null this pointer. - DebugLog("section_from_sect(nullptr) == nullptr"); - assert(false); - return nullptr; - } - // lookup the offset from teh table + // lookup the offset from the table KMX_DWORD offset = find(ident); if (!offset) { - DebugLog("section_from_sect() - not found. section %c%c%c%c (0x%X)", DEBUG_IDENT(ident), ident); + DebugLog("COMP_KMXPLUS_SECT::get() - not found. section %c%c%c%c (0x%X)", DEBUG_IDENT(ident), ident); return nullptr; } // return the potential length, based on table. @@ -415,14 +600,13 @@ const uint8_t *COMP_KMXPLUS_SECT::get(KMX_DWORD ident, KMX_DWORD &entryLength) c // we could take the offset of the next section, as an improvement. entryLength = total - offset; // return the pointer to the raw bytes - return rawbytes+offset; + return get_file_data_at_offset(this, header, offset, total); } // ---- transform related fcns bool -COMP_KMXPLUS_ELEM::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { - DebugLog("header.size 0x%X < expected size"); +COMP_KMXPLUS_ELEM::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), sizeof(*this)+(sizeof(entries[0])*count))) { return false; } const COMP_KMXPLUS_ELEM_ENTRY &firstEntry = entries[0]; @@ -434,7 +618,7 @@ COMP_KMXPLUS_ELEM::valid(KMX_DWORD _kmn_unused(length)) const { for (KMX_DWORD e = 1; e < count; e++) { // Don't need to recheck the first entry hbere. KMX_DWORD listLength; - if (getElementList(e, listLength) == nullptr) { + if (getElementList(header, e, listLength) == nullptr) { return false; } if (listLength == 0) { // only the first element should have length zero @@ -446,7 +630,14 @@ COMP_KMXPLUS_ELEM::valid(KMX_DWORD _kmn_unused(length)) const { } const COMP_KMXPLUS_ELEM_ELEMENT * -COMP_KMXPLUS_ELEM::getElementList(KMX_DWORD elementNumber, KMX_DWORD &length) const { +COMP_KMXPLUS_ELEM_Helper::getElementList(KMX_DWORD elementNumber, KMX_DWORD &length) const { + assert(data()); + if(!data()) return nullptr; + return data()->getElementList(header, elementNumber, length); +} + +const COMP_KMXPLUS_ELEM_ELEMENT * +COMP_KMXPLUS_ELEM::getElementList(const COMP_KMXPLUS_HEADER &header, KMX_DWORD elementNumber, KMX_DWORD &length) const { if (elementNumber >= count) { DebugLog("ERROR: COMP_KMXPLUS_ELEM::getElementList(%d) >= count %d", elementNumber, count); assert(false); @@ -457,15 +648,15 @@ COMP_KMXPLUS_ELEM::getElementList(KMX_DWORD elementNumber, KMX_DWORD &length) co if (length == 0) { return nullptr; // Normal case for first element } - if (entry.offset + (entry.length * sizeof(COMP_KMXPLUS_ELEM_ELEMENT)) > header.size) { + if (entry.offset - header.headerSize() + (entry.length * sizeof(COMP_KMXPLUS_ELEM_ELEMENT)) > header.size) { DebugLog("ERROR: !! COMP_KMXPLUS_ELEM::getElementList(%d) would be off end of data area", elementNumber); assert(false); return nullptr; } - // pointer to beginning of elem section - const uint8_t *rawdata = reinterpret_cast(this); + // pointer to specified entry - return reinterpret_cast(rawdata + entry.offset); + return get_section_data_at_offset(this, header, entry.offset, + entry.length * sizeof(COMP_KMXPLUS_ELEM_ELEMENT)); } @@ -476,7 +667,7 @@ COMP_KMXPLUS_ELEM_ELEMENT::get_element_string() const { } std::deque -COMP_KMXPLUS_ELEM_ELEMENT::loadAsStringList(KMX_DWORD length, const COMP_KMXPLUS_STRS &strs) const { +COMP_KMXPLUS_ELEM_ELEMENT::loadAsStringList(KMX_DWORD length, const COMP_KMXPLUS_STRS_Helper &strs) const { std::deque list; for (KMX_DWORD i = 0; i= tran->groupCount) { + if (!valid() || group >= data()->groupCount) { assert(false); return nullptr; } return groups + group; } - const COMP_KMXPLUS_TRAN_TRANSFORM * COMP_KMXPLUS_TRAN_Helper::getTransform(KMX_DWORD transform) const { - if (!valid() || transform >= tran->transformCount) { + if (!valid() || transform >= data()->transformCount) { assert(false); return nullptr; } return transforms + transform; } - const COMP_KMXPLUS_TRAN_REORDER * COMP_KMXPLUS_TRAN_Helper::getReorder(KMX_DWORD reorder) const { - if (!valid() || reorder >= tran->reorderCount) { + if (!valid() || reorder >= data()->reorderCount) { assert(false); return nullptr; } return reorders + reorder; } - bool -COMP_KMXPLUS_TRAN_Helper::setTran(const COMP_KMXPLUS_TRAN *newTran) { +COMP_KMXPLUS_TRAN_Helper::set(const COMP_KMXPLUS_TRAN *newTran) { + if(!COMP_KMXPLUS_Section_Helper::set(newTran)) { + return false; + } is_valid = true; if (newTran == nullptr) { DebugLog("tran helper: missing, newTran=%p", newTran); @@ -560,44 +750,50 @@ COMP_KMXPLUS_TRAN_Helper::setTran(const COMP_KMXPLUS_TRAN *newTran) { // No assert here: just a missing section return true; } - DebugLog("tran helper: validating '%c%c%c%c' newTran=%p", DEBUG_IDENT(newTran->header.ident), newTran); - tran = newTran; - const uint8_t *rawdata = reinterpret_cast(tran); - rawdata += LDML_LENGTH_TRAN; // skip past non-dynamic portion + KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_TRAN); // groups - if (tran->groupCount > 0) { - groups = reinterpret_cast(rawdata); + if (data()->groupCount > 0) { + groups = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_TRAN_GROUP) * data()->groupCount); } else { - groups = nullptr; + groups = nullptr; + } + + if(!groups) { is_valid = false; assert(is_valid); } - rawdata += sizeof(COMP_KMXPLUS_TRAN_GROUP) * tran->groupCount; + offset += sizeof(COMP_KMXPLUS_TRAN_GROUP) * data()->groupCount; // transforms - if (tran->transformCount > 0) { - transforms = reinterpret_cast(rawdata); + if (data()->transformCount > 0) { + transforms = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_TRAN_TRANSFORM) * data()->transformCount); + if(!transforms) { + is_valid = false; + assert(is_valid); + } } else { - transforms = nullptr; - // is_valid = false; - // assert(is_valid); + transforms = nullptr; } - rawdata += sizeof(COMP_KMXPLUS_TRAN_TRANSFORM) * tran->transformCount; + offset += sizeof(COMP_KMXPLUS_TRAN_TRANSFORM) * data()->transformCount; // reorders - if (tran->reorderCount > 0) { - reorders = reinterpret_cast(rawdata); + if (data()->reorderCount > 0) { + reorders = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_TRAN_REORDER) * data()->reorderCount); + if(!reorders) { + is_valid = false; + assert(is_valid); + } } else { reorders = nullptr; - // is_valid = false; - // assert(is_valid); } - // rawdata += sizeof(COMP_KMXPLUS_TRAN_REORDER) * tran->reorderCount; // Now, validate offsets by walking if (is_valid) { - for(KMX_DWORD i = 0; is_valid && i < tran->groupCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->groupCount; i++) { const COMP_KMXPLUS_TRAN_GROUP &group = groups[i]; // is the count off the end? DebugLog( @@ -605,9 +801,9 @@ COMP_KMXPLUS_TRAN_Helper::setTran(const COMP_KMXPLUS_TRAN *newTran) { group.index + group.count - 1); if (group.type == LDML_TRAN_GROUP_TYPE_TRANSFORM) { DebugLog(" .. type=transform"); - if ((group.index >= tran->transformCount) || (group.index + group.count > tran->transformCount)) { + if ((group.index >= data()->transformCount) || (group.index + group.count > data()->transformCount)) { DebugLog("COMP_KMXPLUS_TRAN_Helper: group[%d] would access transform %d+%d, > count %d", - i, group.index, group.count, tran->transformCount); + i, group.index, group.count, data()->transformCount); is_valid = false; assert(is_valid); } @@ -625,9 +821,9 @@ COMP_KMXPLUS_TRAN_Helper::setTran(const COMP_KMXPLUS_TRAN *newTran) { } } else if (group.type == LDML_TRAN_GROUP_TYPE_REORDER) { DebugLog(" .. type=reorder"); - if ((group.index >= tran->reorderCount) || (group.index + group.count > tran->reorderCount)) { + if ((group.index >= data()->reorderCount) || (group.index + group.count > data()->reorderCount)) { DebugLog("COMP_KMXPLUS_TRAN_Helper: group[%d] would access reorder %d+%d, > count %d", - i, group.index, group.count, tran->reorderCount); + i, group.index, group.count, data()->reorderCount); is_valid = false; assert(is_valid); } @@ -648,7 +844,7 @@ COMP_KMXPLUS_TRAN_Helper::setTran(const COMP_KMXPLUS_TRAN *newTran) { } } // Return results - DebugLog("COMP_KMXPLUS_TRAN_Helper.setTran(): %s", is_valid ? "valid" : "invalid"); + DebugLog("COMP_KMXPLUS_TRAN_Helper.set(): %s", is_valid ? "valid" : "invalid"); assert(is_valid); return is_valid; } @@ -656,28 +852,28 @@ COMP_KMXPLUS_TRAN_Helper::setTran(const COMP_KMXPLUS_TRAN *newTran) { bool -COMP_KMXPLUS_LAYR::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this) +COMP_KMXPLUS_LAYR::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), + sizeof(*this) + (listCount * sizeof(COMP_KMXPLUS_LAYR_LIST)) + (layerCount * sizeof(COMP_KMXPLUS_LAYR_ENTRY)) + (rowCount * sizeof(COMP_KMXPLUS_LAYR_ROW)) - + (keyCount * sizeof(COMP_KMXPLUS_LAYR_KEY))) { - DebugLog("header.size < expected size"); - assert(false); + + (keyCount * sizeof(COMP_KMXPLUS_LAYR_KEY)))) { return false; } DebugLog("layr header is valid"); // Note: We only do minimal validation here because of the - // dynamic structure. See COMP_KMXPLUS_LAYR_Helper.setLayr() (below) + // dynamic structure. See COMP_KMXPLUS_LAYR_Helper.set() (below) // all remaining checks return true; } -COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : layr(nullptr), is_valid(false) { +COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : is_valid(false) { } bool -COMP_KMXPLUS_LAYR_Helper::setLayr(const COMP_KMXPLUS_LAYR *newLayr) { +COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { + COMP_KMXPLUS_Section_Helper::set(newLayr); DebugLog("validating newLayr=%p", newLayr); is_valid = true; if (newLayr == nullptr) { @@ -685,68 +881,78 @@ COMP_KMXPLUS_LAYR_Helper::setLayr(const COMP_KMXPLUS_LAYR *newLayr) { // which validates this section's length. Will be nullptr here if invalid. return true; // not invalid, just missing } - layr = newLayr; - const uint8_t *rawdata = reinterpret_cast(newLayr); - rawdata += LDML_LENGTH_LAYR; // skip past non-dynamic portion + KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_LAYR); // skip past non-dynamic portion // lists - if (layr->listCount > 0) { - lists = reinterpret_cast(rawdata); + if (data()->listCount > 0) { + lists = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_LAYR_LIST) * data()->listCount); } else { lists = nullptr; + } + if(!lists) { is_valid = false; assert(is_valid); } - rawdata += sizeof(COMP_KMXPLUS_LAYR_LIST) * layr->listCount; + offset += sizeof(COMP_KMXPLUS_LAYR_LIST) * data()->listCount; // entries - if (layr->layerCount > 0) { - entries = reinterpret_cast(rawdata); + if (data()->layerCount > 0) { + entries = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_LAYR_ENTRY) * data()->layerCount); } else { entries = nullptr; + } + if(!entries) { is_valid = false; assert(is_valid); } - rawdata += sizeof(COMP_KMXPLUS_LAYR_ENTRY) * layr->layerCount; + offset += sizeof(COMP_KMXPLUS_LAYR_ENTRY) * data()->layerCount; // rows - if (layr->rowCount > 0) { - rows = reinterpret_cast(rawdata); + if (data()->rowCount > 0) { + rows = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_LAYR_ROW) * data()->rowCount); } else { rows = nullptr; + } + if(!rows) { is_valid = false; assert(is_valid); } - rawdata += sizeof(COMP_KMXPLUS_LAYR_ROW) * layr->rowCount; + offset += sizeof(COMP_KMXPLUS_LAYR_ROW) * data()->rowCount; // keys - if (layr->keyCount > 0) { - keys = reinterpret_cast(rawdata); + if (data()->keyCount > 0) { + keys = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_LAYR_KEY) * data()->keyCount); } else { keys = nullptr; + } + if(!keys) { is_valid = false; assert(is_valid); } // Now, validate offsets by walking if (is_valid) { - for(KMX_DWORD i = 0; is_valid && i < layr->listCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->listCount; i++) { const COMP_KMXPLUS_LAYR_LIST &list = lists[i]; // is the count off the end? DebugLog( " %d: hardware s#0x%X, layers %d..%d, minDeviceWidth %.1fmm", i, list.hardware, list.layer, list.layer + list.count - 1, list.minDeviceWidth * (double)0.1); - if ((list.layer >= layr->layerCount) || (list.layer + list.count > layr->layerCount)) { + if ((list.layer >= data()->layerCount) || (list.layer + list.count > data()->layerCount)) { DebugLog("COMP_KMXPLUS_LAYR_Helper: list[%d] would access layer %d+%d, > count %d", - i, list.layer, list.count, layr->layerCount); + i, list.layer, list.count, data()->layerCount); is_valid = false; assert(is_valid); } } - for(KMX_DWORD i = 0; is_valid && i < layr->layerCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->layerCount; i++) { const COMP_KMXPLUS_LAYR_ENTRY &entry = entries[i]; // is the count off the end? DebugLog( " %d: id s#0x%X, rows %d..%d, modifier=0x%X", i, entry.id, entry.row, entry.row+entry.count-1, entry.mod); - if ((entry.row >= layr->rowCount) || (entry.row + entry.count > layr->rowCount)) { + if ((entry.row >= data()->rowCount) || (entry.row + entry.count > data()->rowCount)) { DebugLog("COMP_KMXPLUS_LAYR_Helper: entry[%d] would access row %d+%d, > count %d", - i, entry.row, entry.count, layr->rowCount); + i, entry.row, entry.count, data()->rowCount); is_valid = false; assert(is_valid); } @@ -756,19 +962,19 @@ COMP_KMXPLUS_LAYR_Helper::setLayr(const COMP_KMXPLUS_LAYR *newLayr) { return false; } } - for(KMX_DWORD i = 0; is_valid && i < layr->rowCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->rowCount; i++) { const COMP_KMXPLUS_LAYR_ROW &row = rows[i]; // is the count off the end? - if ((row.key >= layr->keyCount) || (row.key + row.count > layr->keyCount)) { + if ((row.key >= data()->keyCount) || (row.key + row.count > data()->keyCount)) { DebugLog("COMP_KMXPLUS_LAYR_Helper: row[%d] would access key %d+%d, > count %d", - i, row.key, row.count, layr->keyCount); + i, row.key, row.count, data()->keyCount); is_valid = false; assert(is_valid); } } } // Return results - DebugLog("COMP_KMXPLUS_LAYR_Helper.setLayr(): %s", is_valid ? "valid" : "invalid"); + DebugLog("COMP_KMXPLUS_LAYR_Helper.set(): %s", is_valid ? "valid" : "invalid"); assert(is_valid); return is_valid; } @@ -779,7 +985,7 @@ bool COMP_KMXPLUS_LAYR_Helper::valid() const { const COMP_KMXPLUS_LAYR_LIST * COMP_KMXPLUS_LAYR_Helper::getList(KMX_DWORD list) const { - if (!valid() || list >= layr->listCount) { + if (!valid() || list >= data()->listCount) { assert(false); return nullptr; } @@ -788,7 +994,7 @@ COMP_KMXPLUS_LAYR_Helper::getList(KMX_DWORD list) const { const COMP_KMXPLUS_LAYR_ENTRY * COMP_KMXPLUS_LAYR_Helper::getEntry(KMX_DWORD entry) const { - if (!valid() || entry >= layr->layerCount) { + if (!valid() || entry >= data()->layerCount) { assert(false); return nullptr; } @@ -797,7 +1003,7 @@ COMP_KMXPLUS_LAYR_Helper::getEntry(KMX_DWORD entry) const { const COMP_KMXPLUS_LAYR_ROW * COMP_KMXPLUS_LAYR_Helper::getRow(KMX_DWORD row) const { - if (!valid() || row >= layr->rowCount) { + if (!valid() || row >= data()->rowCount) { assert(false); return nullptr; } @@ -806,7 +1012,7 @@ COMP_KMXPLUS_LAYR_Helper::getRow(KMX_DWORD row) const { const COMP_KMXPLUS_LAYR_KEY * COMP_KMXPLUS_LAYR_Helper::getKey(KMX_DWORD key) const { - if (!valid() || key >= layr->keyCount) { + if (!valid() || key >= data()->keyCount) { assert(false); return nullptr; } @@ -814,14 +1020,13 @@ COMP_KMXPLUS_LAYR_Helper::getKey(KMX_DWORD key) const { } bool -COMP_KMXPLUS_KEYS::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this) +COMP_KMXPLUS_KEYS::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), + sizeof(*this) + (keyCount * sizeof(COMP_KMXPLUS_KEYS_KEY)) + (flicksCount * sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST)) + (flickCount * sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT)) - + (kmapCount * sizeof(COMP_KMXPLUS_KEYS_KMAP))) { - DebugLog("header.size < expected size"); - assert(false); + + (kmapCount * sizeof(COMP_KMXPLUS_KEYS_KMAP)))) { return false; } // further validation in the COMP_KMXPLUS_KEYS_Helper helper obj @@ -829,11 +1034,12 @@ COMP_KMXPLUS_KEYS::valid(KMX_DWORD _kmn_unused(length)) const { } -COMP_KMXPLUS_KEYS_Helper::COMP_KMXPLUS_KEYS_Helper() : key2(nullptr), is_valid(false) { +COMP_KMXPLUS_KEYS_Helper::COMP_KMXPLUS_KEYS_Helper() : is_valid(false) { } bool -COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { +COMP_KMXPLUS_KEYS_Helper::set(const COMP_KMXPLUS_KEYS *newKeys) { + COMP_KMXPLUS_Section_Helper::set(newKeys); DebugLog("validating newKeys=%p", newKeys); is_valid = true; if (newKeys == nullptr) { @@ -841,46 +1047,62 @@ COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { // which validates this section's length. Will be nullptr here if invalid. return true; // not invalid, just missing } - key2 = newKeys; - const uint8_t *rawdata = reinterpret_cast(newKeys); - rawdata += LDML_LENGTH_KEYS; // skip past non-dynamic portion // keys - if (key2->keyCount > 0) { - keys = reinterpret_cast(rawdata); + KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_KEYS); + if (data()->keyCount > 0) { + keys = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_KEYS_KEY) * data()->keyCount); } else { keys = nullptr; + } + if(!keys) { is_valid = false; assert(is_valid); } - rawdata += sizeof(COMP_KMXPLUS_KEYS_KEY) * key2->keyCount; + offset += sizeof(COMP_KMXPLUS_KEYS_KEY) * data()->keyCount; // flicks - if (key2->flicksCount > 0) { - flickLists = reinterpret_cast(rawdata); + if (data()->flicksCount > 0) { + flickLists = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST) * data()->flicksCount); + if(!flickLists) { + is_valid = false; + assert(is_valid); + } } else { flickLists = nullptr; // not an error } - rawdata += sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST) * key2->flicksCount; + offset += sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST) * data()->flicksCount; // flick - if (key2->flickCount > 0) { - flickElements = reinterpret_cast(rawdata); + if (data()->flickCount > 0) { + flickElements = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT) * data()->flickCount); + if(!flickElements) { + is_valid = false; + assert(is_valid); + } } else { flickElements = nullptr; // not an error } - rawdata += sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT) * key2->flickCount; + offset += sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT) * data()->flickCount; // kmap - if (key2->kmapCount > 0) { - kmap = reinterpret_cast(rawdata); + if (data()->kmapCount > 0) { + kmap = get_section_data_at_offset(data(), header, offset, + sizeof(COMP_KMXPLUS_KEYS_KMAP) * data()->kmapCount); + if(!kmap) { + is_valid = false; + assert(is_valid); + } } else { kmap = nullptr; // not an error } // Now, validate offsets by walking if (is_valid) { - for(KMX_DWORD i = 0; is_valid && i < key2->keyCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->keyCount; i++) { const auto &key = keys[i]; // is the count off the end? DebugLoad( " id=0x%X, to=0x%X, flicks=%d", i, key.id, key.to, key.flicks); // TODO-LDML: could dump more fields here - if (key.flicks >0 && key.flicks >= key2->flicksCount) { + if (key.flicks >0 && key.flicks >= data()->flicksCount) { DebugLog("key[%d] has invalid flicks index %d", i, key.flicks); is_valid = false; assert(is_valid); @@ -894,7 +1116,7 @@ COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { } } } - for(KMX_DWORD i = 0; is_valid && i < key2->flicksCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->flicksCount; i++) { const auto &e = flickLists[i]; // is the count off the end? DebugLoad(" %d: index %d, count %d", i, e.flick, e.count); @@ -904,13 +1126,13 @@ COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { is_valid = false; assert(is_valid); } - } else if ((e.flick >= key2->flickCount) || (e.flick + e.count > key2->flickCount)) { - DebugLog("flicks[%d] would access flick %d+%d, > count %d", i, e.flick, e.count, key2->flickCount); + } else if ((e.flick >= data()->flickCount) || (e.flick + e.count > data()->flickCount)) { + DebugLog("flicks[%d] would access flick %d+%d, > count %d", i, e.flick, e.count, data()->flickCount); is_valid = false; assert(is_valid); } } - for(KMX_DWORD i = 0; is_valid && i < key2->flickCount; i++) { + for(KMX_DWORD i = 0; is_valid && i < data()->flickCount; i++) { const auto &e = flickElements[i]; // validate to is present if (e.to == 0 || e.directions == 0) { @@ -921,8 +1143,8 @@ COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { DebugLoad(" %d: to=0x%X, directions=0x%X", i, e.to, e.directions); } // now the kmap - DebugLoad(" kmap count: #0x%X", key2->kmapCount); - for (KMX_DWORD i = 0; i < key2->kmapCount; i++) { + DebugLoad(" kmap count: #0x%X", data()->kmapCount); + for (KMX_DWORD i = 0; i < data()->kmapCount; i++) { DebugLoad(" #0x%d\n", i); auto &entry = kmap[i]; DebugLoad(" vkey\t0x%X", entry.vkey); @@ -933,22 +1155,22 @@ COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { assert(false); is_valid = false; } - if (entry.key >= key2->keyCount) { + if (entry.key >= data()->keyCount) { // preposterous key # - DebugLog("kmap[0x%X].key = #0x%X, but that is >= keyCount 0x%X", i, entry.key, key2->keyCount); + DebugLog("kmap[0x%X].key = #0x%X, but that is >= keyCount 0x%X", i, entry.key, data()->keyCount); assert(false); is_valid = false; } } } // Return results - DebugLog("COMP_KMXPLUS_KEYS_Helper.setKeys(): %s", is_valid ? "valid" : "invalid"); + DebugLog("COMP_KMXPLUS_KEYS_Helper.set(): %s", is_valid ? "valid" : "invalid"); return is_valid; } const COMP_KMXPLUS_KEYS_KEY * COMP_KMXPLUS_KEYS_Helper::getKeys(KMX_DWORD i) const { - if (!valid() || i >= key2->keyCount) { + if (!valid() || i >= data()->keyCount) { assert(false); return nullptr; } @@ -957,7 +1179,7 @@ COMP_KMXPLUS_KEYS_Helper::getKeys(KMX_DWORD i) const { const COMP_KMXPLUS_KEYS_KEY* COMP_KMXPLUS_KEYS_Helper::findKeyByStringId(KMX_DWORD strId, KMX_DWORD &i) const { - for (; i < key2->keyCount; i++) { + for (; i < data()->keyCount; i++) { if (keys[i].id == strId) { return &keys[i]; } @@ -967,7 +1189,7 @@ COMP_KMXPLUS_KEYS_Helper::findKeyByStringId(KMX_DWORD strId, KMX_DWORD &i) const const COMP_KMXPLUS_KEYS_KEY* COMP_KMXPLUS_KEYS_Helper::findKeyByStringTo(const std::u16string& str, KMX_DWORD strId, KMX_DWORD &i) const { - for (; i < key2->keyCount; i++) { + for (; i < data()->keyCount; i++) { if (keys[i].flags & LDML_KEYS_KEY_FLAGS_EXTEND) { if (strId != 0 && keys[i].to == strId) { return &keys[i]; @@ -981,7 +1203,7 @@ COMP_KMXPLUS_KEYS_Helper::findKeyByStringTo(const std::u16string& str, KMX_DWORD const COMP_KMXPLUS_KEYS_FLICK_LIST * COMP_KMXPLUS_KEYS_Helper::getFlickLists(KMX_DWORD i) const { - if (!valid() || i >= key2->flicksCount) { + if (!valid() || i >= data()->flicksCount) { assert(false); return nullptr; } @@ -990,7 +1212,7 @@ COMP_KMXPLUS_KEYS_Helper::getFlickLists(KMX_DWORD i) const { const COMP_KMXPLUS_KEYS_FLICK_ELEMENT * COMP_KMXPLUS_KEYS_Helper::getFlickElements(KMX_DWORD i) const { - if (!valid() || i >= key2->flickCount) { + if (!valid() || i >= data()->flickCount) { assert(false); return nullptr; } @@ -999,7 +1221,7 @@ COMP_KMXPLUS_KEYS_Helper::getFlickElements(KMX_DWORD i) const { const COMP_KMXPLUS_KEYS_KMAP * COMP_KMXPLUS_KEYS_Helper::getKmap(KMX_DWORD i) const { - if (!valid() || i >= key2->kmapCount) { + if (!valid() || i >= data()->kmapCount) { assert(false); return nullptr; } @@ -1015,23 +1237,23 @@ COMP_KMXPLUS_KEYS_KEY::get_to_string() const { // LIST bool -COMP_KMXPLUS_LIST::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this) +COMP_KMXPLUS_LIST::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), + sizeof(*this) + (listCount * sizeof(COMP_KMXPLUS_LIST_ITEM)) - + (indexCount * sizeof(COMP_KMXPLUS_LIST_INDEX))) { - DebugLog("header.size < expected size"); - assert(false); + + (indexCount * sizeof(COMP_KMXPLUS_LIST_INDEX)))) { return false; } return true; } -COMP_KMXPLUS_LIST_Helper::COMP_KMXPLUS_LIST_Helper() : list(nullptr), is_valid(false) { +COMP_KMXPLUS_LIST_Helper::COMP_KMXPLUS_LIST_Helper() : is_valid(false) { } bool -COMP_KMXPLUS_LIST_Helper::setList(const COMP_KMXPLUS_LIST *newList) { +COMP_KMXPLUS_LIST_Helper::set(const COMP_KMXPLUS_LIST *newList) { + COMP_KMXPLUS_Section_Helper::set(newList); DebugLog("validating newList=%p", newList); is_valid = true; if (newList == nullptr) { @@ -1039,28 +1261,33 @@ COMP_KMXPLUS_LIST_Helper::setList(const COMP_KMXPLUS_LIST *newList) { // which validates this section's length. Will be nullptr here if invalid. return true; // not invalid, just missing } - list = newList; - const uint8_t *rawdata = reinterpret_cast(newList); - rawdata += LDML_LENGTH_LIST; // skip past non-dynamic portion + + KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_LIST); // skip past non-dynamic portion // lists - if (list->listCount > 0) { - lists = reinterpret_cast(rawdata); + if (data()->listCount > 0) { + lists = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_LIST_ITEM) * data()->listCount); + if(!lists) { + is_valid = false; + assert(is_valid); + } } else { lists = nullptr; // not invalid, just empty. } - rawdata += sizeof(COMP_KMXPLUS_LIST_ITEM) * list->listCount; + offset += sizeof(COMP_KMXPLUS_LIST_ITEM) * data()->listCount; // entries - if (list->indexCount > 0) { - indices = reinterpret_cast(rawdata); + if (data()->indexCount > 0) { + indices = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_LIST_INDEX) * data()->indexCount); + if(!indices) { + is_valid = false; + assert(is_valid); + } } else { indices = nullptr; } - // rawdata += sizeof(COMP_KMXPLUS_LIST_INDEX) * list->indexCount; - // Now, validate offsets by walking if (is_valid) { - for (KMX_DWORD i = 0; is_valid && i < list->listCount; i++) { + for (KMX_DWORD i = 0; is_valid && i < data()->listCount; i++) { const auto &e = lists[i]; // is the count off the end? DebugLog("list 0x%X: index %d, count %d", i, e.index, e.count); @@ -1070,28 +1297,28 @@ COMP_KMXPLUS_LIST_Helper::setList(const COMP_KMXPLUS_LIST *newList) { is_valid = false; assert(is_valid); } - } else if ((e.index >= list->indexCount) || (e.index + e.count > list->indexCount)) { - DebugLog("list[%d] would access index %d+%d, > count %d", i, e.index, e.count, list->indexCount); + } else if ((e.index >= data()->indexCount) || (e.index + e.count > data()->indexCount)) { + DebugLog("list[%d] would access index %d+%d, > count %d", i, e.index, e.count, data()->indexCount); is_valid = false; assert(is_valid); } } #if KMXPLUS_DEBUG_LOAD - for (KMX_DWORD i = 0; is_valid && i < list->indexCount; i++) { + for (KMX_DWORD i = 0; is_valid && i < data()->indexCount; i++) { const auto &e = indices[i]; DebugLoad(" index %d: str 0x%X", i, e); } #endif } // Return results - DebugLog("COMP_KMXPLUS_LIST_Helper.setList(): %s", is_valid ? "valid" : "invalid"); + DebugLog("COMP_KMXPLUS_LIST_Helper.set(): %s", is_valid ? "valid" : "invalid"); assert(is_valid); return is_valid; } const COMP_KMXPLUS_LIST_ITEM * COMP_KMXPLUS_LIST_Helper::getList(KMX_DWORD i) const { - if (!valid() || i >= list->listCount) { + if (!valid() || i >= data()->listCount) { assert(false); return nullptr; } @@ -1100,7 +1327,7 @@ COMP_KMXPLUS_LIST_Helper::getList(KMX_DWORD i) const { const COMP_KMXPLUS_LIST_INDEX * COMP_KMXPLUS_LIST_Helper::getIndex(KMX_DWORD i) const { - if (!valid() || i >= list->indexCount) { + if (!valid() || i >= data()->indexCount) { assert(false); return nullptr; } @@ -1111,12 +1338,10 @@ COMP_KMXPLUS_LIST_Helper::getIndex(KMX_DWORD i) const { // USET bool -COMP_KMXPLUS_USET::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this) +COMP_KMXPLUS_USET::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), sizeof(*this) + (usetCount * sizeof(COMP_KMXPLUS_USET_USET)) - + (rangeCount * sizeof(COMP_KMXPLUS_USET_RANGE))) { - DebugLog("header.size < expected size"); - assert(false); + + (rangeCount * sizeof(COMP_KMXPLUS_USET_RANGE)))) { return false; } return true; // see helper @@ -1128,11 +1353,12 @@ COMP_KMXPLUS_USET_RANGE::COMP_KMXPLUS_USET_RANGE(KMX_DWORD s, KMX_DWORD e) : sta COMP_KMXPLUS_USET_RANGE::COMP_KMXPLUS_USET_RANGE(const COMP_KMXPLUS_USET_RANGE &other) : start(other.start), end(other.end) { } -COMP_KMXPLUS_USET_Helper::COMP_KMXPLUS_USET_Helper() : uset(nullptr), is_valid(false), usets(nullptr), ranges(nullptr) { +COMP_KMXPLUS_USET_Helper::COMP_KMXPLUS_USET_Helper() : is_valid(false), usets(nullptr), ranges(nullptr) { } bool -COMP_KMXPLUS_USET_Helper::setUset(const COMP_KMXPLUS_USET *newUset) { +COMP_KMXPLUS_USET_Helper::set(const COMP_KMXPLUS_USET *newUset) { + COMP_KMXPLUS_Section_Helper::set(newUset); DebugLoad("validating newUset=%p", newUset); is_valid = true; if (newUset == nullptr) { @@ -1140,32 +1366,38 @@ COMP_KMXPLUS_USET_Helper::setUset(const COMP_KMXPLUS_USET *newUset) { // which validates this section's length. Will be nullptr here if invalid. return true; // not invalid, just missing } - uset = newUset; - const uint8_t *rawdata = reinterpret_cast(newUset); - rawdata += LDML_LENGTH_USET; // skip past non-dynamic portion + KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_USET); // skip past non-dynamic portion // usets - if (uset->usetCount > 0) { - usets = reinterpret_cast(rawdata); + if (data()->usetCount > 0) { + usets = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_USET_USET) * data()->usetCount); + if(!usets) { + is_valid = false; + assert(is_valid); + } } else { usets = nullptr; // not invalid, just empty. } - rawdata += sizeof(COMP_KMXPLUS_USET_USET) * uset->usetCount; + offset += sizeof(COMP_KMXPLUS_USET_USET) * data()->usetCount; // entries - if (uset->rangeCount > 0) { - ranges = reinterpret_cast(rawdata); + if (data()->rangeCount > 0) { + ranges = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_USET_RANGE) * data()->rangeCount); + if(!ranges) { + is_valid = false; + assert(is_valid); + } } else { ranges = nullptr; } // Now, validate offsets by walking // is_valid must be true at this point. - for (KMX_DWORD i = 0; is_valid && i < uset->usetCount; i++) { + for (KMX_DWORD i = 0; is_valid && i < data()->usetCount; i++) { const auto &e = usets[i]; // is the count off the end? DebugLog("uset 0x%X: range %d, count %d, pattern 0x%X", i, e.range, e.count, e.pattern); - if ((e.range >= uset->rangeCount) || (e.range + e.count > uset->rangeCount)) { - DebugLog("uset[%d] would access range %d+%d, > count %d", i, e.range, e.count, uset->rangeCount); + if ((e.range >= data()->rangeCount) || (e.range + e.count > data()->rangeCount)) { + DebugLog("uset[%d] would access range %d+%d, > count %d", i, e.range, e.count, data()->rangeCount); is_valid = false; assert(is_valid); } else { @@ -1195,7 +1427,7 @@ COMP_KMXPLUS_USET_Helper::setUset(const COMP_KMXPLUS_USET *newUset) { } } // Return results - DebugLog("COMP_KMXPLUS_USET_Helper.setUset(): %s", is_valid ? "valid" : "invalid"); + DebugLog("COMP_KMXPLUS_USET_Helper.set(): %s", is_valid ? "valid" : "invalid"); assert(is_valid); return is_valid; } @@ -1244,7 +1476,7 @@ SimpleUSet::dump() const { SimpleUSet COMP_KMXPLUS_USET_Helper::getUset(KMXPLUS_USET i) const { - if (!valid() || i >= uset->usetCount) { + if (!valid() || i >= data()->usetCount) { assert(false); return SimpleUSet(nullptr, 0); // empty set } @@ -1254,7 +1486,7 @@ COMP_KMXPLUS_USET_Helper::getUset(KMXPLUS_USET i) const { const COMP_KMXPLUS_USET_RANGE * COMP_KMXPLUS_USET_Helper::getRange(KMX_DWORD i) const { - if (!valid() || i >= uset->rangeCount) { + if (!valid() || i >= data()->rangeCount) { assert(false); return nullptr; } @@ -1292,74 +1524,99 @@ kmx_plus::kmx_plus(const COMP_KEYBOARD *keyboard, size_t length) return; } + if( ex->kmxplus.dwKMXPlusSize < sizeof(COMP_KMXPLUS_HEADER_17)) { + DebugLog("dwKMXPlusSize is too small to contain a section"); + valid = false; + assert(valid); + return; + } + const uint8_t* rawdata = reinterpret_cast(keyboard); valid = true; - valid = section_from_bytes(rawdata+ex->kmxplus.dpKMXPlus, ex->kmxplus.dwKMXPlusSize, sect) && valid; + COMP_KMXPLUS_HEADER sect_header; + KMX_DWORD fileVersion = determine_file_version_from_bytes(rawdata+ex->kmxplus.dpKMXPlus, ex->kmxplus.dwKMXPlusSize); + if(fileVersion == 0) { + valid = false; + assert(valid); + return; + } + + valid = section_from_bytes(rawdata+ex->kmxplus.dpKMXPlus, ex->kmxplus.dwKMXPlusSize, fileVersion, sect_header, sect) && valid; if (sect == nullptr) { DebugLog("kmx_plus(): 'sect' missing or did not validate"); valid = false; - } else { - // load other sections, validating as we go - // each field will be set to nullptr if validation fails or if the section is missing - valid = get_section_from_sect(sect, bksp) && valid; - valid = get_section_from_sect(sect, disp) && valid; - valid = get_section_from_sect(sect, elem) && valid; - valid = get_section_from_sect(sect, key2) && valid; - valid = get_section_from_sect(sect, layr) && valid; - valid = get_section_from_sect(sect, list) && valid; - valid = get_section_from_sect(sect, loca) && valid; - valid = get_section_from_sect(sect, meta) && valid; - valid = get_section_from_sect(sect, strs) && valid; - valid = get_section_from_sect(sect, tran) && valid; - valid = get_section_from_sect(sect, uset) && valid; - valid = get_section_from_sect(sect, vars) && valid; - - // Initialize the helper objects for sections with dynamic parts. - // Note: all of these setters will be passed 'nullptr' - // if any section had failed validation, or was missing. - // A missing section does not invalidate the kmxplus. - // we attempt to initialize each one - - valid = bkspHelper.setTran(bksp) && valid; // bksp handled by …TRAN_Helper - valid = key2Helper.setKeys(key2) && valid; - valid = layrHelper.setLayr(layr) && valid; - valid = listHelper.setList(list) && valid; - valid = tranHelper.setTran(tran) && valid; - valid = usetHelper.setUset(uset) && valid; + assert(valid); + return; } + + // load other sections, validating as we go each field will be set to nullptr + // if validation fails or if the section is missing and then initialize the + // helper objects for sections with dynamic parts. Note: all of the helper + // setters will be passed 'nullptr' if any section had failed validation, or + // was missing. + // + // A missing section does not invalidate the kmxplus. We attempt to initialize + // each one + valid = get_section_from_sect(sect, sect_header, &bkspHelper, bksp) && valid; // note overload + valid = get_section_from_sect(sect, sect_header, &dispHelper, disp) && valid; + valid = get_section_from_sect(sect, sect_header, &elemHelper, elem) && valid; + valid = get_section_from_sect(sect, sect_header, &key2Helper, key2) && valid; + valid = get_section_from_sect(sect, sect_header, &layrHelper, layr) && valid; + valid = get_section_from_sect(sect, sect_header, &listHelper, list) && valid; + valid = get_section_from_sect(sect, sect_header, &locaHelper, loca) && valid; + valid = get_section_from_sect(sect, sect_header, &metaHelper, meta) && valid; + valid = get_section_from_sect(sect, sect_header, &strsHelper, strs) && valid; + valid = get_section_from_sect(sect, sect_header, &tranHelper, tran) && valid; + valid = get_section_from_sect(sect, sect_header, &usetHelper, uset) && valid; + valid = get_section_from_sect(sect, sect_header, &varsHelper, vars) && valid; } std::u16string -COMP_KMXPLUS_STRS::get(KMX_DWORD entry) const { +COMP_KMXPLUS_STRS::get(const COMP_KMXPLUS_HEADER& header, KMX_DWORD entry) const { assert(entry < count); if (entry >= count) { return std::u16string(); // Fallback: empty string } const KMX_DWORD offset = entries[entry].offset; const KMX_DWORD length = entries[entry].length; - assert(offset+((length+1)*2) <= header.size); // assert not out of bounds - const uint8_t* thisptr = reinterpret_cast(this); - const KMX_WCHAR* start = reinterpret_cast(thisptr+offset); + + // the string is null terminated in the data file, thus length + 1 + auto start = get_section_data_at_offset(this, header, offset, (length + 1) * sizeof(KMX_WCHAR)); + if(!start) { + return std::u16string(); + } return std::u16string(start, length); } -KMX_DWORD COMP_KMXPLUS_STRS::find(const std::u16string& s) const { +std::u16string COMP_KMXPLUS_STRS_Helper::get(KMX_DWORD entry) const { + assert(data()); + if(!data()) return std::u16string(); + return data()->get(header, entry); +} + +KMX_DWORD COMP_KMXPLUS_STRS::find(const COMP_KMXPLUS_HEADER& header, const std::u16string& s) const { if (s.empty()) { return 0; // shortcut } // TODO-LDML: suboptimal, but currently only run from the test runner. Could be a binary search since the strings are already in codepoint order. for (KMX_DWORD i = 0; ifind(header, s); +} + bool -COMP_KMXPLUS_VARS::valid(KMX_DWORD _kmn_unused(length)) const { - if (header.size < sizeof(*this) - + (varCount * sizeof(COMP_KMXPLUS_VARS_ITEM))) { +COMP_KMXPLUS_VARS::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(!is_block_valid(header, header.headerSize(), sizeof(*this) + + (varCount * sizeof(COMP_KMXPLUS_VARS_ITEM)))) { DebugLog("header.size < expected size"); assert(false); return false; @@ -1380,7 +1637,6 @@ const COMP_KMXPLUS_VARS_ITEM *COMP_KMXPLUS_VARS::findByStringId(KMX_DWORD strId) } - } // namespace kmx } // namespace core } // namespace km diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index 03738ab578a..0d4344709bd 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -49,15 +49,98 @@ struct COMP_KMXPLUS_TRAN_GROUP; struct COMP_KMXPLUS_TRAN_TRANSFORM; struct COMP_KMXPLUS_TRAN_REORDER; struct COMP_KMXPLUS_STRS; +class COMP_KMXPLUS_STRS_Helper; -struct COMP_KMXPLUS_HEADER { +/** + * @brief Binary-backed version of section header, v17, to be used internally + * only when loading; copied into COMP_KMXPLUS_HEADER for use + */ +struct COMP_KMXPLUS_HEADER_17 { + KMXPLUS_IDENT ident; // 0000 Section name + KMX_DWORD_unaligned size; // 0004 Section length + bool valid(KMX_DWORD length) const; +}; + +/** + * @brief Binary-backed version of section header, v19, to be used internally + * only when loading; copied into COMP_KMXPLUS_HEADER for use + */ +struct COMP_KMXPLUS_HEADER_19 { KMXPLUS_IDENT ident; // 0000 Section name KMX_DWORD_unaligned size; // 0004 Section length + KMX_DWORD_unaligned version; // 0008 Section version bool valid(KMX_DWORD length) const; }; +/** + * @brief In-memory version of section header, copied from binary data e.g. + * COMP_KMXPLUS_HEADER_19, and including file version and other useful metadata + */ +struct COMP_KMXPLUS_HEADER { +private: + KMX_DWORD _fileVersion; + KMX_DWORD _headerSize; +public: + // TODO: read only + KMXPLUS_IDENT ident; + KMX_DWORD size; + KMX_DWORD version; + + inline KMX_DWORD fileVersion() const { + return _fileVersion; + } + + inline KMX_DWORD headerSize() const { + return _headerSize; + } + + /** + * @brief Calculate element size, taking into account change in header size + * from v17 to v19 + * + * @param size + * @return KMX_DWORD + */ + inline KMX_DWORD calculateBaseSize(KMX_DWORD size) { + return size - LDML_LENGTH_HEADER_17 + this->_headerSize; + } + + inline void set(KMX_DWORD fileVersion, KMXPLUS_IDENT ident, KMX_DWORD size, KMX_DWORD version = LDML_KMXPLUS_VERSION_17) { + this->_fileVersion = fileVersion; + this->_headerSize = fileVersion == LDML_KMXPLUS_VERSION_17 ? LDML_LENGTH_HEADER_17 : LDML_LENGTH_HEADER_19; + this->ident = ident; + this->size = size; + this->version = version; + } +}; + +// Assert that the length matches the declared length +static_assert(sizeof(struct COMP_KMXPLUS_HEADER_17) == LDML_LENGTH_HEADER_17, "mismatched size of section header"); // Assert that the length matches the declared length -static_assert(sizeof(struct COMP_KMXPLUS_HEADER) == LDML_LENGTH_HEADER, "mismatched size of section header"); +static_assert(sizeof(struct COMP_KMXPLUS_HEADER_19) == LDML_LENGTH_HEADER_19, "mismatched size of section header"); + +/* ------------------------------------------------------------------ + * Section helper base class - all sections have a helper class + ------------------------------------------------------------------ */ + +template +class COMP_KMXPLUS_Section_Helper { +private: + const T* _data; + +protected: + const T* data() const { + return _data; + } + +public: + COMP_KMXPLUS_HEADER header; + COMP_KMXPLUS_Section_Helper() : _data(nullptr) {} + virtual bool set(const T* section) { + _data = section; + return true; + } +}; /* ------------------------------------------------------------------ * sect section @@ -69,37 +152,40 @@ struct COMP_KMXPLUS_SECT_ENTRY { }; struct COMP_KMXPLUS_SECT { + // static_assert(std::is_base_of::value, "header must be a descendant of COMP_KMXPLUS_HEADER"); static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_SECT; - COMP_KMXPLUS_HEADER header; + static const KMXPLUS_IDENT IDENT_V19 = LDML_SECTIONID_SEC2; KMX_DWORD_unaligned total; // 0008 KMXPlus entire length KMX_DWORD_unaligned count; // 000C number of section headers COMP_KMXPLUS_SECT_ENTRY entries[]; // 0010 section entries /** * @brief Get the offset of a section - * + * * @param ident section id such as 'strs'. Never 'sect' (the sect table does not list itself!) * @return KMX_DWORD offset from beginning of kmxplus, or 0 if not found */ KMX_DWORD find(KMX_DWORD ident) const; /** * @brief Get the pointer to a specific section - * + * * @param ident section id such as 'strs'. Never 'sect' (the sect table does not list itself!) * @param entryLength on exit, will be set to the possible length of the section (based on the remainder of the KMX+ file) * @return pointer to raw bytes of requested section, or nullptr if not found */ - const uint8_t *get(KMX_DWORD ident, KMX_DWORD &entryLength) const; + const uint8_t *get(COMP_KMXPLUS_HEADER const& header, KMX_DWORD ident, KMX_DWORD &entryLength) const; /** * @brief True if section is valid. * Does not validate the entire file. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD fileLength) const; }; // Assert that the length matches the declared length -static_assert(sizeof(struct COMP_KMXPLUS_SECT) == LDML_LENGTH_SECT, "mismatched size of section sect"); +static_assert(sizeof(struct COMP_KMXPLUS_SECT) == LDML_LENGTH_SECT - LDML_LENGTH_HEADER_17, "mismatched size of section sect"); static_assert(sizeof(struct COMP_KMXPLUS_SECT) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +class COMP_KMXPLUS_SECT_Helper : public COMP_KMXPLUS_Section_Helper {}; + /* ------------------------------------------------------------------ * elem section ------------------------------------------------------------------ */ @@ -119,7 +205,7 @@ struct COMP_KMXPLUS_ELEM_ELEMENT { * @param length number of elements, including this one * @return the string elements as a string array */ - std::deque loadAsStringList(KMX_DWORD length, const km::core::kmx::COMP_KMXPLUS_STRS &strs) const; + std::deque loadAsStringList(KMX_DWORD length, const km::core::kmx::COMP_KMXPLUS_STRS_Helper &strs) const; /** @return element type */ KMX_DWORD type() const; @@ -132,25 +218,41 @@ struct COMP_KMXPLUS_ELEM_ENTRY { struct COMP_KMXPLUS_ELEM { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_ELEM; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned count; // 0008 count of str entries COMP_KMXPLUS_ELEM_ENTRY entries[]; // 000C+ entries /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; + /** * @param elementNumber element number, 0..count-1 * @param length fillin: length of list * @return pointer to first element of list of length length. or nullptr */ - const COMP_KMXPLUS_ELEM_ELEMENT *getElementList(KMX_DWORD elementNumber, - KMX_DWORD &length) const; + const COMP_KMXPLUS_ELEM_ELEMENT *getElementList( + COMP_KMXPLUS_HEADER const &header, + KMX_DWORD elementNumber, + KMX_DWORD &length + ) const; }; static_assert(sizeof(struct COMP_KMXPLUS_ELEM) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_ELEM) == LDML_LENGTH_ELEM, "mismatched size of section elem"); +static_assert(sizeof(struct COMP_KMXPLUS_ELEM) == LDML_LENGTH_ELEM - LDML_LENGTH_HEADER_17, "mismatched size of section elem"); + +class COMP_KMXPLUS_ELEM_Helper : public COMP_KMXPLUS_Section_Helper { +public: + /** + * @param elementNumber element number, 0..count-1 + * @param length fillin: length of list + * @return pointer to first element of list of length length. or nullptr + */ + const COMP_KMXPLUS_ELEM_ELEMENT *getElementList( + KMX_DWORD elementNumber, + KMX_DWORD &length + ) const; +}; /* ------------------------------------------------------------------ * finl section is no more @@ -170,17 +272,18 @@ struct COMP_KMXPLUS_LOCA_ENTRY { struct COMP_KMXPLUS_LOCA { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_LOCA; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned count; // 0008 number of locales COMP_KMXPLUS_LOCA_ENTRY entries[]; /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; static_assert(sizeof(struct COMP_KMXPLUS_LOCA) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_LOCA) == LDML_LENGTH_LOCA, "mismatched size of section loca"); +static_assert(sizeof(struct COMP_KMXPLUS_LOCA) == LDML_LENGTH_LOCA - LDML_LENGTH_HEADER_17, "mismatched size of section loca"); + +class COMP_KMXPLUS_LOCA_Helper : public COMP_KMXPLUS_Section_Helper {}; /* ------------------------------------------------------------------ * meta section @@ -188,7 +291,6 @@ static_assert(sizeof(struct COMP_KMXPLUS_LOCA) == LDML_LENGTH_LOCA, "mismatched struct COMP_KMXPLUS_META { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_META; - COMP_KMXPLUS_HEADER header; KMXPLUS_STR author; KMXPLUS_STR conform; KMXPLUS_STR layout; @@ -199,7 +301,7 @@ struct COMP_KMXPLUS_META { /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; /** @brief True if normalization disabled*/ bool normalization_disabled() const { @@ -207,7 +309,9 @@ struct COMP_KMXPLUS_META { } }; -static_assert(sizeof(struct COMP_KMXPLUS_META) == LDML_LENGTH_META, "mismatched size of section meta"); +static_assert(sizeof(struct COMP_KMXPLUS_META) == LDML_LENGTH_META - LDML_LENGTH_HEADER_17, "mismatched size of section meta"); + +class COMP_KMXPLUS_META_Helper : public COMP_KMXPLUS_Section_Helper {}; /* ------------------------------------------------------------------ * strs section @@ -220,7 +324,6 @@ struct COMP_KMXPLUS_STRS_ENTRY { struct COMP_KMXPLUS_STRS { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_STRS; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned count; // 0008 count of str entries COMP_KMXPLUS_STRS_ENTRY entries[]; // 0010+ entries @@ -232,16 +335,16 @@ struct COMP_KMXPLUS_STRS { * @param bufsiz buffer size in bytes * @return nullptr or a pointer to the output buffer */ - std::u16string get(KMX_DWORD entry) const; + std::u16string get(const COMP_KMXPLUS_HEADER& header, KMX_DWORD entry) const; /** * Slow search */ - KMX_DWORD find(const std::u16string&) const; + KMX_DWORD find(const COMP_KMXPLUS_HEADER& header, const std::u16string&) const; /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(const COMP_KMXPLUS_HEADER& header, KMX_DWORD length) const; /** convert a single char to a string*/ static std::u16string str_from_char(KMX_DWORD v); @@ -250,7 +353,13 @@ struct COMP_KMXPLUS_STRS { }; static_assert(sizeof(struct COMP_KMXPLUS_STRS) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_STRS) == LDML_LENGTH_STRS, "mismatched size of section strs"); +static_assert(sizeof(struct COMP_KMXPLUS_STRS) == LDML_LENGTH_STRS - LDML_LENGTH_HEADER_17, "mismatched size of section strs"); + +class COMP_KMXPLUS_STRS_Helper : public COMP_KMXPLUS_Section_Helper { +public: + std::u16string get(KMX_DWORD entry) const; + KMX_DWORD find(const std::u16string&) const; +}; /* ------------------------------------------------------------------ * tran section @@ -276,7 +385,6 @@ struct COMP_KMXPLUS_TRAN_REORDER { struct COMP_KMXPLUS_TRAN { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_TRAN; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned groupCount; KMX_DWORD_unaligned transformCount; KMX_DWORD_unaligned reorderCount; @@ -287,34 +395,32 @@ struct COMP_KMXPLUS_TRAN { /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; -class COMP_KMXPLUS_TRAN_Helper { +class COMP_KMXPLUS_TRAN_Helper : public COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_TRAN_Helper(); /** - * Initialize the helper to point at a layr section. + * Initialize the helper to point at a tran or bksp section. * @return true if valid */ - bool setTran(const COMP_KMXPLUS_TRAN *newTran); - bool valid() const; + virtual bool set(const COMP_KMXPLUS_TRAN *section); + virtual bool valid() const; const COMP_KMXPLUS_TRAN_GROUP *getGroup(KMX_DWORD n) const; const COMP_KMXPLUS_TRAN_TRANSFORM *getTransform(KMX_DWORD n) const; const COMP_KMXPLUS_TRAN_REORDER *getReorder(KMX_DWORD n) const; private: - const COMP_KMXPLUS_TRAN *tran; bool is_valid; const COMP_KMXPLUS_TRAN_GROUP *groups; const COMP_KMXPLUS_TRAN_TRANSFORM *transforms; const COMP_KMXPLUS_TRAN_REORDER *reorders; }; - static_assert(sizeof(struct COMP_KMXPLUS_TRAN) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_TRAN) == LDML_LENGTH_TRAN, "mismatched size of section tran"); +static_assert(sizeof(struct COMP_KMXPLUS_TRAN) == LDML_LENGTH_TRAN - LDML_LENGTH_HEADER_17, "mismatched size of section tran"); static_assert(sizeof(struct COMP_KMXPLUS_TRAN_GROUP) == LDML_LENGTH_TRAN_GROUP, "mismatched size of tran group"); static_assert(sizeof(struct COMP_KMXPLUS_TRAN_TRANSFORM) == LDML_LENGTH_TRAN_TRANSFORM, "mismatched size of tran transform"); static_assert(sizeof(struct COMP_KMXPLUS_TRAN_REORDER) == LDML_LENGTH_TRAN_REORDER, "mismatched size of tran reorder"); @@ -333,12 +439,11 @@ static inline bool is_valid_marker(KMX_DWORD marker_no) { * bksp section ------------------------------------------------------------------ */ -typedef COMP_KMXPLUS_TRAN_Helper COMP_KMXPLUS_BKSP_Helper; - struct COMP_KMXPLUS_BKSP : public COMP_KMXPLUS_TRAN { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_BKSP; }; +typedef COMP_KMXPLUS_TRAN_Helper COMP_KMXPLUS_BKSP_Helper; /* ------------------------------------------------------------------ * vars section @@ -353,22 +458,23 @@ struct COMP_KMXPLUS_VARS_ITEM { struct COMP_KMXPLUS_VARS { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_VARS; - COMP_KMXPLUS_HEADER header; KMXPLUS_LIST markers; KMX_DWORD_unaligned varCount; COMP_KMXPLUS_VARS_ITEM varEntries[]; /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; const COMP_KMXPLUS_VARS_ITEM *findByStringId(KMX_DWORD strId) const; }; static_assert(sizeof(struct COMP_KMXPLUS_VARS) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_VARS) == LDML_LENGTH_VARS, "mismatched size of section vars"); +static_assert(sizeof(struct COMP_KMXPLUS_VARS) == LDML_LENGTH_VARS - LDML_LENGTH_HEADER_17, "mismatched size of section vars"); static_assert(sizeof(struct COMP_KMXPLUS_VARS_ITEM) == LDML_LENGTH_VARS_ITEM, "mismatched size of vars item"); +class COMP_KMXPLUS_VARS_Helper : public COMP_KMXPLUS_Section_Helper {}; + /* ------------------------------------------------------------------ * disp section ------------------------------------------------------------------ */ @@ -381,20 +487,19 @@ struct COMP_KMXPLUS_DISP_ENTRY { struct COMP_KMXPLUS_DISP { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_DISP; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned count; KMXPLUS_STR baseCharacter; COMP_KMXPLUS_DISP_ENTRY entries[]; /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; static_assert(sizeof(struct COMP_KMXPLUS_DISP) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_DISP) == LDML_LENGTH_DISP, "mismatched size of section disp"); - +static_assert(sizeof(struct COMP_KMXPLUS_DISP) == LDML_LENGTH_DISP - LDML_LENGTH_HEADER_17, "mismatched size of section disp"); +class COMP_KMXPLUS_DISP_Helper : public COMP_KMXPLUS_Section_Helper {}; /* ------------------------------------------------------------------ * layr section @@ -434,7 +539,6 @@ static_assert(sizeof(struct COMP_KMXPLUS_LAYR_KEY) == LDML_LENGTH_LAYR_KEY, "mis struct COMP_KMXPLUS_LAYR { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_LAYR; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned listCount; KMX_DWORD_unaligned layerCount; KMX_DWORD_unaligned rowCount; @@ -449,21 +553,21 @@ struct COMP_KMXPLUS_LAYR { /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; /** * Helper accessor for the dynamic part of a layr section. */ -class COMP_KMXPLUS_LAYR_Helper { +class COMP_KMXPLUS_LAYR_Helper : public COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_LAYR_Helper(); /** * Initialize the helper to point at a layr section. * @return true if valid */ - bool setLayr(const COMP_KMXPLUS_LAYR *newLayr); - bool valid() const; + virtual bool set(const COMP_KMXPLUS_LAYR *newLayr); + virtual bool valid() const; /** * @param list index from 0 to layr->listCount @@ -477,7 +581,6 @@ class COMP_KMXPLUS_LAYR_Helper { const COMP_KMXPLUS_LAYR_KEY *getKey(KMX_DWORD key) const; private: - const COMP_KMXPLUS_LAYR *layr; bool is_valid; const COMP_KMXPLUS_LAYR_LIST *lists; const COMP_KMXPLUS_LAYR_ENTRY *entries; @@ -486,14 +589,13 @@ class COMP_KMXPLUS_LAYR_Helper { }; static_assert(sizeof(struct COMP_KMXPLUS_LAYR) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_LAYR) == LDML_LENGTH_LAYR, "mismatched size of section layr"); +static_assert(sizeof(struct COMP_KMXPLUS_LAYR) == LDML_LENGTH_LAYR - LDML_LENGTH_HEADER_17, "mismatched size of section layr"); /* ------------------------------------------------------------------ * key2 section ------------------------------------------------------------------ */ struct COMP_KMXPLUS_KEYS { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_KEYS; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned keyCount; KMX_DWORD_unaligned flicksCount; KMX_DWORD_unaligned flickCount; @@ -506,7 +608,7 @@ struct COMP_KMXPLUS_KEYS { /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; struct COMP_KMXPLUS_KEYS_FLICK_ELEMENT { @@ -541,15 +643,15 @@ struct COMP_KMXPLUS_KEYS_KMAP { KMX_DWORD_unaligned key; // index into key subtable }; -class COMP_KMXPLUS_KEYS_Helper { +class COMP_KMXPLUS_KEYS_Helper : public COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_KEYS_Helper(); /** * Initialize the helper to point at a layr section. * @return true if valid */ - bool setKeys(const COMP_KMXPLUS_KEYS *newKeys); - inline bool valid() const { return is_valid; } + virtual bool set(const COMP_KMXPLUS_KEYS *newKeys); + virtual bool valid() const { return is_valid; } const COMP_KMXPLUS_KEYS_KEY *getKeys(KMX_DWORD key) const; const COMP_KMXPLUS_KEYS_FLICK_LIST *getFlickLists(KMX_DWORD list) const; @@ -573,7 +675,6 @@ class COMP_KMXPLUS_KEYS_Helper { const COMP_KMXPLUS_KEYS_KEY *findKeyByStringTo(const std::u16string& str, KMX_DWORD strId, KMX_DWORD &index) const; private: - const COMP_KMXPLUS_KEYS *key2; bool is_valid; const COMP_KMXPLUS_KEYS_KEY *keys; const COMP_KMXPLUS_KEYS_FLICK_LIST *flickLists; @@ -586,14 +687,13 @@ static_assert(sizeof(struct COMP_KMXPLUS_KEYS_FLICK_ELEMENT) == LDML_LENGTH_KEYS static_assert(sizeof(struct COMP_KMXPLUS_KEYS_FLICK_LIST) == LDML_LENGTH_KEYS_FLICK_LIST, "mismatched size of key2.flicks"); static_assert(sizeof(struct COMP_KMXPLUS_KEYS_KMAP) == LDML_LENGTH_KEYS_KMAP, "mismatched size of key2.kmap"); static_assert(sizeof(struct COMP_KMXPLUS_KEYS) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_KEYS) == LDML_LENGTH_KEYS, "mismatched size of section key2"); +static_assert(sizeof(struct COMP_KMXPLUS_KEYS) == LDML_LENGTH_KEYS - LDML_LENGTH_HEADER_17, "mismatched size of section key2"); /* ------------------------------------------------------------------ * list section ------------------------------------------------------------------ */ struct COMP_KMXPLUS_LIST { static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_LIST; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned listCount; KMX_DWORD_unaligned indexCount; // see helper for: lists sub-table @@ -602,7 +702,7 @@ struct COMP_KMXPLUS_LIST { /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; /** @@ -619,21 +719,20 @@ struct COMP_KMXPLUS_LIST_ITEM { typedef KMX_DWORD_unaligned COMP_KMXPLUS_LIST_INDEX; -class COMP_KMXPLUS_LIST_Helper { +class COMP_KMXPLUS_LIST_Helper : public COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_LIST_Helper(); /** * Initialize the helper to point at a layr section. * @return true if valid */ - bool setList(const COMP_KMXPLUS_LIST *newList); - inline bool valid() const { return is_valid; } + virtual bool set(const COMP_KMXPLUS_LIST *newList); + virtual bool valid() const { return is_valid; } const COMP_KMXPLUS_LIST_ITEM *getList(KMX_DWORD list) const; const COMP_KMXPLUS_LIST_INDEX *getIndex(KMX_DWORD index) const; private: - const COMP_KMXPLUS_LIST *list; bool is_valid; const COMP_KMXPLUS_LIST_ITEM *lists; const COMP_KMXPLUS_LIST_INDEX *indices; @@ -641,7 +740,7 @@ class COMP_KMXPLUS_LIST_Helper { static_assert(sizeof(struct COMP_KMXPLUS_LIST) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_LIST) == LDML_LENGTH_LIST, "mismatched size of section list"); +static_assert(sizeof(struct COMP_KMXPLUS_LIST) == LDML_LENGTH_LIST - LDML_LENGTH_HEADER_17, "mismatched size of section list"); static_assert(sizeof(struct COMP_KMXPLUS_LIST_ITEM) == LDML_LENGTH_LIST_ITEM, "mismatched size of section list.lists subtable"); static_assert(sizeof(COMP_KMXPLUS_LIST_INDEX) == LDML_LENGTH_LIST_INDEX, "mismatched size of section list.indices subtable"); @@ -657,7 +756,6 @@ typedef KMX_DWORD KMXPLUS_USET; struct COMP_KMXPLUS_USET { static const KMX_DWORD IDENT = LDML_SECTIONID_USET; - COMP_KMXPLUS_HEADER header; KMX_DWORD_unaligned usetCount; KMX_DWORD_unaligned rangeCount; // see helper for: usets sub-table @@ -666,7 +764,7 @@ struct COMP_KMXPLUS_USET { /** * @brief True if section is valid. */ - bool valid(KMX_DWORD length) const; + bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; /** @@ -706,28 +804,27 @@ class SimpleUSet { std::list ranges; }; -class COMP_KMXPLUS_USET_Helper { +class COMP_KMXPLUS_USET_Helper: public COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_USET_Helper(); /** * Initialize the helper to point at a uset section. * @return true if valid */ - bool setUset(const COMP_KMXPLUS_USET *newUset); - inline bool valid() const { return is_valid; } + virtual bool set(const COMP_KMXPLUS_USET *newUset); + virtual bool valid() const { return is_valid; } SimpleUSet getUset(KMXPLUS_USET list) const; const COMP_KMXPLUS_USET_RANGE *getRange(KMX_DWORD index) const; private: - const COMP_KMXPLUS_USET *uset; bool is_valid; const COMP_KMXPLUS_USET_USET *usets; const COMP_KMXPLUS_USET_RANGE *ranges; }; static_assert(sizeof(struct COMP_KMXPLUS_USET) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_USET) == LDML_LENGTH_USET, "mismatched size of section uset"); +static_assert(sizeof(struct COMP_KMXPLUS_USET) == LDML_LENGTH_USET - LDML_LENGTH_HEADER_17, "mismatched size of section uset"); static_assert(sizeof(struct COMP_KMXPLUS_USET_RANGE) == LDML_LENGTH_USET_RANGE, "mismatched size of section uset.ranges subtable"); static_assert(sizeof(struct COMP_KMXPLUS_USET_USET) == LDML_LENGTH_USET_USET, "mismatched size of section uset.usets subtable"); @@ -747,26 +844,21 @@ class kmx_plus { */ kmx_plus(const COMP_KEYBOARD *keyboard, size_t length); // keep the next elements sorted - const COMP_KMXPLUS_BKSP *bksp; - const COMP_KMXPLUS_DISP *disp; - const COMP_KMXPLUS_ELEM *elem; - const COMP_KMXPLUS_KEYS *key2; - const COMP_KMXPLUS_LAYR *layr; - const COMP_KMXPLUS_LIST *list; - const COMP_KMXPLUS_LOCA *loca; - const COMP_KMXPLUS_META *meta; - const COMP_KMXPLUS_SECT *sect; - const COMP_KMXPLUS_STRS *strs; - const COMP_KMXPLUS_TRAN *tran; - const COMP_KMXPLUS_USET *uset; - const COMP_KMXPLUS_VARS *vars; + // TODO: put data inside the helper only, rename + const COMP_KMXPLUS_BKSP *bksp; COMP_KMXPLUS_TRAN_Helper bkspHelper; + const COMP_KMXPLUS_DISP *disp; COMP_KMXPLUS_DISP_Helper dispHelper; + const COMP_KMXPLUS_ELEM *elem; COMP_KMXPLUS_ELEM_Helper elemHelper; + const COMP_KMXPLUS_KEYS *key2; COMP_KMXPLUS_KEYS_Helper key2Helper; + const COMP_KMXPLUS_LAYR *layr; COMP_KMXPLUS_LAYR_Helper layrHelper; + const COMP_KMXPLUS_LIST *list; COMP_KMXPLUS_LIST_Helper listHelper; + const COMP_KMXPLUS_LOCA *loca; COMP_KMXPLUS_LOCA_Helper locaHelper; + const COMP_KMXPLUS_META *meta; COMP_KMXPLUS_META_Helper metaHelper; + const COMP_KMXPLUS_SECT *sect; COMP_KMXPLUS_SECT_Helper sectHelper; + const COMP_KMXPLUS_STRS *strs; COMP_KMXPLUS_STRS_Helper strsHelper; + const COMP_KMXPLUS_TRAN *tran; COMP_KMXPLUS_TRAN_Helper tranHelper; + const COMP_KMXPLUS_USET *uset; COMP_KMXPLUS_USET_Helper usetHelper; + const COMP_KMXPLUS_VARS *vars; COMP_KMXPLUS_VARS_Helper varsHelper; inline bool is_valid() { return valid; } - COMP_KMXPLUS_BKSP_Helper bkspHelper; - COMP_KMXPLUS_KEYS_Helper key2Helper; - COMP_KMXPLUS_LAYR_Helper layrHelper; - COMP_KMXPLUS_LIST_Helper listHelper; - COMP_KMXPLUS_TRAN_Helper tranHelper; - COMP_KMXPLUS_USET_Helper usetHelper; private: bool valid; // true if valid }; diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp index 63e7995273c..93342697cf3 100644 --- a/core/src/ldml/ldml_processor.cpp +++ b/core/src/ldml/ldml_processor.cpp @@ -77,7 +77,7 @@ ldml_processor::ldml_processor(std::u16string const& kb_name, const std::vector< assert(false); return; } - str = kplus.strs->get(keyEntry->to); + str = kplus.strsHelper.get(keyEntry->to); } else { str = keyEntry->get_to_string(); } diff --git a/core/src/ldml/ldml_transforms.cpp b/core/src/ldml/ldml_transforms.cpp index 8e37011bda4..ca729a92487 100644 --- a/core/src/ldml/ldml_transforms.cpp +++ b/core/src/ldml/ldml_transforms.cpp @@ -180,7 +180,7 @@ element_list::match_end(const std::u32string &str) const { bool element_list::load(const kmx::kmx_plus &kplus, kmx::KMXPLUS_ELEM id) { KMX_DWORD elementsLength; - auto elements = kplus.elem->getElementList(id, elementsLength); // pointer to beginning of element list + auto elements = kplus.elemHelper.getElementList(id, elementsLength); // pointer to beginning of element list assert((elementsLength == 0) || (elements != nullptr)); // it could be a 0-length list for (size_t i = 0; itype == LDML_VARS_ENTRY_TYPE_SET); assert(toVar->type == LDML_VARS_ENTRY_TYPE_SET); KMX_DWORD fromLength, toLength; - auto *fromList = kplus.elem->getElementList(fromVar->elem, fromLength); - auto *toList = kplus.elem->getElementList(toVar->elem, toLength); + auto *fromList = kplus.elemHelper.getElementList(fromVar->elem, fromLength); + auto *toList = kplus.elemHelper.getElementList(toVar->elem, toLength); assert(fromLength == toLength); assert(fromList != nullptr); assert(toList != nullptr); // populate the deques from the lists - fMapFromList = fromList->loadAsStringList(fromLength, *(kplus.strs)); - fMapToList = toList->loadAsStringList(toLength, *(kplus.strs)); + fMapFromList = fromList->loadAsStringList(fromLength, kplus.strsHelper); + fMapToList = toList->loadAsStringList(toLength, kplus.strsHelper); // did we get the expected items? assert(fMapFromList.size() == fromLength); assert(fMapToList.size() == toLength); @@ -743,8 +743,8 @@ transforms::load( for (KMX_DWORD itemNumber = 0; itemNumber < group->count; itemNumber++) { const kmx::COMP_KMXPLUS_TRAN_TRANSFORM *element = tranHelper.getTransform(group->index + itemNumber); - const std::u32string fromStr = kmx::u16string_to_u32string(kplus.strs->get(element->from)); - const std::u32string toStr = kmx::u16string_to_u32string(kplus.strs->get(element->to)); + const std::u32string fromStr = kmx::u16string_to_u32string(kplus.strsHelper.get(element->from)); + const std::u32string toStr = kmx::u16string_to_u32string(kplus.strsHelper.get(element->to)); KMX_DWORD mapFrom = element->mapFrom; // copy, because of alignment KMX_DWORD mapTo = element->mapTo; // copy, because of alignment assert(!fromStr.empty()); diff --git a/core/tests/unit/ldml/kmx_plus.tests.cpp b/core/tests/unit/ldml/kmx_plus.tests.cpp index c0643a5d600..9c705a5ba4f 100644 --- a/core/tests/unit/ldml/kmx_plus.tests.cpp +++ b/core/tests/unit/ldml/kmx_plus.tests.cpp @@ -12,6 +12,16 @@ using namespace km::core::kmx; +namespace km { + namespace core { + namespace kmx { + + // Declarations of helper functions + extern bool header_from_bytes(const uint8_t *data, KMX_DWORD length, KMX_DWORD fileVersion, uint32_t ident, COMP_KMXPLUS_HEADER &out); + } + } +} + TEST(KMXPlusTest, test_COMP_KMXPLUS_KEYS_KEY) { COMP_KMXPLUS_KEYS_KEY e[2] = { { @@ -282,18 +292,26 @@ TEST(KMXPlusTest, COMP_KMXPLUS_STRS_withGoodStrings) { 0x00000000, // null // data @ +7 - 0x0041, // 'a' + 0x0041, // 'A' 0x0000, // null }; - const COMP_KMXPLUS_HEADER *header = reinterpret_cast(mystrs); - ASSERT_TRUE(header->valid(mystrs[1])); - const COMP_KMXPLUS_STRS *strs = reinterpret_cast(mystrs); - ASSERT_TRUE(strs->valid(mystrs[1])); + + COMP_KMXPLUS_HEADER header; + ASSERT_TRUE(header_from_bytes(reinterpret_cast(&mystrs[0]), len * 4, LDML_KMXPLUS_VERSION_17, COMP_KMXPLUS_STRS::IDENT, header)); + + ASSERT_EQ(header.fileVersion(), LDML_KMXPLUS_VERSION_17); + ASSERT_EQ(header.headerSize(), LDML_LENGTH_HEADER_17); + ASSERT_EQ(header.ident, LDML_SECTIONID_STRS); + ASSERT_EQ(header.size, len * 4); + ASSERT_EQ(header.version, LDML_KMXPLUS_VERSION_17); + + const COMP_KMXPLUS_STRS *strs = reinterpret_cast(&mystrs[2]); + ASSERT_TRUE(strs->valid(header, mystrs[1])); KMX_DWORD_unaligned mycount = strs->count; ASSERT_EQ(mycount, 2); - ASSERT_EQ(strs->get(0), u""); - ASSERT_EQ(strs->get(1), u"A"); + ASSERT_EQ(strs->get(header, 0), u""); + ASSERT_EQ(strs->get(header, 1), u"A"); } @@ -320,14 +338,24 @@ TEST(KMXPlusTest, COMP_KMXPLUS_STRS_withBadStrings) { 0x0000, // null }; - const COMP_KMXPLUS_HEADER *header = reinterpret_cast(mystrs); - ASSERT_TRUE(header->valid(mystrs[1])); - const COMP_KMXPLUS_STRS *strs = reinterpret_cast(mystrs); - ASSERT_FALSE(strs->valid(mystrs[1])); + COMP_KMXPLUS_HEADER header; + ASSERT_TRUE(header_from_bytes(reinterpret_cast(mystrs), len * 4, LDML_KMXPLUS_VERSION_17, COMP_KMXPLUS_STRS::IDENT, header)); + + ASSERT_EQ(header.fileVersion(), LDML_KMXPLUS_VERSION_17); + ASSERT_EQ(header.headerSize(), LDML_LENGTH_HEADER_17); + ASSERT_EQ(header.ident, LDML_SECTIONID_STRS); + ASSERT_EQ(header.size, len * 4); + ASSERT_EQ(header.version, LDML_KMXPLUS_VERSION_17); + + const COMP_KMXPLUS_STRS *strs = reinterpret_cast(&mystrs[2]); + ASSERT_FALSE(strs->valid(header, mystrs[1])); } +extern KMX_BOOL km::core::kmx::g_debug_KeymanLog; + GTEST_API_ int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); + km::core::kmx::g_debug_KeymanLog = FALSE; return RUN_ALL_TESTS(); } diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp index 76b7a34a4a4..72094fa0d2f 100644 --- a/core/tests/unit/ldml/ldml_test_source.cpp +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -554,7 +554,7 @@ bool LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id) test_assert(kmxplus->key2Helper.valid()); // First, find the string - KMX_DWORD strId = kmxplus->strs->find(id); + KMX_DWORD strId = kmxplus->strsHelper.find(id); if (strId == 0) { return false; } @@ -744,7 +744,7 @@ LdmlJsonRepertoireTestSource::next_action(ldml_action &fillin) { // First, find the string as an id // TODO-LDML: will not work for multi string cases - KMX_DWORD strId = kmxplus->strs->find(chstr); // not an error if chstr is 0, may be single ch + KMX_DWORD strId = kmxplus->strsHelper.find(chstr); // not an error if chstr is 0, may be single ch // OK. Now we can search the keybag KMX_DWORD keyIndex = 0; diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts index 2ab11a10404..d18ef6e28c7 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts @@ -23,7 +23,7 @@ export function build_sect(version: KMXPlusVersion): BUILDER_SECT { return { header: { // v19+ uses ident "sec2" to indicate that we have a `version` header field - ident: constants.hex_section_id(version == KMXPlusVersion.Version17 ? constants.section.sect : constants.section_sec2), + ident: constants.hex_section_id(version == KMXPlusVersion.Version17 ? constants.section.sect : constants.sectionname_sec2), size: 0, // finalized later version, }, From 8d74d2c8a6c9e1a270a31df310b92db40adb2e29 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 21 Oct 2025 12:02:02 +0200 Subject: [PATCH 12/37] feat(developer): add targetVersion support to kmc and kmc-kmn Make the target version parameter for kmc-ldml into a generic build parameter, so we can use it for any compiler. Add support and tests to kmc-kmn, kmcmplib, kmc-ldml, kmc. Support not yet in place for lexical models, or packages. However, packages will be picking up the min version from the embedded keyboards. Lexical models need work on minimum version support before this can be implemented there. --- common/web/types/src/kmx/kmx.ts | 33 +++++ common/web/types/tests/kmx/kmx-file.tests.ts | 41 +++++- .../web/utils/src/compiler-interfaces.ts | 8 +- .../src/kmc-kmn/src/compiler/compiler.ts | 1 + developer/src/kmc-kmn/test/features.tests.ts | 52 ++++++-- .../test/fixtures/features/unversioned.kmn | 9 ++ .../test/fixtures/features/version_190.kmn | 8 ++ .../src/kmc-ldml/src/compiler/compiler.ts | 32 ++++- .../src/compiler/ldml-compiler-messages.ts | 6 + .../src/compiler/ldml-compiler-options.ts | 4 - developer/src/kmc/src/commands/build.ts | 1 + .../src/messages/infrastructureMessages.ts | 5 + .../kmc/src/util/extendedCompilerOptions.ts | 8 ++ developer/src/kmc/test/build.tests.ts | 122 ++++++++++++------ .../src/kmc/test/fixtures/ldml/basic.xml | 61 +++++++++ developer/src/kmcmplib/include/kmcmplibapi.h | 1 + .../kmcmplib/src/CompileKeyboardBuffer.cpp | 6 +- developer/src/kmcmplib/src/Compiler.cpp | 8 ++ .../src/kmcmplib/src/CompilerInterfaces.cpp | 1 + .../kmcmplib/src/CompilerInterfacesWasm.cpp | 1 + developer/src/kmcmplib/src/kmcmplib.h | 1 + developer/src/kmcmplib/tests/api.tests.cpp | 1 + .../kmcmplib/tests/gtest-compiler.tests.cpp | 2 + .../src/kmcmplib/tests/kmcompx.tests.cpp | 1 + 24 files changed, 353 insertions(+), 60 deletions(-) create mode 100644 developer/src/kmc-kmn/test/fixtures/features/unversioned.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/features/version_190.kmn create mode 100644 developer/src/kmc/test/fixtures/ldml/basic.xml diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts index 002d0a1f16d..e2f54f62a70 100644 --- a/common/web/types/src/kmx/kmx.ts +++ b/common/web/types/src/kmx/kmx.ts @@ -27,6 +27,39 @@ export enum KMX_Version { VERSION_190 = 0x00001300, }; +/** + * Convert a version string from 6.0 - current Keyman version into a + * KMX_Version value. Earlier versions are not supported + * @param version + * @returns null if not matched, otherwise a valid KMX_Version + */ +export function versionStringToKmxVersion(version: string): KMX_Version { + // We allow version strings to be 'x.y' or just 'x' + if(typeof version !== 'string') { + return null; + } + if(!/^\d+(\.0)?$/.test(version)) { + return null; + } + + const major = parseInt(version, 10); + if(Number.isNaN(major)) { + return null; + } + + // assuming a reasonable range for Keyman versions for now + if(major < 6 || major > 999) { + return null; + } + + const num = major << 8; + + if(Object.values(KMX_Version).includes(num)) { + return num; + } + + return null; +} export class KEYBOARD { fileVersion?: number; // dwFileVersion (TSS_FILEVERSION) diff --git a/common/web/types/tests/kmx/kmx-file.tests.ts b/common/web/types/tests/kmx/kmx-file.tests.ts index b50521427c6..ce6fafb56db 100644 --- a/common/web/types/tests/kmx/kmx-file.tests.ts +++ b/common/web/types/tests/kmx/kmx-file.tests.ts @@ -3,7 +3,7 @@ import 'mocha'; import { assert } from 'chai'; import { makePathToFixture } from '../helpers/index.js'; import { KmxFileReader } from "../../src/kmx/kmx-file-reader.js"; -import { KMXFile } from "../../src/kmx/kmx.js"; +import { KMX_Version, KMXFile, versionStringToKmxVersion } from "../../src/kmx/kmx.js"; describe('kmx-file-reader', function () { it('should read a valid file', function() { @@ -27,3 +27,42 @@ describe('kmx-file-reader', function () { // TODO: add header, group, key tests once we have added support in KmxFileReader }); }); + +describe('versionStringToKmxVersion', function() { + [ + // We only care about v6.0 and up these days + {s:'6.0', v:KMX_Version.VERSION_60}, + {s:'6', v:KMX_Version.VERSION_60}, + {s:'7.0', v:KMX_Version.VERSION_70}, + {s:'7', v:KMX_Version.VERSION_70}, + {s:'8.0', v:KMX_Version.VERSION_80}, + {s:'8', v:KMX_Version.VERSION_80}, + {s:'9.0', v:KMX_Version.VERSION_90}, + {s:'9', v:KMX_Version.VERSION_90}, + {s:'10.0', v:KMX_Version.VERSION_100}, + {s:'10', v:KMX_Version.VERSION_100}, + {s:'14.0', v:KMX_Version.VERSION_140}, + {s:'14', v:KMX_Version.VERSION_140}, + {s:'15.0', v:KMX_Version.VERSION_150}, + {s:'15', v:KMX_Version.VERSION_150}, + {s:'16.0', v:KMX_Version.VERSION_160}, + {s:'16', v:KMX_Version.VERSION_160}, + {s:'17.0', v:KMX_Version.VERSION_170}, + {s:'17', v:KMX_Version.VERSION_170}, + {s:'19.0', v:KMX_Version.VERSION_190}, + {s:'19', v:KMX_Version.VERSION_190}, + ].forEach(function(v) { + it(`should convert valid version string ${v.s}`, function() { + const actual = versionStringToKmxVersion(v.s); + assert.equal(actual, v.v); + }); + }); + + ['zero','six','VERSION_60','1.0','-6','','5.0.1','19.0-alpha',null,undefined,] + .forEach(function(v) { + it(`should reject invalid version string '${v}'`, function() { + const actual = versionStringToKmxVersion(v); + assert.isNull(actual); + }); + }); +}); \ No newline at end of file diff --git a/developer/src/common/web/utils/src/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts index 74231aaf2b8..861ba1774b4 100644 --- a/developer/src/common/web/utils/src/compiler-interfaces.ts +++ b/developer/src/common/web/utils/src/compiler-interfaces.ts @@ -1,5 +1,5 @@ import { CompilerCallbacks } from "./compiler-callbacks.js"; -import { ObjectWithCompileContext } from '@keymanapp/common-types'; +import { KMX, ObjectWithCompileContext } from '@keymanapp/common-types'; import { KeymanXMLReader, XML_FILENAME_SYMBOL } from "./xml-utils.js"; /** @@ -406,6 +406,11 @@ export interface CompilerOptions extends CompilerBaseOptions { * Check filename conventions in packages */ checkFilenameConventions?: boolean; + /** + * Target version of Keyman for compiled objects; default is minimum Keyman + * version that supports all features in the object + */ + targetVersion?: KMX.KMX_Version; }; export const defaultCompilerOptions: CompilerOptions = { @@ -417,6 +422,7 @@ export const defaultCompilerOptions: CompilerOptions = { compilerWarningsAsErrors: false, warnDeprecatedCode: true, checkFilenameConventions: false, + targetVersion: undefined, } /** diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 403f721f907..c85eddc1641 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -322,6 +322,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet wasm_options.compilerWarningsAsErrors = options.compilerWarningsAsErrors; wasm_options.warnDeprecatedCode = options.warnDeprecatedCode; wasm_options.shouldAddCompilerVersion = options.shouldAddCompilerVersion; + wasm_options.targetVersion = options.targetVersion ?? 0; wasm_options.target = 0; // CKF_KEYMAN; TODO use COMPILETARGETS_KMX wasm_result = Module.kmcmp_compile(infile, wasm_options, wasm_callbacks); diff --git a/developer/src/kmc-kmn/test/features.tests.ts b/developer/src/kmc-kmn/test/features.tests.ts index 1f82a03ed52..8a971093a37 100644 --- a/developer/src/kmc-kmn/test/features.tests.ts +++ b/developer/src/kmc-kmn/test/features.tests.ts @@ -1,39 +1,50 @@ import 'mocha'; import { assert } from 'chai'; -import { KmnCompiler } from '../src/main.js'; +import { KMX, KmxFileReader } from '@keymanapp/common-types'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { makePathToFixture } from './helpers/index.js'; -import { KMX, KmxFileReader } from '@keymanapp/common-types'; + +import { KmnCompiler } from '../src/main.js'; describe('Keyboard compiler features', function() { - let compiler: KmnCompiler = null; let callbacks: TestCompilerCallbacks = null; this.beforeAll(async function() { - compiler = new KmnCompiler(); callbacks = new TestCompilerCallbacks(); - assert(await compiler.init(callbacks, {saveDebug: true})); - assert(compiler.verifyInitialized()); }); - beforeEach(function() { + this.beforeEach(function() { callbacks.clear(); + + }); + + this.afterEach(function() { + if(this.currentTest.isFailed()) { + callbacks.printMessages(); + } }); + + // Test each Keyman file version target const versions = [ - // TODO(lowpri): we should add a test for each version + also test automatic feature detection + // TODO-EMBED-OSK-IN-KMX: we should add a test for each version + also test automatic feature detection + // and also verify that targetVersion works as expected with that {major: '16.0', vstr: '160', vernum: KMX.KMXFile.VERSION_160}, {major: '17.0', vstr: '170', vernum: KMX.KMXFile.VERSION_170}, + {major: '19.0', vstr: '190', vernum: KMX.KMXFile.VERSION_190}, ]; for(const v of versions) { it(`should build a version ${v.major} keyboard`, async function() { + const compiler = new KmnCompiler(); + assert.isTrue(await compiler.init(callbacks, {saveDebug: true})); + assert.isTrue(compiler.verifyInitialized()); + const fixtureName = makePathToFixture('features', `version_${v.vstr}.kmn`); const result = await compiler.run(fixtureName, `version_${v.vstr}.kmx`); - if(result === null) callbacks.printMessages(); assert.isNotNull(result); const reader = new KmxFileReader(); @@ -41,4 +52,27 @@ describe('Keyboard compiler features', function() { assert.equal(keyboard.fileVersion, v.vernum); }); } + + [ + {s:'VERSION_60', t:undefined, e:KMX.KMXFile.VERSION_60}, + {s:'VERSION_170', t:17, e:KMX.KMXFile.VERSION_170}, + {s:'VERSION_190', t:19, e:KMX.KMXFile.VERSION_190}, + ].forEach(function(v) { + it(`should build a minimum version keyboard to ${v.s} with targetVersion=${v.t}`, async function() { + const compiler = new KmnCompiler(); + assert.isTrue(await compiler.init(callbacks, { + saveDebug: true, + targetVersion: v.t, + })); + assert.isTrue(compiler.verifyInitialized()); + const fixtureName = makePathToFixture('features', `unversioned.kmn`); + + const result = await compiler.run(fixtureName, `unversioned.kmx`); + assert.isNotNull(result); + + const reader = new KmxFileReader(); + const keyboard = reader.read(result.artifacts.kmx.data); + assert.equal(keyboard.fileVersion, v.e); + }); + }); }); diff --git a/developer/src/kmc-kmn/test/fixtures/features/unversioned.kmn b/developer/src/kmc-kmn/test/fixtures/features/unversioned.kmn new file mode 100644 index 00000000000..9844ceaa3a3 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/features/unversioned.kmn @@ -0,0 +1,9 @@ +c Description: Verifies that kmcmplib can compile a keyboard +c without a version statement, that either picks up targetVersion, +c or uses the keyboard's features to determine minimum version. + +store(&NAME) 'unversioned' + +begin unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc-kmn/test/fixtures/features/version_190.kmn b/developer/src/kmc-kmn/test/fixtures/features/version_190.kmn new file mode 100644 index 00000000000..7883a2cda4a --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/features/version_190.kmn @@ -0,0 +1,8 @@ +c Description: Verifies that kmcmplib can compile a v19.0 keyboard + +store(&NAME) 'version_190' +store(&VERSION) '19.0' + +begin unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index d5fc7f97bba..be9ba5c5115 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -93,6 +93,7 @@ export interface LdmlKeyboardCompilerResult extends KeymanCompilerResult { export class LdmlKeyboardCompiler implements KeymanCompiler { private callbacks: CompilerCallbacks; private options: LdmlCompilerOptions; + private kmxPlusTargetVersion: KMXPlusVersion; // uset parser private usetparser?: LdmlKeyboardTypes.UnicodeSetParser = undefined; @@ -111,6 +112,14 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { this.reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, callbacks); // wrap the callbacks so that the eventresolver is called this.callbacks = new ResolvingCompilerCallbacks(this.reader, this.options, callbacks); + + // resolve and check command line parameters + + this.kmxPlusTargetVersion = this.targetVersionToKmxPlusVersion(this.options.targetVersion); + if(!this.kmxPlusTargetVersion) { + return false; + } + return true; } @@ -127,8 +136,6 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { */ async run(inputFilename: string, outputFilename?: string): Promise { - this.options.version = this.options.version ?? KMXPlusVersion.Version17; - const compilerOptions: LdmlCompilerOptions = { ...defaultCompilerOptions, ...this.options, @@ -167,7 +174,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { }); } - const kmxBinary = kmxBuilder.compile(this.options.version); + const kmxBinary = kmxBuilder.compile(this.kmxPlusTargetVersion); const kvkWriter = new KvkFileWriter(); const kvkBinary = vkData ? kvkWriter.write(vkData) : null; @@ -360,13 +367,11 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { */ public async compile(source: LDMLKeyboardXMLSourceFile, postValidate?: boolean): Promise { // TODO-EMBED-OSK-IN-KMX: add a unitTestEndpoints prop and make this private - // unit tests may not have set a version number; so do it again here - this.options.version = this.options.version ?? KMXPlusVersion.Version17; const sections = this.buildSections(source); let passed = true; - const kmx = new KMXPlusFile(this.options.version); + const kmx = new KMXPlusFile(this.kmxPlusTargetVersion); for (const section of sections) { if (!section.validate()) { @@ -430,5 +435,20 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { return passed ? kmx : null; } + + private targetVersionToKmxPlusVersion(version?: KMX.KMX_Version): KMXPlusVersion { + if(version === undefined || version === null) { + return KMXPlusVersion.Version17; + } + if(version === KMX.KMX_Version.VERSION_170) { + return KMXPlusVersion.Version17; + } + if(version === KMX.KMX_Version.VERSION_190) { + return KMXPlusVersion.Version19; + } + + this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidTargetVersion({ version })); + return null; + } } diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts index 1f879f475b9..b947f29f649 100644 --- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts +++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts @@ -292,6 +292,12 @@ export class LdmlCompilerMessages { `**Hint**: Use "${def(o.recommended)}"`, ); + static ERROR_InvalidTargetVersion = SevError | 0x0031; + static Error_InvalidTargetVersion = (o: {version: number}) => m( + this.ERROR_InvalidTargetVersion, + `Target version ${def(o.version)} is not a valid version. Only 17.0 and 19.0 target versions are currently supported for LDML keyboards."`, + ); + // // Transform syntax errors begin at ...F00 (SevErrorTransform) diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts index f2b920389cb..1b4f72113c1 100644 --- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts +++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts @@ -1,7 +1,4 @@ import { CompilerOptions, LDMLKeyboardXMLSourceFileReaderOptions } from "@keymanapp/developer-utils"; -import { KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; - -export type LdmlKeyboardVersion = KMXPlusVersion; /** * @public @@ -12,5 +9,4 @@ export interface LdmlCompilerOptions extends CompilerOptions { * Paths and other options required for reading .xml files */ readerOptions: LDMLKeyboardXMLSourceFileReaderOptions; - version?: LdmlKeyboardVersion; // used in v19+ }; diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 20f1092a9fd..a23181354c3 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -27,6 +27,7 @@ export function declareBuild(program: Command) { .option('-W, --no-compiler-warnings-as-errors', 'Warnings do not fail the build; overrides project-level warnings-as-errors option') .option('-m, --message ', 'Adjust severity of info, hint or warning message to Disable (default), Info, Hint, Warn or Error (option can be repeated)', (value, previous) => previous.concat([value]), []) + .option('--target-version ', 'Target version of Keyman for compiled objects (default is minimum version that supports all features in the object)') .option('--no-compiler-version', 'Exclude compiler version metadata from output') .option('--no-warn-deprecated-code', 'Turn off warnings for deprecated code styles'); diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index d384aa7d866..373736fbd34 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -197,5 +197,10 @@ export class InfrastructureMessages { `Failed to generate new project '${def(o.id)}'.`, )}); + static ERROR_InvalidTargetVersion = SevError | 0x0029; + static Error_InvalidTargetVersion = (o:{targetVersion:string}) => m( + this.ERROR_InvalidTargetVersion, + `Target version parameter '${def(o.targetVersion)}' is not a valid Keyman version.`, + ); } diff --git a/developer/src/kmc/src/util/extendedCompilerOptions.ts b/developer/src/kmc/src/util/extendedCompilerOptions.ts index 53f68288f04..8dd3eed9c8a 100644 --- a/developer/src/kmc/src/util/extendedCompilerOptions.ts +++ b/developer/src/kmc/src/util/extendedCompilerOptions.ts @@ -1,6 +1,7 @@ import { CompilerBaseOptions, CompilerCallbacks, CompilerError, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageOverride, CompilerMessageOverrideMap, CompilerOptions } from '@keymanapp/developer-utils'; import { InfrastructureMessages } from '../messages/infrastructureMessages.js'; import { CompilerMessageSource, messageNamespaceKeys, messageSources } from '../messages/messageNamespaces.js'; +import { KMX } from '@keymanapp/common-types'; export interface ExtendedCompilerOptions extends CompilerOptions { /** @@ -186,6 +187,12 @@ export function commanderOptionsToCompilerOptions(options: any, callbacks: Compi return null; } + const targetVersion = options.targetVersion ? KMX.versionStringToKmxVersion(options.targetVersion) : undefined; + if(targetVersion === null) { + callbacks.reportMessage(InfrastructureMessages.Error_InvalidTargetVersion({targetVersion: options.targetVersion})); + return null; + } + // We don't want to rename command line options to match the precise // properties that we have in CompilerOptions, but nor do we want to rename // CompilerOptions properties... @@ -196,6 +203,7 @@ export function commanderOptionsToCompilerOptions(options: any, callbacks: Compi saveDebug: options.debug, compilerWarningsAsErrors: options.compilerWarningsAsErrors, warnDeprecatedCode: options.warnDeprecatedCode, + targetVersion, // ExtendedOptions forPublishing: options.forPublishing, messageOverrides: overrides, diff --git a/developer/src/kmc/test/build.tests.ts b/developer/src/kmc/test/build.tests.ts index f8356db54b6..3828c1f2e3c 100644 --- a/developer/src/kmc/test/build.tests.ts +++ b/developer/src/kmc/test/build.tests.ts @@ -1,11 +1,17 @@ -import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; -import { clearOptions } from '../src/util/options.js'; -import { assert } from 'chai'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; import 'mocha'; -import { BuildProject } from '../src/commands/buildClasses/BuildProject.js'; +import { assert } from 'chai'; + +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { makePathToFixture } from './helpers/index.js'; -const callbacks = new TestCompilerCallbacks(); +import { clearOptions } from '../src/util/options.js'; +import { BuildProject } from '../src/commands/buildClasses/BuildProject.js'; +import { BuildLdmlKeyboard } from '../src/commands/buildClasses/BuildLdmlKeyboard.js'; +import { KMX, KmxFileReader } from '@keymanapp/common-types'; +import { LdmlCompilerMessages } from '@keymanapp/kmc-ldml'; interface CompilerWarningsAsErrorsTruthTable { cli: boolean; @@ -13,42 +19,84 @@ interface CompilerWarningsAsErrorsTruthTable { result: boolean; }; -describe('compilerWarningsAsErrors', function () { - beforeEach(() => { +describe('build', function() { + const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(); + let outPath: string = null; + + this.beforeEach(function() { callbacks.clear(); clearOptions(); + outPath = fs.mkdtempSync(path.join(os.tmpdir(), 'kmc-')); + }); + + this.afterEach(function() { + if(this.currentTest.isFailed()) { + callbacks.printMessages(); + console.error(`Output kept at ${outPath}`); + } else { + if(outPath) fs.rmSync(outPath, {recursive: true, force: true}); + } + outPath = null; }); - // The CLI option should override the project setting - - const truthTable: CompilerWarningsAsErrorsTruthTable[] = [ - {cli:undefined, kpj:undefined, result:true}, // default setting (false) wins - {cli:undefined, kpj:false, result:true}, // kpj setting wins - {cli:undefined, kpj:true, result:false}, // kpj setting wins - {cli:false, kpj:undefined, result:true}, // cli setting wins - {cli:false, kpj:false, result:true}, // cli setting wins - {cli:false, kpj:true, result:true}, // cli setting wins - {cli:true, kpj:undefined, result:false}, // cli setting wins - {cli:true, kpj:false, result:false}, // cli setting wins - {cli:true, kpj:true, result:false}, // cli setting wins - ] - - for(const truth of truthTable) { - it(`should ${truth.result?'':'fail to '}build a project when kpj option=${truth.kpj} and cli option=${truth.cli}`, async function() { - const builder = new BuildProject(); - const path = makePathToFixture('compiler-warnings-as-errors', - `compiler_warnings_as_errors_${truth.kpj === true ? 'true' : (truth.kpj === false ? 'false' : 'undefined')}.kpj`); - const result = await builder.build(path, null, callbacks, { - compilerWarningsAsErrors: truth.cli, + describe('compilerWarningsAsErrors', function () { + + // The CLI option should override the project setting + + const truthTable: CompilerWarningsAsErrorsTruthTable[] = [ + {cli:undefined, kpj:undefined, result:true}, // default setting (false) wins + {cli:undefined, kpj:false, result:true}, // kpj setting wins + {cli:undefined, kpj:true, result:false}, // kpj setting wins + {cli:false, kpj:undefined, result:true}, // cli setting wins + {cli:false, kpj:false, result:true}, // cli setting wins + {cli:false, kpj:true, result:true}, // cli setting wins + {cli:true, kpj:undefined, result:false}, // cli setting wins + {cli:true, kpj:false, result:false}, // cli setting wins + {cli:true, kpj:true, result:false}, // cli setting wins + ] + + for(const truth of truthTable) { + it(`should ${truth.result?'':'fail to '}build a project when kpj option=${truth.kpj} and cli option=${truth.cli}`, async function() { + const builder = new BuildProject(); + const path = makePathToFixture('compiler-warnings-as-errors', + `compiler_warnings_as_errors_${truth.kpj === true ? 'true' : (truth.kpj === false ? 'false' : 'undefined')}.kpj`); + const result = await builder.build(path, null, callbacks, { + compilerWarningsAsErrors: truth.cli, + }); + if(truth.result) { + assert.isTrue(result); + } else { + assert.isFalse(result); + } }); - if(truth.result != result) { - callbacks.printMessages(); - } - if(truth.result) { + } + }); + + describe('targetVersion', function() { + [ + [17, KMX.KMXFile.VERSION_170], + [19, KMX.KMXFile.VERSION_190], + ].forEach(function(v) { + it(`should build a v${v[0]}.0 LDML keyboard`, async function() { + const builder = new BuildLdmlKeyboard(); + const infile = makePathToFixture('ldml', 'basic.xml'); + const outfile = path.join(outPath, 'basic.kmx'); + const result = await builder.build(infile, outfile, callbacks, { targetVersion: v[1] }); assert.isTrue(result); - } else { - assert.isFalse(result); - } + + const reader = new KmxFileReader(); + const keyboard = reader.read(callbacks.loadFile(outfile)); + assert.equal(keyboard.fileVersion, v[1]); + }); }); - } -}); + + it(`should error if an unsupported version is passed when compiling an LDML keyboard`, async function() { + const builder = new BuildLdmlKeyboard(); + const infile = makePathToFixture('ldml', 'basic.xml'); + const outfile = path.join(outPath, 'basic.kmx'); + const result = await builder.build(infile, outfile, callbacks, { targetVersion: KMX.KMX_Version.VERSION_70 }); + assert.isFalse(result); + assert.isTrue(callbacks.hasMessage(LdmlCompilerMessages.ERROR_InvalidTargetVersion)); + }); + }); +}); \ No newline at end of file diff --git a/developer/src/kmc/test/fixtures/ldml/basic.xml b/developer/src/kmc/test/fixtures/ldml/basic.xml new file mode 100644 index 00000000000..168ee0ef9a0 --- /dev/null +++ b/developer/src/kmc/test/fixtures/ldml/basic.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 536d4fff0d4..e4700ff0002 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -31,6 +31,7 @@ struct KMCMP_COMPILER_OPTIONS { bool warnDeprecatedCode; bool shouldAddCompilerVersion; int target; // CKF_KEYMAN, CKF_KEYMANWEB + int targetVersion; // 0 = unspecified, otherwise Keyman version }; // diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp index 155b1252589..d9e570c5cbe 100644 --- a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp @@ -31,7 +31,7 @@ bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) } fk->KeyboardID = 0; - fk->version = 0; + fk->version = kmcmp::TargetVersion; fk->dpStoreArray = NULL; fk->dpGroupArray = NULL; fk->cxStoreArray = 0; @@ -39,7 +39,9 @@ bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) fk->StartGroup[0] = fk->StartGroup[1] = -1; fk->szName[0] = 0; fk->szCopyright[0] = 0; - fk->dwFlags = KF_AUTOMATICVERSION; + // If the targetVersion option is passed in, then we don't use automatic + // versioning, and we ignore the &VERSION store + fk->dwFlags = kmcmp::TargetVersion == 0 ? KF_AUTOMATICVERSION : 0; fk->currentGroup = 0xFFFFFFFF; fk->currentStore = 0; fk->cxDeadKeyArray = 0; diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 099a5e69785..b8b1aa6cb8c 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -121,6 +121,7 @@ namespace kmcmp{ KMX_BOOL FOldCharPosMatching = FALSE; int CompileTarget; int BeginLine[4]; + int TargetVersion; KMX_BOOL IsValidCallStore(PFILE_STORE fs); void CheckStoreUsage(PFILE_KEYBOARD fk, int storeIndex, KMX_BOOL fIsStore, KMX_BOOL fIsOption, KMX_BOOL fIsCall); @@ -1132,6 +1133,12 @@ KMX_BOOL ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE s break; case TSS_VERSION: + if(kmcmp::TargetVersion != 0) { + // If the targetVersion option is passed in, then we don't use automatic + // versioning, and we ignore the &VERSION store + // TODO-EMBED-OSK-IN-KMX: consider a compiler hint? + return TRUE; + } if ((fk->dwFlags & KF_AUTOMATICVERSION) == 0) { ReportCompilerMessage(KmnCompilerMessages::ERROR_VersionAlreadyIncluded); return FALSE; @@ -1156,6 +1163,7 @@ KMX_BOOL ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE s else if (u16ncmp(p, u"15.0", 4) == 0) fk->version = VERSION_150; // Adds support for U_xxxx_yyyy #2858 else if (u16ncmp(p, u"16.0", 4) == 0) fk->version = VERSION_160; // KMXPlus else if (u16ncmp(p, u"17.0", 4) == 0) fk->version = VERSION_170; // Flicks and gestures + else if (u16ncmp(p, u"19.0", 4) == 0) fk->version = VERSION_190; // Embedded OSK else { ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidVersion); diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 0e424c93b00..b70e9a33b47 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -22,6 +22,7 @@ EXTERN bool kmcmp_CompileKeyboard( AWarnDeprecatedCode_GLOBAL_LIB = options.warnDeprecatedCode; kmcmp::FShouldAddCompilerVersion = options.shouldAddCompilerVersion; kmcmp::CompileTarget = options.target; + kmcmp::TargetVersion = options.targetVersion; if (!messageProc || !loadFileProc || !pszInfile) { ReportCompilerMessage(KmnCompilerMessages::FATAL_BadCallParams); diff --git a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp index 1ddd45dd3f8..9414b94c495 100644 --- a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp @@ -124,6 +124,7 @@ EMSCRIPTEN_BINDINGS(compiler_interface) { .property("warnDeprecatedCode", &KMCMP_COMPILER_OPTIONS::warnDeprecatedCode) .property("shouldAddCompilerVersion", &KMCMP_COMPILER_OPTIONS::shouldAddCompilerVersion) .property("target", &KMCMP_COMPILER_OPTIONS::target) + .property("targetVersion", &KMCMP_COMPILER_OPTIONS::targetVersion) ; emscripten::class_("CompilerResult") diff --git a/developer/src/kmcmplib/src/kmcmplib.h b/developer/src/kmcmplib/src/kmcmplib.h index 91edfe1b260..a485a0652c5 100644 --- a/developer/src/kmcmplib/src/kmcmplib.h +++ b/developer/src/kmcmplib/src/kmcmplib.h @@ -16,6 +16,7 @@ namespace kmcmp { extern int CompileTarget; extern int BeginLine[4]; extern int currentLine; + extern int TargetVersion; extern NamedCodeConstants *CodeConstants; extern kmcmp_LoadFileProc loadfileproc; diff --git a/developer/src/kmcmplib/tests/api.tests.cpp b/developer/src/kmcmplib/tests/api.tests.cpp index ea44e3fade0..2809572570c 100644 --- a/developer/src/kmcmplib/tests/api.tests.cpp +++ b/developer/src/kmcmplib/tests/api.tests.cpp @@ -64,6 +64,7 @@ void test_kmcmp_CompileKeyboard(char *kmn_file) { options.warnDeprecatedCode = true; options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; + options.targetVersion = 0; test_assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)); test_assert(error_vec.size() == 1); test_assert(error_vec[0] == KmnCompilerMessages::ERROR_InfileNotExist); // zero byte no longer gives us KmnCompilerMessages::ERROR_CannotReadInfile diff --git a/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp b/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp index 7e65bfdd553..c525d7ce146 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp @@ -34,6 +34,7 @@ namespace kmcmp { extern std::string messageFilename; extern int BeginLine[4]; extern int CompileTarget; + extern int TargetVersion; extern KMX_BOOL FMnemonicLayout; } @@ -57,6 +58,7 @@ class CompilerTest : public testing::Test { kmcmp::nErrors = 0; kmcmp::currentLine = 0; kmcmp::ErrChr = 0; + kmcmp::TargetVersion = 0; kmcmp::messageFilename = ""; kmcmp::BeginLine[BEGIN_ANSI] = -1; kmcmp::BeginLine[BEGIN_UNICODE] = -1; diff --git a/developer/src/kmcmplib/tests/kmcompx.tests.cpp b/developer/src/kmcmplib/tests/kmcompx.tests.cpp index 827199967e6..f562c329653 100644 --- a/developer/src/kmcmplib/tests/kmcompx.tests.cpp +++ b/developer/src/kmcmplib/tests/kmcompx.tests.cpp @@ -57,6 +57,7 @@ int main(int argc, char *argv[]) options.warnDeprecatedCode = true; options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; + options.targetVersion = 0; if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)) { char* testname = strrchr( (char*) kmn_file, '/') + 1; From d779274dc6695d89c8e29b0551817bac540978dc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Oct 2025 05:43:58 +0200 Subject: [PATCH 13/37] test(core): add unit tests for v19.0 kmx+ format --- .../unit/kmnkbd/actions_get_api.tests.cpp | 2 +- .../unit/kmnkbd/actions_normalize.tests.cpp | 2 +- .../unit/ldml/context_normalization.tests.cpp | 2 +- .../{keyboards.kpj.in => keyboards-17.kpj.in} | 2 +- .../unit/ldml/keyboards/keyboards-19.kpj.in | 13 ++++++++++ core/tests/unit/ldml/keyboards/meson.build | 26 +++++++++++++++---- core/tests/unit/ldml/ldml.cpp | 2 +- core/tests/unit/ldml/ldml_test_source.cpp | 4 +-- core/tests/unit/ldml/ldml_test_source.hpp | 2 +- core/tests/unit/ldml/meson.build | 10 +++++-- 10 files changed, 50 insertions(+), 15 deletions(-) rename core/tests/unit/ldml/keyboards/{keyboards.kpj.in => keyboards-17.kpj.in} (89%) create mode 100644 core/tests/unit/ldml/keyboards/keyboards-19.kpj.in diff --git a/core/tests/unit/kmnkbd/actions_get_api.tests.cpp b/core/tests/unit/kmnkbd/actions_get_api.tests.cpp index f9b7c75dac5..3566add5ec3 100644 --- a/core/tests/unit/kmnkbd/actions_get_api.tests.cpp +++ b/core/tests/unit/kmnkbd/actions_get_api.tests.cpp @@ -48,7 +48,7 @@ void teardown() { void setup(const km_core_cu *app_context, const km_core_cu *cached_context, int actions_code_points_to_delete, const std::u32string actions_output) { teardown(); - km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx"); + km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "17.0", "k_001_tiny.kmx"); auto blob = km::tests::load_kmx_file(path.native().c_str()); try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb)); try_status(km_core_state_create(test_kb, test_env_opts, &test_state)); diff --git a/core/tests/unit/kmnkbd/actions_normalize.tests.cpp b/core/tests/unit/kmnkbd/actions_normalize.tests.cpp index f71ef1cd3f1..cf2e7f4f022 100644 --- a/core/tests/unit/kmnkbd/actions_normalize.tests.cpp +++ b/core/tests/unit/kmnkbd/actions_normalize.tests.cpp @@ -46,7 +46,7 @@ void teardown() { void setup(const km_core_cu *app_context, const km_core_cu *cached_context_string, const km_core_context_item *cached_context_items, int actions_code_points_to_delete, const std::u32string actions_output) { teardown(); - km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx"); + km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "17.0", "k_001_tiny.kmx"); auto blob = km::tests::load_kmx_file(path.native().c_str()); try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb)); try_status(km_core_state_create(test_kb, test_env_opts, &test_state)); diff --git a/core/tests/unit/ldml/context_normalization.tests.cpp b/core/tests/unit/ldml/context_normalization.tests.cpp index ea9d635ab23..b9a0020c911 100644 --- a/core/tests/unit/ldml/context_normalization.tests.cpp +++ b/core/tests/unit/ldml/context_normalization.tests.cpp @@ -42,7 +42,7 @@ void teardown() { void setup(const char *keyboard) { teardown(); - km::core::path path = km::core::path::join(arg_path, "keyboards", keyboard); + km::core::path path = km::core::path::join(arg_path, "keyboards", "17.0", keyboard); auto blob = km::tests::load_kmx_file(path.native().c_str()); try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb)); try_status(km_core_state_create(test_kb, test_env_opts, &test_state)); diff --git a/core/tests/unit/ldml/keyboards/keyboards.kpj.in b/core/tests/unit/ldml/keyboards/keyboards-17.kpj.in similarity index 89% rename from core/tests/unit/ldml/keyboards/keyboards.kpj.in rename to core/tests/unit/ldml/keyboards/keyboards-17.kpj.in index 9d704396c4a..9f85a4f73d3 100644 --- a/core/tests/unit/ldml/keyboards/keyboards.kpj.in +++ b/core/tests/unit/ldml/keyboards/keyboards-17.kpj.in @@ -1,7 +1,7 @@ - $PROJECTPATH + $PROJECTPATH/17.0 False False False diff --git a/core/tests/unit/ldml/keyboards/keyboards-19.kpj.in b/core/tests/unit/ldml/keyboards/keyboards-19.kpj.in new file mode 100644 index 00000000000..3785383e049 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/keyboards-19.kpj.in @@ -0,0 +1,13 @@ + + + + $PROJECTPATH/19.0 + False + False + False + keyboard + + + @keyboards_kpj@ + + diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 0fe3980681b..19e158f482d 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -105,14 +105,30 @@ endforeach cfg = configuration_data() cfg.set('keyboards_kpj', test_ldml_keyboards_kpj) -ldml_keyboards_kpj = configure_file( +# Build v17.0 keyboard fixtures + +ldml_keyboards_kpj_17 = configure_file( + configuration: cfg, + input: 'keyboards-17.kpj.in', + output: 'keyboards-17.kpj' +) + +configure_file( + input: ldml_keyboards_kpj_17, + output: tests_from_cldr[0]+'.kmx', + command: kmc_cmd + ['build', '--target-version', '17', '@INPUT@'] +) + +# Build v19.0 keyboard fixtures + +ldml_keyboards_kpj_19 = configure_file( configuration: cfg, - input: 'keyboards.kpj.in', - output: 'keyboards.kpj' + input: 'keyboards-19.kpj.in', + output: 'keyboards19.kpj' ) configure_file( - input: ldml_keyboards_kpj, + input: ldml_keyboards_kpj_19, output: tests_from_cldr[0]+'.kmx', - command: kmc_cmd + ['build', '@INPUT@'] + command: kmc_cmd + ['build', '--target-version', '19', '@INPUT@'] ) diff --git a/core/tests/unit/ldml/ldml.cpp b/core/tests/unit/ldml/ldml.cpp index f0b1eac4fed..bbc6661043c 100644 --- a/core/tests/unit/ldml/ldml.cpp +++ b/core/tests/unit/ldml/ldml.cpp @@ -516,7 +516,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlJsonTestSourceFactory json_factory; // adjust path - const auto json_path = km::tests::LdmlJsonTestSourceFactory::kmx_to_test_json(compiled); + const auto json_path = km::tests::LdmlJsonTestSourceFactory::source_to_test_json(source); int json_result = json_factory.load(compiled, json_path); if (json_result != -1) { const km::tests::JsonTestMap& json_tests = json_factory.get_tests(); diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp index 72094fa0d2f..c20d6dbf2d0 100644 --- a/core/tests/unit/ldml/ldml_test_source.cpp +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -813,8 +813,8 @@ LdmlJsonTestSourceFactory::LdmlJsonTestSourceFactory() : test_map() { } km::core::path -LdmlJsonTestSourceFactory::kmx_to_test_json(const km::core::path &kmx) { - km::core::path p = kmx; +LdmlJsonTestSourceFactory::source_to_test_json(const km::core::path &source) { + km::core::path p = source; p.replace_extension(TEST_JSON_SUFFIX); return p; } diff --git a/core/tests/unit/ldml/ldml_test_source.hpp b/core/tests/unit/ldml/ldml_test_source.hpp index 4e511d43bf2..b93f00d7b69 100644 --- a/core/tests/unit/ldml/ldml_test_source.hpp +++ b/core/tests/unit/ldml/ldml_test_source.hpp @@ -164,7 +164,7 @@ class LdmlJsonTestSourceFactory { */ int load(const km::core::path &compiled, const km::core::path &path); - static km::core::path kmx_to_test_json(const km::core::path& kmx); + static km::core::path source_to_test_json(const km::core::path& kmx); const JsonTestMap& get_tests() const; private: diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build index 21ae49590d6..6a67a2c0314 100644 --- a/core/tests/unit/ldml/meson.build +++ b/core/tests/unit/ldml/meson.build @@ -147,8 +147,14 @@ test('unicode_tests', test_unicode, suite: 'ldml', foreach kbd : tests kbd_src = test_path / kbd + '.xml' - kbd_obj = test_path / kbd + '.kmx' - test(kbd, ldml, args: [kbd_src, kbd_obj], suite: 'ldml-keyboards') + kbd_obj = test_path / '17.0' / kbd + '.kmx' + test(kbd, ldml, args: [kbd_src, kbd_obj], suite: 'ldml-keyboards-17') +endforeach + +foreach kbd : tests + kbd_src = test_path / kbd + '.xml' + kbd_obj = test_path / '19.0' / kbd + '.kmx' + test(kbd, ldml, args: [kbd_src, kbd_obj], suite: 'ldml-keyboards-19') endforeach # Run tests on all invalid keyboards (`invalid_tests` defined in invalid-keyboards/meson.build) From 77ea540fddcd38ea9d662c3e4b7c5b0e3ac2b648 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Oct 2025 11:27:35 +0200 Subject: [PATCH 14/37] refactor(core): DRY out filling of sub-section data and fix cross-platform diffs * Address cross-platform compiler warnings around shadowed pointers and non-virtual destructors. * Refactor the offset calculations for sub-section data to reduce repetition both within each calculation, and the patterns of the calculations themselves. This mostly resolves possibility of typo errors when mapping the section data in, e.g. using the wrong count, as each variable and type is only referenced once, and compiler will catch most discrepancies - except for count vs type. This is also much easier to read and verify in code review, I hope! --- core/src/kmx/kmx_plus.cpp | 377 +++++++++++++++++++------------------- core/src/kmx/kmx_plus.h | 13 +- 2 files changed, 196 insertions(+), 194 deletions(-) diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp index 234e91c1459..013697608a0 100644 --- a/core/src/kmx/kmx_plus.cpp +++ b/core/src/kmx/kmx_plus.cpp @@ -341,26 +341,94 @@ const U* get_file_data_at_offset(const COMP_KMXPLUS_SECT *base, COMP_KMXPLUS_HEA /** * @brief Get the section data at offset, casting to desired type * - * @tparam T - * @tparam U - * @param base start of the section data, i.e. immedaitely after section header + * @tparam T struct type of the section + * @tparam U struct type of data to return + * @param base start of the section data, i.e. immediately after section + * header * @param header header data for the section * @param offset offset in bytes from the start of the section data * @param size size of the data to return, for validation - * @return U + * @return pointer to data, type U */ template -U get_section_data_at_offset(const T *base, COMP_KMXPLUS_HEADER const &header, KMX_DWORD offset, KMX_DWORD size) { +const U* get_section_data_at_offset(const T *base, COMP_KMXPLUS_HEADER const &header, KMX_DWORD offset, KMX_DWORD size) { if(!is_block_valid(header, offset, size)) { return nullptr; } offset -= header.headerSize(); const uint8_t* thisptr = reinterpret_cast(base); - U start = reinterpret_cast(thisptr+offset); + const U* start = reinterpret_cast(thisptr+offset); return start; } +/** + * @brief Get the section data at offset, casting to desired type, verify that + * the section is long enough for count * data, and update the offset to point + * to the next byte after the data. Allows zero-length, optional data. + * + * @tparam T struct type of the section + * @tparam U struct type of data to return + * @param base start of the section data, i.e. immediately after section + * header + * @param header header data for the section + * @param count number of U items expected + * @param offset (in, out) offset in bytes from the start of the section data, + * updated on return to next byte after data + * @param out (out) pointer to start of data + * @return bool false on error + */ +template +bool get_optional_section_data_at_offset_and_increment(const T *base, COMP_KMXPLUS_HEADER const &header, + KMX_DWORD count, KMX_DWORD& offset, const U*& out +) { + out = nullptr; + + if(count == 0) { + return true; + } + + KMX_DWORD size = count * sizeof(U); + + out = get_section_data_at_offset(base, header, offset, size); + + if(out == nullptr) { + return false; + } + + offset += size; + + return true; +} + +/** + * @brief Get the section data at offset, casting to desired type, verify that + * the section is long enough for count * data, and update the offset to point + * to the next byte after the data. Does not allow zero-length, optional data. + * + * @tparam T struct type of the section + * @tparam U struct type of data to return + * @param base start of the section data, i.e. immediately after section + * header + * @param header header data for the section + * @param count number of U items expected + * @param offset (in, out) offset in bytes from the start of the section data, + * updated on return to next byte after data + * @param out (out) pointer to start of data + * @return bool false on error or missing data + */ +template +bool get_required_section_data_at_offset_and_increment(const T *base, COMP_KMXPLUS_HEADER const &header, + KMX_DWORD count, KMX_DWORD& offset, const U*& out +) { + if(count == 0) { + out = nullptr; + return false; + } + + return get_optional_section_data_at_offset_and_increment(base, header, count, offset, out); +} + bool COMP_KMXPLUS_HEADER_17::valid(KMX_DWORD length) const { DebugLog("%c%c%c%c: (%X) size 0x%X\n", DEBUG_IDENT(ident), ident, size); @@ -429,7 +497,7 @@ COMP_KMXPLUS_META::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unuse bool COMP_KMXPLUS_DISP::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { DebugLog("disp: count 0x%X\n", count); - if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { + if (!is_block_valid(header, header.headerSize(), sizeof(*this)+(sizeof(entries[0])*count))) { DebugLog("header.size < expected size"); assert(false); return false; @@ -459,7 +527,7 @@ COMP_KMXPLUS_STRS::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unuse for (KMX_DWORD i=0; i(this, header, offset, length); + const KMX_WCHAR* start = get_section_data_at_offset(this, header, offset, (length+1)*sizeof(KMX_WCHAR)); if(!start) { return false; } @@ -655,7 +723,7 @@ COMP_KMXPLUS_ELEM::getElementList(const COMP_KMXPLUS_HEADER &header, KMX_DWORD e } // pointer to specified entry - return get_section_data_at_offset(this, header, entry.offset, + return get_section_data_at_offset(this, header, entry.offset, entry.length * sizeof(COMP_KMXPLUS_ELEM_ELEMENT)); } @@ -739,6 +807,11 @@ COMP_KMXPLUS_TRAN_Helper::getReorder(KMX_DWORD reorder) const { bool COMP_KMXPLUS_TRAN_Helper::set(const COMP_KMXPLUS_TRAN *newTran) { + is_valid = false; + groups = nullptr; + transforms = nullptr; + reorders = nullptr; + if(!COMP_KMXPLUS_Section_Helper::set(newTran)) { return false; } @@ -752,44 +825,20 @@ COMP_KMXPLUS_TRAN_Helper::set(const COMP_KMXPLUS_TRAN *newTran) { } KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_TRAN); - // groups - if (data()->groupCount > 0) { - groups = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_TRAN_GROUP) * data()->groupCount); - } else { - groups = nullptr; - } - - if(!groups) { - is_valid = false; - assert(is_valid); - } - offset += sizeof(COMP_KMXPLUS_TRAN_GROUP) * data()->groupCount; + // groups (required) + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->groupCount, offset, groups); + assert(is_valid); - // transforms - if (data()->transformCount > 0) { - transforms = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_TRAN_TRANSFORM) * data()->transformCount); - if(!transforms) { - is_valid = false; - assert(is_valid); - } - } else { - transforms = nullptr; - } - offset += sizeof(COMP_KMXPLUS_TRAN_TRANSFORM) * data()->transformCount; + // transforms (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->transformCount, offset, transforms); + assert(is_valid); - // reorders - if (data()->reorderCount > 0) { - reorders = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_TRAN_REORDER) * data()->reorderCount); - if(!reorders) { - is_valid = false; - assert(is_valid); - } - } else { - reorders = nullptr; - } + // reorders (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->reorderCount, offset, reorders); + assert(is_valid); // Now, validate offsets by walking if (is_valid) { @@ -873,7 +922,15 @@ COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : is_valid(false) { bool COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { - COMP_KMXPLUS_Section_Helper::set(newLayr); + is_valid = false; + lists = nullptr; + entries = nullptr; + rows = nullptr; + keys = nullptr; + + if(!COMP_KMXPLUS_Section_Helper::set(newLayr)) { + return false; + } DebugLog("validating newLayr=%p", newLayr); is_valid = true; if (newLayr == nullptr) { @@ -882,53 +939,26 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { return true; // not invalid, just missing } KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_LAYR); // skip past non-dynamic portion - // lists - if (data()->listCount > 0) { - lists = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_LAYR_LIST) * data()->listCount); - } else { - lists = nullptr; - } - if(!lists) { - is_valid = false; - assert(is_valid); - } - offset += sizeof(COMP_KMXPLUS_LAYR_LIST) * data()->listCount; - // entries - if (data()->layerCount > 0) { - entries = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_LAYR_ENTRY) * data()->layerCount); - } else { - entries = nullptr; - } - if(!entries) { - is_valid = false; - assert(is_valid); - } - offset += sizeof(COMP_KMXPLUS_LAYR_ENTRY) * data()->layerCount; - // rows - if (data()->rowCount > 0) { - rows = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_LAYR_ROW) * data()->rowCount); - } else { - rows = nullptr; - } - if(!rows) { - is_valid = false; - assert(is_valid); - } - offset += sizeof(COMP_KMXPLUS_LAYR_ROW) * data()->rowCount; - // keys - if (data()->keyCount > 0) { - keys = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_LAYR_KEY) * data()->keyCount); - } else { - keys = nullptr; - } - if(!keys) { - is_valid = false; - assert(is_valid); - } + + // lists (required) + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->listCount, offset, lists); + assert(is_valid); + + // entries (required) - note, "entryCount" is called "layerCount" in COMP_KMXPLUS_LAYR + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->layerCount, offset, entries); + assert(is_valid); + + // rows (required) + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->rowCount, offset, rows); + assert(is_valid); + + // keys (required) + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->keyCount, offset, keys); + assert(is_valid); // Now, validate offsets by walking if (is_valid) { @@ -1039,7 +1069,15 @@ COMP_KMXPLUS_KEYS_Helper::COMP_KMXPLUS_KEYS_Helper() : is_valid(false) { bool COMP_KMXPLUS_KEYS_Helper::set(const COMP_KMXPLUS_KEYS *newKeys) { - COMP_KMXPLUS_Section_Helper::set(newKeys); + is_valid = false; + keys = nullptr; + flickLists = nullptr; + flickElements = nullptr; + kmap = nullptr; + + if(!COMP_KMXPLUS_Section_Helper::set(newKeys)) { + return false; + } DebugLog("validating newKeys=%p", newKeys); is_valid = true; if (newKeys == nullptr) { @@ -1047,54 +1085,28 @@ COMP_KMXPLUS_KEYS_Helper::set(const COMP_KMXPLUS_KEYS *newKeys) { // which validates this section's length. Will be nullptr here if invalid. return true; // not invalid, just missing } - // keys + KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_KEYS); - if (data()->keyCount > 0) { - keys = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_KEYS_KEY) * data()->keyCount); - } else { - keys = nullptr; - } - if(!keys) { - is_valid = false; - assert(is_valid); - } - offset += sizeof(COMP_KMXPLUS_KEYS_KEY) * data()->keyCount; - // flicks - if (data()->flicksCount > 0) { - flickLists = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST) * data()->flicksCount); - if(!flickLists) { - is_valid = false; - assert(is_valid); - } - } else { - flickLists = nullptr; // not an error - } - offset += sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST) * data()->flicksCount; - // flick - if (data()->flickCount > 0) { - flickElements = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT) * data()->flickCount); - if(!flickElements) { - is_valid = false; - assert(is_valid); - } - } else { - flickElements = nullptr; // not an error - } - offset += sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT) * data()->flickCount; - // kmap - if (data()->kmapCount > 0) { - kmap = get_section_data_at_offset(data(), header, offset, - sizeof(COMP_KMXPLUS_KEYS_KMAP) * data()->kmapCount); - if(!kmap) { - is_valid = false; - assert(is_valid); - } - } else { - kmap = nullptr; // not an error - } + + // keys (required) + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->keyCount, offset, keys); + assert(is_valid); + + // flicks (optional) - note tricky "flicksCount" vs "flickCount" in COMP_KMXPLUS_KEYS + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->flicksCount, offset, flickLists); + assert(is_valid); + + // flick (optional) - note tricky "flicksCount" vs "flickCount" in COMP_KMXPLUS_KEYS + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->flickCount, offset, flickElements); + assert(is_valid); + + // kmap (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->kmapCount, offset, kmap); + assert(is_valid); // Now, validate offsets by walking if (is_valid) { @@ -1253,7 +1265,13 @@ COMP_KMXPLUS_LIST_Helper::COMP_KMXPLUS_LIST_Helper() : is_valid(false) { bool COMP_KMXPLUS_LIST_Helper::set(const COMP_KMXPLUS_LIST *newList) { - COMP_KMXPLUS_Section_Helper::set(newList); + is_valid = false; + lists = nullptr; + indices = nullptr; + + if(!COMP_KMXPLUS_Section_Helper::set(newList)) { + return false; + } DebugLog("validating newList=%p", newList); is_valid = true; if (newList == nullptr) { @@ -1263,28 +1281,17 @@ COMP_KMXPLUS_LIST_Helper::set(const COMP_KMXPLUS_LIST *newList) { } KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_LIST); // skip past non-dynamic portion - // lists - if (data()->listCount > 0) { - lists = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_LIST_ITEM) * data()->listCount); - if(!lists) { - is_valid = false; - assert(is_valid); - } - } else { - lists = nullptr; - // not invalid, just empty. - } - offset += sizeof(COMP_KMXPLUS_LIST_ITEM) * data()->listCount; - // entries - if (data()->indexCount > 0) { - indices = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_LIST_INDEX) * data()->indexCount); - if(!indices) { - is_valid = false; - assert(is_valid); - } - } else { - indices = nullptr; - } + + // lists (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->listCount, offset, lists); + assert(is_valid); + + // indices (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->indexCount, offset, indices); + assert(is_valid); + // Now, validate offsets by walking if (is_valid) { for (KMX_DWORD i = 0; is_valid && i < data()->listCount; i++) { @@ -1358,7 +1365,13 @@ COMP_KMXPLUS_USET_Helper::COMP_KMXPLUS_USET_Helper() : is_valid(false), usets(nu bool COMP_KMXPLUS_USET_Helper::set(const COMP_KMXPLUS_USET *newUset) { - COMP_KMXPLUS_Section_Helper::set(newUset); + is_valid = false; + usets = nullptr; + ranges = nullptr; + + if(!COMP_KMXPLUS_Section_Helper::set(newUset)) { + return false; + } DebugLoad("validating newUset=%p", newUset); is_valid = true; if (newUset == nullptr) { @@ -1367,28 +1380,16 @@ COMP_KMXPLUS_USET_Helper::set(const COMP_KMXPLUS_USET *newUset) { return true; // not invalid, just missing } KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_USET); // skip past non-dynamic portion - // usets - if (data()->usetCount > 0) { - usets = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_USET_USET) * data()->usetCount); - if(!usets) { - is_valid = false; - assert(is_valid); - } - } else { - usets = nullptr; - // not invalid, just empty. - } - offset += sizeof(COMP_KMXPLUS_USET_USET) * data()->usetCount; - // entries - if (data()->rangeCount > 0) { - ranges = get_section_data_at_offset(data(), header, offset, sizeof(COMP_KMXPLUS_USET_RANGE) * data()->rangeCount); - if(!ranges) { - is_valid = false; - assert(is_valid); - } - } else { - ranges = nullptr; - } + + // usets (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->usetCount, offset, usets); + assert(is_valid); + + // ranges (optional) + is_valid = is_valid && get_optional_section_data_at_offset_and_increment( + data(), header, data()->rangeCount, offset, ranges); + assert(is_valid); // Now, validate offsets by walking // is_valid must be true at this point. @@ -1581,7 +1582,7 @@ COMP_KMXPLUS_STRS::get(const COMP_KMXPLUS_HEADER& header, KMX_DWORD entry) const const KMX_DWORD length = entries[entry].length; // the string is null terminated in the data file, thus length + 1 - auto start = get_section_data_at_offset(this, header, offset, (length + 1) * sizeof(KMX_WCHAR)); + auto start = get_section_data_at_offset(this, header, offset, (length + 1) * sizeof(KMX_WCHAR)); if(!start) { return std::u16string(); } diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index 0d4344709bd..d6eff001d29 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -101,16 +101,16 @@ struct COMP_KMXPLUS_HEADER { * @param size * @return KMX_DWORD */ - inline KMX_DWORD calculateBaseSize(KMX_DWORD size) { - return size - LDML_LENGTH_HEADER_17 + this->_headerSize; + inline KMX_DWORD calculateBaseSize(KMX_DWORD elementSize) { + return elementSize - LDML_LENGTH_HEADER_17 + this->_headerSize; } - inline void set(KMX_DWORD fileVersion, KMXPLUS_IDENT ident, KMX_DWORD size, KMX_DWORD version = LDML_KMXPLUS_VERSION_17) { + inline void set(KMX_DWORD fileVersion, KMXPLUS_IDENT identIn, KMX_DWORD sizeIn, KMX_DWORD versionIn = LDML_KMXPLUS_VERSION_17) { this->_fileVersion = fileVersion; this->_headerSize = fileVersion == LDML_KMXPLUS_VERSION_17 ? LDML_LENGTH_HEADER_17 : LDML_LENGTH_HEADER_19; - this->ident = ident; - this->size = size; - this->version = version; + this->ident = identIn; + this->size = sizeIn; + this->version = versionIn; } }; @@ -136,6 +136,7 @@ class COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_HEADER header; COMP_KMXPLUS_Section_Helper() : _data(nullptr) {} + virtual ~COMP_KMXPLUS_Section_Helper() {} virtual bool set(const T* section) { _data = section; return true; From 6dd878d4ed1a50ee4978e5f5322a7bdeceb0c37a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Oct 2025 13:55:05 +0200 Subject: [PATCH 15/37] test(developer): fix tests for versioning --- developer/src/kmc-kmn/test/features.tests.ts | 6 +++--- developer/src/kmc-ldml/test/compiler-e2e.tests.ts | 2 +- developer/src/kmc-ldml/test/messages.tests.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc-kmn/test/features.tests.ts b/developer/src/kmc-kmn/test/features.tests.ts index 8a971093a37..7fa2f060fb4 100644 --- a/developer/src/kmc-kmn/test/features.tests.ts +++ b/developer/src/kmc-kmn/test/features.tests.ts @@ -54,9 +54,9 @@ describe('Keyboard compiler features', function() { } [ - {s:'VERSION_60', t:undefined, e:KMX.KMXFile.VERSION_60}, - {s:'VERSION_170', t:17, e:KMX.KMXFile.VERSION_170}, - {s:'VERSION_190', t:19, e:KMX.KMXFile.VERSION_190}, + {s:'VERSION_60', t:undefined, e:KMX.KMXFile.VERSION_60}, + {s:'VERSION_170', t:KMX.KMXFile.VERSION_170, e:KMX.KMXFile.VERSION_170}, + {s:'VERSION_190', t:KMX.KMXFile.VERSION_190, e:KMX.KMXFile.VERSION_190}, ].forEach(function(v) { it(`should build a minimum version keyboard to ${v.s} with targetVersion=${v.t}`, async function() { const compiler = new KmnCompiler(); diff --git a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts index 70f0a30a926..5b2c4bd6318 100644 --- a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts +++ b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts @@ -46,7 +46,7 @@ describe('compiler-tests', function() { // Let's build basic.xml // It should match basic-vv.kmx (built from basic.txt) const k = new LdmlKeyboardCompiler(); - await k.init(compilerTestCallbacks, { ...compilerTestOptions, version, saveDebug: true, shouldAddCompilerVersion: false }); + await k.init(compilerTestCallbacks, { ...compilerTestOptions, targetVersion: version, saveDebug: true, shouldAddCompilerVersion: false }); const { artifacts } = await k.run(inputFilename, "basic-xml.kmx"); // need the exact name passed to build-fixtures assert.isNotNull(artifacts); diff --git a/developer/src/kmc-ldml/test/messages.tests.ts b/developer/src/kmc-ldml/test/messages.tests.ts index 60df66fb095..1fad6b48666 100644 --- a/developer/src/kmc-ldml/test/messages.tests.ts +++ b/developer/src/kmc-ldml/test/messages.tests.ts @@ -20,8 +20,8 @@ describe('LdmlCompilerMessages', function () { const fakeOffsetNumber = 1234; const fakeOffsetObject = withOffset(fakeOffsetNumber); for(const key of keys) { - // exclude this one, does not need line numbers - if (key == 'Error_InvalidFile') continue; + // exclude these ones, do not need line numbers + if (key == 'Error_InvalidFile' || key == 'Error_InvalidTargetVersion') continue; if (typeof m[key] == 'function') { total++; const f = m[key] as Function; From d068ba47773ef2694cdcf668e115964da07dd749 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Oct 2025 13:55:05 +0200 Subject: [PATCH 16/37] test(developer): fix tests for versioning --- developer/src/kmc-kmn/test/features.tests.ts | 6 +++--- developer/src/kmc-ldml/test/compiler-e2e.tests.ts | 2 +- developer/src/kmc-ldml/test/messages.tests.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc-kmn/test/features.tests.ts b/developer/src/kmc-kmn/test/features.tests.ts index 8a971093a37..7fa2f060fb4 100644 --- a/developer/src/kmc-kmn/test/features.tests.ts +++ b/developer/src/kmc-kmn/test/features.tests.ts @@ -54,9 +54,9 @@ describe('Keyboard compiler features', function() { } [ - {s:'VERSION_60', t:undefined, e:KMX.KMXFile.VERSION_60}, - {s:'VERSION_170', t:17, e:KMX.KMXFile.VERSION_170}, - {s:'VERSION_190', t:19, e:KMX.KMXFile.VERSION_190}, + {s:'VERSION_60', t:undefined, e:KMX.KMXFile.VERSION_60}, + {s:'VERSION_170', t:KMX.KMXFile.VERSION_170, e:KMX.KMXFile.VERSION_170}, + {s:'VERSION_190', t:KMX.KMXFile.VERSION_190, e:KMX.KMXFile.VERSION_190}, ].forEach(function(v) { it(`should build a minimum version keyboard to ${v.s} with targetVersion=${v.t}`, async function() { const compiler = new KmnCompiler(); diff --git a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts index 70f0a30a926..5b2c4bd6318 100644 --- a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts +++ b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts @@ -46,7 +46,7 @@ describe('compiler-tests', function() { // Let's build basic.xml // It should match basic-vv.kmx (built from basic.txt) const k = new LdmlKeyboardCompiler(); - await k.init(compilerTestCallbacks, { ...compilerTestOptions, version, saveDebug: true, shouldAddCompilerVersion: false }); + await k.init(compilerTestCallbacks, { ...compilerTestOptions, targetVersion: version, saveDebug: true, shouldAddCompilerVersion: false }); const { artifacts } = await k.run(inputFilename, "basic-xml.kmx"); // need the exact name passed to build-fixtures assert.isNotNull(artifacts); diff --git a/developer/src/kmc-ldml/test/messages.tests.ts b/developer/src/kmc-ldml/test/messages.tests.ts index 60df66fb095..1fad6b48666 100644 --- a/developer/src/kmc-ldml/test/messages.tests.ts +++ b/developer/src/kmc-ldml/test/messages.tests.ts @@ -20,8 +20,8 @@ describe('LdmlCompilerMessages', function () { const fakeOffsetNumber = 1234; const fakeOffsetObject = withOffset(fakeOffsetNumber); for(const key of keys) { - // exclude this one, does not need line numbers - if (key == 'Error_InvalidFile') continue; + // exclude these ones, do not need line numbers + if (key == 'Error_InvalidFile' || key == 'Error_InvalidTargetVersion') continue; if (typeof m[key] == 'function') { total++; const f = m[key] as Function; From f16545380a131781fe86f83b5d3af9c5082f57d5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Oct 2025 14:32:30 +0200 Subject: [PATCH 17/37] docs(common): clarify usage of KMXPlus structures Build-bot: skip --- common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts | 10 ++++++++++ common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts index 2644247323a..c7f35c7d301 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts @@ -1,7 +1,17 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-10-08 + */ import * as KMX from '../kmx.js'; import * as r from 'restructure'; import KMXFile = KMX.KMXFile; +/** + * Binary representation of KMX+ data, using Restructure. These structures + * should be directly used only by KMX+ file readers and writers; in general, + * most things should use the in-memory `KMXPlusData` structures in kmx-plus.ts. + */ export class KMXPlusFileFormat extends KMXFile { /* KMXPlus file structures */ diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index e46ca58ced6..127d8a75b87 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import { constants } from '@keymanapp/ldml-keyboard-constants'; import { ElementString } from './element-string.js'; import { ListItem } from '../../ldml-keyboard/string-list.js'; @@ -611,6 +614,10 @@ export class List extends Section { export { ListItem as ListItem }; +/** + * In-memory representation of KMX+ data. See also `KMXPlusFileFormat` and + * `KMXPlusFile`. + */ export interface KMXPlusData { sect?: Strs; // sect is ignored in-memory bksp?: Bksp; @@ -627,7 +634,6 @@ export interface KMXPlusData { vars?: Vars; }; - export class KMXPlusFile extends KMXPlusFileFormat { /* File in-memory data */ public kmxplus: KMXPlusData = { }; From d987f3f0001f83064fa2365bb5d2243b258b1b59 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Oct 2025 14:37:12 +0200 Subject: [PATCH 18/37] chore(developer): clean up variable names and filenames Build-bot: skip --- developer/src/kmc-kmn/build.sh | 12 +++++------ .../src/kmc-kmn/test/kmw/constants.tests.ts | 20 +++++++++---------- developer/src/kmc-kmn/test/tsconfig.json | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/developer/src/kmc-kmn/build.sh b/developer/src/kmc-kmn/build.sh index d1cc6816639..ddcb2a355c3 100755 --- a/developer/src/kmc-kmn/build.sh +++ b/developer/src/kmc-kmn/build.sh @@ -68,13 +68,13 @@ function do_test() { # We want to compare the key cap values from both KMW and Developer and ensure # that all three are in sync. We'll copy the relevant source files and patch - # them in directly. Builder's constants.js is not an ES6 module, so we hackily - # patch that here. - echo 'export const builder = {specialCharacters:{}}' > ./test/kmw/_imported_constants.js + # them in directly. The touch layout builder's constants.js is not an ES6 + # module, so we hackily patch that here. + echo 'export const builder = {specialCharacters:{}}' > ./test/kmw/_imported_layoutbuilder_constants.js # shellcheck disable=SC2016 - echo 'function $(v) {v()}' >> ./test/kmw/_imported_constants.js - cat "${KEYMAN_ROOT}/developer/src/tike/xml/layoutbuilder/constants.js" >> ./test/kmw/_imported_constants.js - cp "${KEYMAN_ROOT}/web/src/engine/osk/src/specialCharacters.ts" ./test/kmw/_imported_specialCharacters.ts + echo 'function $(v) {v()}' >> ./test/kmw/_imported_layoutbuilder_constants.js + cat "${KEYMAN_ROOT}/developer/src/tike/xml/layoutbuilder/constants.js" >> ./test/kmw/_imported_layoutbuilder_constants.js + cp "${KEYMAN_ROOT}/web/src/engine/osk/src/specialCharacters.ts" ./test/kmw/_imported_web_osk_specialCharacters.ts typescript_run_eslint_mocha_tests 80 } diff --git a/developer/src/kmc-kmn/test/kmw/constants.tests.ts b/developer/src/kmc-kmn/test/kmw/constants.tests.ts index d9acdb7d356..69dab212ef1 100644 --- a/developer/src/kmc-kmn/test/kmw/constants.tests.ts +++ b/developer/src/kmc-kmn/test/kmw/constants.tests.ts @@ -23,10 +23,10 @@ import { fileURLToPath } from 'node:url'; import 'mocha'; import {assert} from 'chai'; -import keymanWebSpecialCharacters from "./_imported_specialCharacters.js"; +import keymanWebSpecialCharacters from "./_imported_web_osk_specialCharacters.js"; import { CSpecialText17, CSpecialText14, CSpecialText10, CSpecialText17ZWNJ } from "../../src/kmw-compiler/constants.js"; -import { builder } from "./_imported_constants.js"; -import { constants as coreConstants } from "@keymanapp/ldml-keyboard-constants"; +import { builder } from "./_imported_layoutbuilder_constants.js"; +import { constants as coreLdmlConstants } from "@keymanapp/ldml-keyboard-constants"; /** Verify key cap constants across 4 modules: KMW treated as primary */ describe('Key cap special text values from KeymanWeb', function() { @@ -102,11 +102,11 @@ describe('Key cap special text values from KeymanWeb', function() { } // 3. We only want to compare the dis2_key_cap_ values from the - // coreConstants object + // coreLdmlConstants object const coreConstantsFiltered: any = {}; - for(const key of Object.keys(coreConstants)) { + for(const key of Object.keys(coreLdmlConstants)) { if(key.match(/^dis2_key_cap_/)) { - coreConstantsFiltered[key] = (coreConstants)[key]; + coreConstantsFiltered[key] = (coreLdmlConstants)[key]; } } @@ -119,16 +119,16 @@ describe('Key cap special text values from KeymanWeb', function() { // web/src/engine/osk/src/specialCharacters.ts const helpFile = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../../../docs/help/reference/file-types/keyman-touch-layout.md'); - const lines = fs.readFileSync(helpFile, 'utf-8').replaceAll(/\r\n/g, '\n').split('\n'); + const helpLines = fs.readFileSync(helpFile, 'utf-8').replaceAll(/\r\n/g, '\n').split('\n'); // Find the relevant section in the file between start:special_key_caps and // end:special_key_caps - const line0 = lines.findIndex(line => line.includes('start:special_key_caps')); + const line0 = helpLines.findIndex(line => line.includes('start:special_key_caps')); assert.notEqual(line0, -1); - const line1 = lines.findIndex(line => line.includes('end:special_key_caps')); + const line1 = helpLines.findIndex(line => line.includes('end:special_key_caps')); assert.notEqual(line1, -1); - const content = lines.slice(line0+1, line1); + const content = helpLines.slice(line0+1, line1); const markdownConstants: any = {}; diff --git a/developer/src/kmc-kmn/test/tsconfig.json b/developer/src/kmc-kmn/test/tsconfig.json index ffdcf60ae75..82f24a7385c 100644 --- a/developer/src/kmc-kmn/test/tsconfig.json +++ b/developer/src/kmc-kmn/test/tsconfig.json @@ -10,8 +10,8 @@ }, "include": [ "**/*.tests.ts", - "./kmw/_imported_constants.js", - "./kmw/_imported_specialCharacters.ts", + "./kmw/_imported_layoutbuilder_constants.js", + "./kmw/_imported_web_osk_specialCharacters.ts", "./helpers/index.ts", "./kmw/util.ts" ], From 83b62471397133b7a0020dd08f42829f6e2a20cb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 23 Oct 2025 13:32:57 +0200 Subject: [PATCH 19/37] Update common/web/types/src/kmx/kmx.ts --- common/web/types/src/kmx/kmx.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts index e2f54f62a70..04901401a03 100644 --- a/common/web/types/src/kmx/kmx.ts +++ b/common/web/types/src/kmx/kmx.ts @@ -52,6 +52,9 @@ export function versionStringToKmxVersion(version: string): KMX_Version { return null; } + // Version number is 16 bit number with MINOR in lower 8 bits, + // MAJOR in upper 8 bits. In practice, we now only use MAJOR + // version for Keyman versions. const num = major << 8; if(Object.values(KMX_Version).includes(num)) { From e9705242c15b70d34db5e0c55156a1ed5a4c28cc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 27 Oct 2025 16:38:21 +0100 Subject: [PATCH 20/37] docs(core): add function comments Build-bot: skip --- core/src/kmx/kmx_plus.h | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index d6eff001d29..990e9f86119 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -169,13 +169,16 @@ struct COMP_KMXPLUS_SECT { /** * @brief Get the pointer to a specific section * - * @param ident section id such as 'strs'. Never 'sect' (the sect table does not list itself!) - * @param entryLength on exit, will be set to the possible length of the section (based on the remainder of the KMX+ file) + * @param header reference to the 'sect' section header for the file + * @param ident section id such as 'strs'. Never 'sect' (the sect table does not list itself!) + * @param entryLength on exit, will be set to the possible length of the section (based on the remainder of the KMX+ file) * @return pointer to raw bytes of requested section, or nullptr if not found */ const uint8_t *get(COMP_KMXPLUS_HEADER const& header, KMX_DWORD ident, KMX_DWORD &entryLength) const; /** * @brief True if section is valid. + * @param header reference to the 'sect' section header for the file + * @param fileLength length of the KMX+ file data (not including KMX data, if KMX+ is embedded) * Does not validate the entire file. */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD fileLength) const; @@ -224,12 +227,16 @@ struct COMP_KMXPLUS_ELEM { /** * @brief True if section is valid. + * @param header reference to the 'elem' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; /** + * @brief + * @param header reference to the 'elem' section header * @param elementNumber element number, 0..count-1 - * @param length fillin: length of list + * @param length fillin: length of list * @return pointer to first element of list of length length. or nullptr */ const COMP_KMXPLUS_ELEM_ELEMENT *getElementList( @@ -277,6 +284,8 @@ struct COMP_KMXPLUS_LOCA { COMP_KMXPLUS_LOCA_ENTRY entries[]; /** * @brief True if section is valid. + * @param header reference to the 'loca' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; @@ -301,6 +310,8 @@ struct COMP_KMXPLUS_META { KMX_DWORD_unaligned settings; /** * @brief True if section is valid. + * @param header reference to the 'meta' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; @@ -331,19 +342,21 @@ struct COMP_KMXPLUS_STRS { /** * @brief Get a string entry * - * @param entry entry number - * @param buf output buffer - * @param bufsiz buffer size in bytes + * @param header reference to the 'strs' section header + * @param entry entry number * @return nullptr or a pointer to the output buffer */ std::u16string get(const COMP_KMXPLUS_HEADER& header, KMX_DWORD entry) const; /** * Slow search + * @param header reference to the 'strs' section header */ KMX_DWORD find(const COMP_KMXPLUS_HEADER& header, const std::u16string&) const; /** * @brief True if section is valid. + * @param header reference to the 'strs' section header + * @param length length of the section in bytes */ bool valid(const COMP_KMXPLUS_HEADER& header, KMX_DWORD length) const; @@ -395,6 +408,8 @@ struct COMP_KMXPLUS_TRAN { // COMP_KMXPLUS_TRAN_REORDER reorders[] /** * @brief True if section is valid. + * @param header reference to the 'tran' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; @@ -464,6 +479,8 @@ struct COMP_KMXPLUS_VARS { COMP_KMXPLUS_VARS_ITEM varEntries[]; /** * @brief True if section is valid. + * @param header reference to the 'vars' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; @@ -493,6 +510,8 @@ struct COMP_KMXPLUS_DISP { COMP_KMXPLUS_DISP_ENTRY entries[]; /** * @brief True if section is valid. + * @param header reference to the 'disp' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; @@ -553,6 +572,8 @@ struct COMP_KMXPLUS_LAYR { // COMP_KMXPLUS_LAYR_KEY keys[]; /** * @brief True if section is valid. + * @param header reference to the 'layr' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; @@ -608,6 +629,8 @@ struct COMP_KMXPLUS_KEYS { /** * @brief True if section is valid. + * @param header reference to the 'keys' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; @@ -702,6 +725,8 @@ struct COMP_KMXPLUS_LIST { /** * @brief True if section is valid. + * @param header reference to the 'list' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; @@ -764,6 +789,8 @@ struct COMP_KMXPLUS_USET { /** * @brief True if section is valid. + * @param header reference to the 'uset' section header + * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; From 21c3a630bf09f435c5ce548b015cb78e61234019 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 30 Oct 2025 16:31:45 +0100 Subject: [PATCH 21/37] chore(windows): upgrade VC++ projects to v143 (VS2022) Test-bot: skip Build-bot: skip release:developer,windows --- .../delphi/ext/tds2dbg/tds2dbg.vcxproj | 236 ++--- developer/src/kmanalyze/kmanalyze.vcxproj | 418 ++++---- developer/src/kmdecomp/kmdecomp.vcxproj | 340 +++---- .../src/samples/imsample/imsample.vcxproj | 530 +++++----- .../src/engine/keyman32/keyman-engine.vcxproj | 802 +++++++-------- windows/src/engine/keyman32/keyman32.vcxproj | 930 +++++++++--------- .../engine/keyman32/keyman32.vcxproj.filters | 534 +++++----- .../keyman-engine-tests.vcxproj | 320 +++--- .../src/engine/keymanx64/keymanx64.vcxproj | 304 +++--- .../src/engine/kmrefresh/kmrefresh.vcxproj | 312 +++--- windows/src/engine/kmtip/kmtip.vcxproj | 862 ++++++++-------- windows/src/engine/mcompile/mcompile.vcxproj | 266 ++--- windows/src/engine/testhost/testhost.vcxproj | 342 +++---- windows/src/support/etl2log/etl2log.vcxproj | 362 +++---- windows/src/support/texteditor/Editor.vcxproj | 348 +++---- .../shared-data/cpp/cppshareddata.vcxproj | 350 +++---- 16 files changed, 3631 insertions(+), 3625 deletions(-) diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj b/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj index 8d57ae5c662..5183081136e 100644 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj +++ b/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj @@ -1,119 +1,119 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B} - Win32Proj - 10.0.17763.0 - - - - Application - true - v141 - - - Application - false - v141 - - - Application - true - v141 - - - Application - false - v141 - - - - - - - - - - - - - - - - - - - - - true - - - true - - - - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - Level3 - ProgramDatabase - Disabled - - - MachineX86 - true - Console - - - - - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDLL - Level3 - ProgramDatabase - - - MachineX86 - true - Console - true - true - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {40BFECE3-7FC0-4649-A3DC-E017FF84C75B} + Win32Proj + 10.0 + + + + Application + true + v143 + + + Application + false + v143 + + + Application + true + v143 + + + Application + false + v143 + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + + + MachineX86 + true + Console + + + + + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Console + true + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/developer/src/kmanalyze/kmanalyze.vcxproj b/developer/src/kmanalyze/kmanalyze.vcxproj index 20fd857a352..da59aaf8338 100644 --- a/developer/src/kmanalyze/kmanalyze.vcxproj +++ b/developer/src/kmanalyze/kmanalyze.vcxproj @@ -1,210 +1,210 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {4EAE6DFD-E18A-493D-A00F-4846A2593F56} - Win32Proj - kmanalyze - 10.0 - - - - Application - true - v142 - MultiByte - - - Application - false - v142 - true - MultiByte - - - Application - true - v142 - MultiByte - - - Application - false - v142 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - true - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreadedDebug - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreaded - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreaded - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - true - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreaded - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - true - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - Create - Create - Create - Create - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {4EAE6DFD-E18A-493D-A00F-4846A2593F56} + Win32Proj + kmanalyze + 10.0 + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreadedDebug + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + Create + Create + Create + Create + + + + + + + + \ No newline at end of file diff --git a/developer/src/kmdecomp/kmdecomp.vcxproj b/developer/src/kmdecomp/kmdecomp.vcxproj index 223ae72199e..36cea5f3ff5 100644 --- a/developer/src/kmdecomp/kmdecomp.vcxproj +++ b/developer/src/kmdecomp/kmdecomp.vcxproj @@ -1,171 +1,171 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {963D608A-6689-469C-AE42-F56696DD42CC} - 10.0 - - - - Application - false - MultiByte - v142 - - - Application - false - MultiByte - v142 - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - false - AllRules.ruleset - - - AllRules.ruleset - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(IncludePath) - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(IncludePath) - - - - .\Debug/kmdecomp.tlb - - - - - Disabled - $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - Level3 - true - EditAndContinue - stdcpp17 - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - true - Console - MachineX86 - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - true - .\Debug/kmdecomp.bsc - - - - - .\Release/kmdecomp.tlb - - - - - MaxSpeed - OnlyExplicitInline - $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - MultiThreaded - true - Level3 - true - true - stdcpp17 - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - Console - MachineX86 - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - true - .\Release/kmdecomp.bsc - - - - - NotUsing - NotUsing - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - Create - Create - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + + {963D608A-6689-469C-AE42-F56696DD42CC} + 10.0 + + + + Application + false + MultiByte + v143 + + + Application + false + MultiByte + v143 + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + false + AllRules.ruleset + + + AllRules.ruleset + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(IncludePath) + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(IncludePath) + + + + .\Debug/kmdecomp.tlb + + + + + Disabled + $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + true + EditAndContinue + stdcpp17 + Use + pch.h + + + _DEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + true + true + Console + MachineX86 + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + true + .\Debug/kmdecomp.bsc + + + + + .\Release/kmdecomp.tlb + + + + + MaxSpeed + OnlyExplicitInline + $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + true + Level3 + true + true + stdcpp17 + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + true + Console + MachineX86 + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + true + .\Release/kmdecomp.bsc + + + + + NotUsing + NotUsing + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + Create + Create + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + + + + + + + + + \ No newline at end of file diff --git a/developer/src/samples/imsample/imsample.vcxproj b/developer/src/samples/imsample/imsample.vcxproj index 9abee9aeff3..93413dbe8f2 100644 --- a/developer/src/samples/imsample/imsample.vcxproj +++ b/developer/src/samples/imsample/imsample.vcxproj @@ -1,266 +1,266 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - - {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E} - 10.0 - imsample - - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - - - - - - - - - - - - - - - - - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - .dll - $(ProjectName).x64 - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - .dll - $(ProjectName).x64 - - - - MultiThreadedDebug - Default - true - Disabled - true - Level3 - true - EditAndContinue - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - _DEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - Win32 - - - 0x0c09 - _DEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - true - Windows - $(OutDir)$(TargetName)$(TargetExt) - .\IMSample.lib - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - MultiThreadedDebug - Default - false - Disabled - true - Level3 - ProgramDatabase - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - _DEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - - - 0x0c09 - _DEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - true - Windows - $(OutDir)$(TargetName)$(TargetExt) - $(OutDir)$(TargetName).lib - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - MultiThreaded - OnlyExplicitInline - true - true - MaxSpeed - true - Level3 - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - NDEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - Win32 - - - 0x0c09 - NDEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - Windows - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - MultiThreaded - OnlyExplicitInline - true - true - MaxSpeed - true - Level3 - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - NDEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - - - 0x0c09 - NDEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - Windows - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E} + 10.0 + imsample + + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + .dll + $(ProjectName).x64 + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + .dll + $(ProjectName).x64 + + + + MultiThreadedDebug + Default + true + Disabled + true + Level3 + true + EditAndContinue + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + _DEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + Win32 + + + 0x0c09 + _DEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + true + Windows + $(OutDir)$(TargetName)$(TargetExt) + .\IMSample.lib + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + MultiThreadedDebug + Default + false + Disabled + true + Level3 + ProgramDatabase + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + _DEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + + + 0x0c09 + _DEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + true + Windows + $(OutDir)$(TargetName)$(TargetExt) + $(OutDir)$(TargetName).lib + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + MultiThreaded + OnlyExplicitInline + true + true + MaxSpeed + true + Level3 + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + NDEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + Win32 + + + 0x0c09 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + Windows + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + MultiThreaded + OnlyExplicitInline + true + true + MaxSpeed + true + Level3 + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + NDEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + + + 0x0c09 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + Windows + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman-engine.vcxproj b/windows/src/engine/keyman32/keyman-engine.vcxproj index f958f9a7971..e268b10e229 100644 --- a/windows/src/engine/keyman32/keyman-engine.vcxproj +++ b/windows/src/engine/keyman32/keyman-engine.vcxproj @@ -1,402 +1,402 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {A7069186-5AE7-4504-8926-861B7A34BA9E} - Keyman32 - 10.0 - - - - StaticLibrary - false - v142 - - - StaticLibrary - false - v142 - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - AllRules.ruleset - - - AllRules.ruleset - - - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\core\include;$(IncludePath) - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - MultiThreaded - true - Use - pch.h - All - ./ - ./ - ./ - Level4 - true - true - ProgramDatabase - - - NDEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - keymancore.lib;libicuuc.a;libicuin.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - ./Keyman32.dll - true - keyman32.def - true - ./Keyman32.pdb - true - ./Keyman32.map - false - Windows - true - true - 0x1c100000 - ./Keyman32.lib - MachineX86 - true - true - - - true - ./Keyman32.bsc - - - - - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - Disabled - .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - MultiThreadedDebug - Use - pch.h - All - ./ - ./ - ./ - true - Level4 - true - true - EditAndContinue - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - keymancore.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - ./Keyman32.dll - true - keyman32.def - true - ./Keyman32.pdb - true - ./Keyman32.map - Windows - 0x1C100000 - ./Keyman32.lib - MachineX86 - true - true - - - true - ./Keyman32.bsc - - - - - - - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - Create - Create - - - - - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - Document - false - false - mc %(FullPath) - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - mc %(FullPath) - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - Designer - - - - - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + + {A7069186-5AE7-4504-8926-861B7A34BA9E} + Keyman32 + 10.0 + + + + StaticLibrary + false + v143 + + + StaticLibrary + false + v143 + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + AllRules.ruleset + + + AllRules.ruleset + + + + + $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\core\include;$(IncludePath) + + + $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + ./ + ./ + ./ + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + keymancore.lib;libicuuc.a;libicuin.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + ./Keyman32.dll + true + keyman32.def + true + ./Keyman32.pdb + true + ./Keyman32.map + false + Windows + true + true + 0x1c100000 + ./Keyman32.lib + MachineX86 + true + true + + + true + ./Keyman32.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + ./ + ./ + ./ + true + Level4 + true + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + keymancore.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + ./Keyman32.dll + true + keyman32.def + true + ./Keyman32.pdb + true + ./Keyman32.map + Windows + 0x1C100000 + ./Keyman32.lib + MachineX86 + true + true + + + true + ./Keyman32.bsc + + + + + + + + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + Create + Create + + + + + + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + + Document + false + false + mc %(FullPath) + Compiling keyman-debug-etw + %(Filename).rc;%(Filename.h);MSG00001.bin + mc %(FullPath) + Compiling keyman-debug-etw + %(Filename).rc;%(Filename.h);MSG00001.bin + Designer + + + + + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman32.vcxproj b/windows/src/engine/keyman32/keyman32.vcxproj index 71ae7ec5018..ba15db6516c 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj +++ b/windows/src/engine/keyman32/keyman32.vcxproj @@ -1,466 +1,466 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593} - Keyman32 - 10.0 - keyman32 - - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - AllRules.ruleset - AllRules.ruleset - - - - - AllRules.ruleset - AllRules.ruleset - - - - - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) - keyman32 - - - $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) - keyman64 - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) - keyman32 - - - $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) - keyman64 - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - MultiThreaded - true - Use - pch.h - All - Level4 - true - true - ProgramDatabase - - - NDEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman32.def - true - true - false - Windows - true - true - 0x1c100000 - MachineX86 - true - true - ..\..\..\lib\keyman32.lib - - - true - ./Keyman32.bsc - - - - - - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - ./Keyman32.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - MultiThreaded - true - Use - pch.h - All - Level4 - true - true - ProgramDatabase - - - NDEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman64.def - true - true - false - Windows - true - true - 0x1c100000 - true - true - ..\..\..\lib\keyman64.lib - - - true - ./Keyman64.bsc - - - - - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - Disabled - .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - MultiThreadedDebug - Use - pch.h - All - true - Level4 - true - true - EditAndContinue - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman32.def - true - true - Windows - 0x1C100000 - MachineX86 - true - true - ..\..\..\lib\keyman32.lib - - - true - ./Keyman32.bsc - - - - - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - ./Keyman32.tlb - - - - - Disabled - .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - MultiThreadedDebug - Use - pch.h - All - true - Level4 - true - true - ProgramDatabase - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman64.def - true - true - Windows - 0x1C100000 - true - true - ..\..\..\lib\keyman64.lib - - - true - ./Keyman32.bsc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - - - - - - - - - - Document - false - false - false - false - mc %(FullPath) - mc %(FullPath) - Compiling keyman-debug-etw - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - %(Filename).rc;%(Filename.h);MSG00001.bin - mc %(FullPath) - mc %(FullPath) - Compiling keyman-debug-etw - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - %(Filename).rc;%(Filename.h);MSG00001.bin - Designer - - - true - true - - - - - true - true - - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - true - true - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593} + Keyman32 + 10.0 + keyman32 + + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + false + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + true + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + + + + + + + $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) + keyman32 + + + $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) + keyman64 + + + $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) + keyman32 + + + $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) + keyman64 + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman32.def + true + true + false + Windows + true + true + 0x1c100000 + MachineX86 + true + true + ..\..\..\lib\keyman32.lib + + + true + ./Keyman32.bsc + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman64.def + true + true + false + Windows + true + true + 0x1c100000 + true + true + ..\..\..\lib\keyman64.lib + + + true + ./Keyman64.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + true + Level4 + true + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman32.def + true + true + Windows + 0x1C100000 + MachineX86 + true + true + ..\..\..\lib\keyman32.lib + + + true + ./Keyman32.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + true + Level4 + true + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman64.def + true + true + Windows + 0x1C100000 + true + true + ..\..\..\lib\keyman64.lib + + + true + ./Keyman32.bsc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + Document + false + false + false + false + mc %(FullPath) + mc %(FullPath) + Compiling keyman-debug-etw + Compiling keyman-debug-etw + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + mc %(FullPath) + mc %(FullPath) + Compiling keyman-debug-etw + Compiling keyman-debug-etw + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + Designer + + + true + true + + + + + true + true + + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + true + true + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman32.vcxproj.filters b/windows/src/engine/keyman32/keyman32.vcxproj.filters index 5627c72b04a..cffae6a908e 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj.filters +++ b/windows/src/engine/keyman32/keyman32.vcxproj.filters @@ -1,265 +1,271 @@ - - - - - {2dc5904b-4033-47e6-88ac-ebe442c06c2e} - cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 - - - {27baf0f2-3926-4720-b5f9-045e47cd975c} - h;hpp;hxx;hm;inl;fi;fd - - - {088a2b68-c9db-41f4-9f45-37fdf758931e} - ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Source Files - - - Resource Files - - - Resource Files - - - Source Files - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Source Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - + + + + + {2dc5904b-4033-47e6-88ac-ebe442c06c2e} + cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 + + + {27baf0f2-3926-4720-b5f9-045e47cd975c} + h;hpp;hxx;hm;inl;fi;fd + + + {088a2b68-c9db-41f4-9f45-37fdf758931e} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Resource Files + + + Resource Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj index 48afed6fdd8..5bf2365a59b 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj @@ -1,160 +1,160 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {46399ed7-79a1-4e7e-bd96-4b1aeec838ae} - Win32Proj - 10.0.19041.0 - Application - v142 - Unicode - keyman-engine-tests - - - - - - - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\kmtip\bin\Win32\$(Configuration);$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) - true - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - {a7069186-5ae7-4504-8926-861b7a34ba9e} - - - - - - - - - - Use - pch.h - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - Level3 - %(AdditionalIncludeDirectories) - - - true - Console - version.lib;psapi.lib;imm32.lib;libkeymancore.a;libicuuc.a;libicuin.a;%(AdditionalDependencies) - - - false - - - - - Use - pch.h - Disabled - X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - Level3 - - - true - Console - - - - - Use - pch.h - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - Level3 - ProgramDatabase - - - true - Console - true - true - - - - - Use - pch.h - X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - Level3 - ProgramDatabase - - - true - Console - true - true - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {46399ed7-79a1-4e7e-bd96-4b1aeec838ae} + Win32Proj + 10.0 + Application + v143 + Unicode + keyman-engine-tests + + + + + + + + + $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\kmtip\bin\Win32\$(Configuration);$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + true + + + $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + {a7069186-5ae7-4504-8926-861b7a34ba9e} + + + + + + + + + + Use + pch.h + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + %(AdditionalIncludeDirectories) + + + true + Console + version.lib;psapi.lib;imm32.lib;libkeymancore.a;libicuuc.a;libicuin.a;%(AdditionalDependencies) + + + false + + + + + Use + pch.h + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + + + true + Console + + + + + Use + pch.h + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + + + true + Console + true + true + + + + + Use + pch.h + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + + + true + Console + true + true + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/windows/src/engine/keymanx64/keymanx64.vcxproj b/windows/src/engine/keymanx64/keymanx64.vcxproj index 82b3bcb59f1..2fd59555603 100644 --- a/windows/src/engine/keymanx64/keymanx64.vcxproj +++ b/windows/src/engine/keymanx64/keymanx64.vcxproj @@ -1,153 +1,153 @@ - - - - - Debug - x64 - - - Release - x64 - - - - {A34650EA-D6E8-4229-8091-6BF1443565B5} - keymanx64 - Win32Proj - 10.0 - - - - Application - Unicode - true - v142 - - - Application - Unicode - v142 - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - false - AllRules.ruleset - - - AllRules.ruleset - - - *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi - *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi - - - - X64 - - - Disabled - ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - false - true - EnableFastChecks - MultiThreadedDebug - - - Level4 - true - ProgramDatabase - true - - - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmtd.lib;%(AdditionalDependencies) - ..\..\..\lib;%(AdditionalLibraryDirectories) - false - true - Windows - MachineX64 - true - sentry.dll - - - - - - - - - - - - - - - X64 - - - ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - false - MultiThreaded - - - Level4 - true - ProgramDatabase - true - - - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmt.lib;%(AdditionalDependencies) - ..\..\..\lib;%(AdditionalLibraryDirectories) - false - true - Windows - true - true - MachineX64 - true - true - sentry.dll - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + x64 + + + Release + x64 + + + + {A34650EA-D6E8-4229-8091-6BF1443565B5} + keymanx64 + Win32Proj + 10.0 + + + + Application + Unicode + true + v143 + + + Application + Unicode + v143 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi + + + + X64 + + + Disabled + ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + false + true + EnableFastChecks + MultiThreadedDebug + + + Level4 + true + ProgramDatabase + true + + + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmtd.lib;%(AdditionalDependencies) + ..\..\..\lib;%(AdditionalLibraryDirectories) + false + true + Windows + MachineX64 + true + sentry.dll + + + + + + + + + + + + + + + X64 + + + ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + false + MultiThreaded + + + Level4 + true + ProgramDatabase + true + + + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmt.lib;%(AdditionalDependencies) + ..\..\..\lib;%(AdditionalLibraryDirectories) + false + true + Windows + true + true + MachineX64 + true + true + sentry.dll + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/kmrefresh/kmrefresh.vcxproj b/windows/src/engine/kmrefresh/kmrefresh.vcxproj index a978cb947ed..0b252895f7f 100644 --- a/windows/src/engine/kmrefresh/kmrefresh.vcxproj +++ b/windows/src/engine/kmrefresh/kmrefresh.vcxproj @@ -1,157 +1,157 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {A3B8F75F-E63E-434E-8A2A-034AD9B66571} - kmrefresh - 10.0 - - - - Application - true - Unicode - v142 - - - Application - false - true - Unicode - v142 - - - Application - true - Unicode - v142 - - - Application - false - true - Unicode - v142 - - - - - - - - - - - - - - - - - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectName).x86 - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectName).x86 - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Level3 - Disabled - true - true - MultiThreadedDebug - - - Windows - - - - - Level3 - Disabled - true - true - MultiThreadedDebug - - - Windows - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - Windows - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - Windows - true - true - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {A3B8F75F-E63E-434E-8A2A-034AD9B66571} + kmrefresh + 10.0 + + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectName).x64 + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectName).x86 + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectName).x86 + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectName).x64 + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Level3 + Disabled + true + true + MultiThreadedDebug + + + Windows + + + + + Level3 + Disabled + true + true + MultiThreadedDebug + + + Windows + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + Windows + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + Windows + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/kmtip/kmtip.vcxproj b/windows/src/engine/kmtip/kmtip.vcxproj index f3fb74116fb..173853f507b 100644 --- a/windows/src/engine/kmtip/kmtip.vcxproj +++ b/windows/src/engine/kmtip/kmtip.vcxproj @@ -1,432 +1,432 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {93391ECB-E2F1-4D23-B85E-6FDEB7ACF9B4} - 10.0 - - - - DynamicLibrary - false - MultiByte - v142 - - - DynamicLibrary - false - MultiByte - v142 - - - DynamicLibrary - false - MultiByte - v142 - - - DynamicLibrary - false - MultiByte - v142 - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - false - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - true - true - AllRules.ruleset - AllRules.ruleset - - - - - AllRules.ruleset - AllRules.ruleset - - - - - .dll - .dll - .dll - .dll - kmtip64 - kmtip64 - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./kmtip.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - true - MultiThreaded - true - All - Level3 - true - true - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip.def - true - 0x4C100000 - MachineX86 - true - true - - - true - ./kmtip.bsc - - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - ./kmtip.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - true - MultiThreaded - true - All - Level3 - true - true - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip64.def - true - 0x4C100000 - true - true - - - true - ./kmtip.bsc - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./kmtip.tlb - - - - - Disabled - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - true - Level3 - true - EditAndContinue - true - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip.def - true - true - 0x4C100000 - MachineX86 - true - - - true - ./kmtip.bsc - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - ./kmtip.tlb - - - - - Disabled - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - true - Level3 - true - ProgramDatabase - true - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip64.def - true - true - 0x4C100000 - true - - - true - ./kmtip.bsc - - - - - - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - Create - Create - Create - Create - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - true - true - - - true - true - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {93391ECB-E2F1-4D23-B85E-6FDEB7ACF9B4} + 10.0 + + + + DynamicLibrary + false + MultiByte + v143 + + + DynamicLibrary + false + MultiByte + v143 + + + DynamicLibrary + false + MultiByte + v143 + + + DynamicLibrary + false + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + true + false + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + true + true + true + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + + + + + .dll + .dll + .dll + .dll + kmtip64 + kmtip64 + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./kmtip.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + true + All + Level3 + true + true + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip.def + true + 0x4C100000 + MachineX86 + true + true + + + true + ./kmtip.bsc + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + ./kmtip.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + true + All + Level3 + true + true + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip64.def + true + 0x4C100000 + true + true + + + true + ./kmtip.bsc + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./kmtip.tlb + + + + + Disabled + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + true + Level3 + true + EditAndContinue + true + Use + pch.h + + + _DEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip.def + true + true + 0x4C100000 + MachineX86 + true + + + true + ./kmtip.bsc + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + ./kmtip.tlb + + + + + Disabled + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + true + Level3 + true + ProgramDatabase + true + Use + pch.h + + + _DEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip64.def + true + true + 0x4C100000 + true + + + true + ./kmtip.bsc + + + + + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + Create + Create + Create + Create + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + true + true + + + true + true + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/mcompile/mcompile.vcxproj b/windows/src/engine/mcompile/mcompile.vcxproj index e9c426a330d..99f60fa722a 100644 --- a/windows/src/engine/mcompile/mcompile.vcxproj +++ b/windows/src/engine/mcompile/mcompile.vcxproj @@ -1,134 +1,134 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {5C22DDDA-CEAD-45F1-96B0-1473111156AA} - Win32Proj - mtop - mcompile - 10.0 - - - - Application - true - Unicode - v142 - - - Application - false - true - Unicode - v142 - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - false - *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - false - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDebug - ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc - pch.h - - - Console - true - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Level3 - Use - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc - pch.h - - - Console - true - true - true - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - - - - - - - - - - - - NotUsing - NotUsing - - - - - - - - - - Create - Create - pch.h - pch.h - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + + {5C22DDDA-CEAD-45F1-96B0-1473111156AA} + Win32Proj + mtop + mcompile + 10.0 + + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + false + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + false + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebug + ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc + pch.h + + + Console + true + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc + pch.h + + + Console + true + true + true + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + + Create + Create + pch.h + pch.h + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/testhost/testhost.vcxproj b/windows/src/engine/testhost/testhost.vcxproj index 8b965e942c5..e9d06a87a6f 100644 --- a/windows/src/engine/testhost/testhost.vcxproj +++ b/windows/src/engine/testhost/testhost.vcxproj @@ -1,172 +1,172 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {12818da3-0472-49a5-96fc-a4808a1a77fa} - testhost - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Level3 - true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - true - true - - - - - Level3 - true - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - - - - - Level3 - true - true - true - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - true - true - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {12818da3-0472-49a5-96fc-a4808a1a77fa} + testhost + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/support/etl2log/etl2log.vcxproj b/windows/src/support/etl2log/etl2log.vcxproj index d8e0372cb26..839d6f28868 100644 --- a/windows/src/support/etl2log/etl2log.vcxproj +++ b/windows/src/support/etl2log/etl2log.vcxproj @@ -1,182 +1,182 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {8754791A-42D7-4E93-84F1-97DEF9D644EB} - Win32Proj - etl2log - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - tdh.lib;%(AdditionalDependencies) - - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - tdh.lib;%(AdditionalDependencies) - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - tdh.lib;%(AdditionalDependencies) - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - tdh.lib;%(AdditionalDependencies) - - - - - - - - Create - Create - Create - Create - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {8754791A-42D7-4E93-84F1-97DEF9D644EB} + Win32Proj + etl2log + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + tdh.lib;%(AdditionalDependencies) + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + tdh.lib;%(AdditionalDependencies) + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + tdh.lib;%(AdditionalDependencies) + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + tdh.lib;%(AdditionalDependencies) + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/support/texteditor/Editor.vcxproj b/windows/src/support/texteditor/Editor.vcxproj index 4b01793829a..30879810b34 100644 --- a/windows/src/support/texteditor/Editor.vcxproj +++ b/windows/src/support/texteditor/Editor.vcxproj @@ -1,175 +1,175 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {6804767e-857f-48cc-b843-22bf357a668c} - Editor - 10.0 - - - - Application - true - v142 - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - Application - false - v142 - true - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - Application - true - v142 - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - Application - false - v142 - true - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName)32 - - - false - $(ProjectName)32 - - - true - $(ProjectName)64 - - - false - $(ProjectName)64 - - - - Level3 - true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - $(OutDir)$(TargetName)$(TargetExt) - - - - - Level3 - true - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - Level3 - true - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - - - - - Level3 - true - true - true - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {6804767e-857f-48cc-b843-22bf357a668c} + Editor + 10.0 + + + + Application + true + v143 + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + Application + false + v143 + true + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + Application + true + v143 + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + Application + false + v143 + true + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName)32 + + + false + $(ProjectName)32 + + + true + $(ProjectName)64 + + + false + $(ProjectName)64 + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj b/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj index 77f601a5c59..8c76a595a8c 100644 --- a/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj +++ b/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj @@ -1,176 +1,176 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {748219AC-3BDE-40FC-8E93-7516C4BCE81D} - Win32Proj - cppshareddata - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - true - true - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - true - true - - - - - - - - - Create - Create - Create - Create - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {748219AC-3BDE-40FC-8E93-7516C4BCE81D} + Win32Proj + cppshareddata + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + true + true + + + + + + + + + Create + Create + Create + Create + + + + + \ No newline at end of file From fc0ff9068493eccbba2635a8ce34eaeb8649bb57 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 12 Nov 2025 11:29:58 +0100 Subject: [PATCH 22/37] refactor(developer): rename layr.lists to layr.forms The name `form` better represents the semantic meaning of the structure, and reduces confusion with the `list` section and other uses of `list` in KMX+. Test-bot: skip --- .../types/src/kmx/kmx-plus/kmx-plus-file.ts | 8 ++-- common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 4 +- core/include/ldml/keyman_core_ldml.h | 4 +- core/include/ldml/keyman_core_ldml.ts | 10 ++-- core/src/kmx/kmx_plus.cpp | 32 ++++++------- core/src/kmx/kmx_plus.h | 16 +++---- .../types/kmx/kmx-plus-builder/build-layr.ts | 46 +++++++++---------- developer/src/kmc-ldml/src/compiler/layr.ts | 4 +- .../src/compiler/visual-keyboard-compiler.ts | 6 +-- developer/src/kmc-ldml/src/util/serialize.ts | 4 +- .../src/kmc-ldml/test/fixtures/basic-17.txt | 2 +- .../src/kmc-ldml/test/fixtures/basic-19.txt | 2 +- developer/src/kmc-ldml/test/layr.tests.ts | 18 ++++---- docs/file-formats/kmx-plus-file-format.md | 17 ++++--- 14 files changed, 88 insertions(+), 85 deletions(-) diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts index 5dd07c78606..a99ec0e074e 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts @@ -35,7 +35,7 @@ export class KMXPlusFileFormat extends KMXFile { public readonly COMP_PLUS_LAYR_ENTRY: any; public readonly COMP_PLUS_LAYR_KEY: any; - public readonly COMP_PLUS_LAYR_LIST: any; + public readonly COMP_PLUS_LAYR_FORM: any; public readonly COMP_PLUS_LAYR_ROW: any; public readonly COMP_PLUS_LAYR: any; @@ -172,7 +172,7 @@ export class KMXPlusFileFormat extends KMXFile { key: r.uint32le, // str: key id }); - this.COMP_PLUS_LAYR_LIST = new r.Struct({ + this.COMP_PLUS_LAYR_FORM = new r.Struct({ hardware: STR_REF, // str: hardware name layer: r.uint32le, // index into layers count: r.uint32le, @@ -186,11 +186,11 @@ export class KMXPlusFileFormat extends KMXFile { this.COMP_PLUS_LAYR = new r.Struct({ header: this.COMP_PLUS_SectionHeader, - listCount: r.uint32le, + formCount: r.uint32le, layerCount: r.uint32le, rowCount: r.uint32le, keyCount: r.uint32le, - lists: new r.Array(this.COMP_PLUS_LAYR_LIST, 'listCount'), + forms: new r.Array(this.COMP_PLUS_LAYR_FORM, 'formCount'), layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'), rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'), keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'), diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 127d8a75b87..b494c53f2fc 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -498,7 +498,7 @@ export class Disp extends Section { /** * In-memory `` */ -export class LayrList { +export class LayrForm { hardware: StrsItem; layers: LayrEntry[] = []; minDeviceWidth: number; // millimeters @@ -521,7 +521,7 @@ export class LayrList { }; export class Layr extends Section { - lists: LayrList[] = []; + forms: LayrForm[] = []; }; export class KeysKeys { diff --git a/core/include/ldml/keyman_core_ldml.h b/core/include/ldml/keyman_core_ldml.h index c080d56b53b..21a2e527905 100644 --- a/core/include/ldml/keyman_core_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -47,7 +47,7 @@ #define LDML_KEYS_MOD_SHIFT 0x10 #define LDML_KMXPLUS_VERSION_17 0x1100 #define LDML_KMXPLUS_VERSION_19 0x1300 -#define LDML_LAYR_LIST_HARDWARE_TOUCH "touch" +#define LDML_LAYR_FORM_HARDWARE_TOUCH "touch" #define LDML_LAYR_MAX_MINDEVICEWIDTH 0x3E7 #define LDML_LAYR_MIN_MINDEVICEWIDTH 0x1 #define LDML_LENGTH_BKSP 0xC @@ -68,8 +68,8 @@ #define LDML_LENGTH_KEYS_KMAP 0xC #define LDML_LENGTH_LAYR 0x18 #define LDML_LENGTH_LAYR_ENTRY 0x10 +#define LDML_LENGTH_LAYR_FORM 0x10 #define LDML_LENGTH_LAYR_KEY 0x4 -#define LDML_LENGTH_LAYR_LIST 0x10 #define LDML_LENGTH_LAYR_ROW 0x8 #define LDML_LENGTH_LIST 0x10 #define LDML_LENGTH_LIST_INDEX 0x4 diff --git a/core/include/ldml/keyman_core_ldml.ts b/core/include/ldml/keyman_core_ldml.ts index b6c6fe76930..ee5a45c3be7 100644 --- a/core/include/ldml/keyman_core_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -363,13 +363,13 @@ class Constants { */ readonly length_layr = 24; /** - * Length of each layer list in the 'layr' section variable part + * Length of each layer form in the 'layr' section variable part */ - readonly length_layr_list = 16; + readonly length_layr_form = 16; /** * for the 'hardware' field indicating a touch keyboard, non-hardware */ - readonly layr_list_hardware_touch = 'touch'; + readonly layr_form_hardware_touch = 'touch'; /** * Length of each layer entry in the 'layr' section variable part */ @@ -384,12 +384,12 @@ class Constants { readonly length_layr_key = 4; /** - * Minimum allowed minDeviceWidth for a layer list + * Minimum allowed minDeviceWidth for a layer form */ readonly layr_min_minDeviceWidth = 1; /** - * Maximum allowed minDeviceWidth for a layer list + * Maximum allowed minDeviceWidth for a layer form */ readonly layr_max_minDeviceWidth = 999; diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp index 013697608a0..a5c68ffbd36 100644 --- a/core/src/kmx/kmx_plus.cpp +++ b/core/src/kmx/kmx_plus.cpp @@ -904,7 +904,7 @@ bool COMP_KMXPLUS_LAYR::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { if(!is_block_valid(header, header.headerSize(), sizeof(*this) - + (listCount * sizeof(COMP_KMXPLUS_LAYR_LIST)) + + (formCount * sizeof(COMP_KMXPLUS_LAYR_FORM)) + (layerCount * sizeof(COMP_KMXPLUS_LAYR_ENTRY)) + (rowCount * sizeof(COMP_KMXPLUS_LAYR_ROW)) + (keyCount * sizeof(COMP_KMXPLUS_LAYR_KEY)))) { @@ -923,7 +923,7 @@ COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : is_valid(false) { bool COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { is_valid = false; - lists = nullptr; + forms = nullptr; entries = nullptr; rows = nullptr; keys = nullptr; @@ -940,9 +940,9 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { } KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_LAYR); // skip past non-dynamic portion - // lists (required) - is_valid = is_valid && get_required_section_data_at_offset_and_increment( - data(), header, data()->listCount, offset, lists); + // forms (required) + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->formCount, offset, forms); assert(is_valid); // entries (required) - note, "entryCount" is called "layerCount" in COMP_KMXPLUS_LAYR @@ -962,15 +962,15 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { // Now, validate offsets by walking if (is_valid) { - for(KMX_DWORD i = 0; is_valid && i < data()->listCount; i++) { - const COMP_KMXPLUS_LAYR_LIST &list = lists[i]; + for(KMX_DWORD i = 0; is_valid && i < data()->formCount; i++) { + const COMP_KMXPLUS_LAYR_FORM &form = forms[i]; // is the count off the end? DebugLog( - " %d: hardware s#0x%X, layers %d..%d, minDeviceWidth %.1fmm", i, list.hardware, list.layer, - list.layer + list.count - 1, list.minDeviceWidth * (double)0.1); - if ((list.layer >= data()->layerCount) || (list.layer + list.count > data()->layerCount)) { - DebugLog("COMP_KMXPLUS_LAYR_Helper: list[%d] would access layer %d+%d, > count %d", - i, list.layer, list.count, data()->layerCount); + " %d: hardware s#0x%X, layers %d..%d, minDeviceWidth %.1fmm", i, form.hardware, form.layer, + form.layer + form.count - 1, form.minDeviceWidth * (double)0.1); + if ((form.layer >= data()->layerCount) || (form.layer + form.count > data()->layerCount)) { + DebugLog("COMP_KMXPLUS_LAYR_Helper: form[%d] would access layer %d+%d, > count %d", + i, form.layer, form.count, data()->layerCount); is_valid = false; assert(is_valid); } @@ -1013,13 +1013,13 @@ bool COMP_KMXPLUS_LAYR_Helper::valid() const { return is_valid; } -const COMP_KMXPLUS_LAYR_LIST * -COMP_KMXPLUS_LAYR_Helper::getList(KMX_DWORD list) const { - if (!valid() || list >= data()->listCount) { +const COMP_KMXPLUS_LAYR_FORM * +COMP_KMXPLUS_LAYR_Helper::getForm(KMX_DWORD form) const { + if (!valid() || form >= data()->formCount) { assert(false); return nullptr; } - return lists + list; + return forms + form; } const COMP_KMXPLUS_LAYR_ENTRY * diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index 990e9f86119..139b5ee74c2 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -525,14 +525,14 @@ class COMP_KMXPLUS_DISP_Helper : public COMP_KMXPLUS_Section_HelperlistCount + * @param form index from 0 to layr->formCount */ - const COMP_KMXPLUS_LAYR_LIST *getList(KMX_DWORD list) const; + const COMP_KMXPLUS_LAYR_FORM *getForm(KMX_DWORD form) const; /** - * @param entry index value: COMP_KMXPLUS_LAYR_LIST.layer but less than COMP_KMXPLUS_LAYR_LIST.layer+COMP_KMXPLUS_LAYR_LIST.count + * @param entry index value: COMP_KMXPLUS_LAYR_FORM.layer but less than COMP_KMXPLUS_LAYR_FORM.layer+COMP_KMXPLUS_LAYR_FORM.count */ const COMP_KMXPLUS_LAYR_ENTRY *getEntry(KMX_DWORD entry) const; const COMP_KMXPLUS_LAYR_ROW *getRow(KMX_DWORD row) const; @@ -604,7 +604,7 @@ class COMP_KMXPLUS_LAYR_Helper : public COMP_KMXPLUS_Section_Helper element + * A form that containts a set of layers, the element */ -interface BUILDER_LAYR_LIST { +interface BUILDER_LAYR_FORM { hardware: BUILDER_STR_REF; // hardware or 'touch' - layer: number; // index of first layer in the list, in the - count: number; // number of layer entries in the list + layer: number; // index of first layer in the form, in the + count: number; // number of layer entries in the form minDeviceWidth: number; // width in millimeters _layers: LayrEntry[]; // original layer entry, for in-memory only }; @@ -56,18 +56,18 @@ interface BUILDER_LAYR_KEY { * Builder for the 'keys' section */ export interface BUILDER_LAYR extends BUILDER_SECTION { - listCount: number, // number of entries in lists subtable + formCount: number, // number of entries in forms subtable layerCount: number, // number of entries in layers subtable rowCount: number, // number of entries in rows subtable keyCount: number, // number of entries in keys subtable - lists: BUILDER_LAYR_LIST[], // subtable of elements + forms: BUILDER_LAYR_FORM[], // subtable of elements layers: BUILDER_LAYR_LAYER[], // subtable of elements rows: BUILDER_LAYR_ROW[], // subtable of elements keys: BUILDER_LAYR_KEY[], // subtable of key entries }; export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_list: BUILDER_LIST): BUILDER_LAYR { - if (!kmxplus.layr?.lists) { + if (!kmxplus.layr?.forms) { return null; // if there aren't any layers at all (which should be an invalid keyboard) } @@ -77,28 +77,28 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l size: constants.length_layr, }, _offset: 0, - listCount: kmxplus.layr.lists.length, + formCount: kmxplus.layr.forms.length, layerCount: 0, // calculated below rowCount: 0, // calculated below keyCount: 0, // calculated below - lists: [], + forms: [], layers: [], rows: [], keys: [] }; - layr.lists = kmxplus.layr.lists.map((list) => { - const blist: BUILDER_LAYR_LIST = { - hardware: build_strs_index(sect_strs, list.hardware), + layr.forms = kmxplus.layr.forms.map((form) => { + const bform: BUILDER_LAYR_FORM = { + hardware: build_strs_index(sect_strs, form.hardware), layer: null, // to be set below - _layers: list.layers, - count: list.layers.length, - minDeviceWidth: list.minDeviceWidth, + _layers: form.layers, + count: form.layers.length, + minDeviceWidth: form.minDeviceWidth, }; - return blist; + return bform; }); - // now sort the lists - layr.lists.sort((a, b) => { + // now sort the forms + layr.forms.sort((a, b) => { // sort by string # if (a.hardware < b.hardware) { return -1; @@ -114,9 +114,9 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l } }); // Now allocate the layers, rows, and keys - layr.lists.forEach((list) => { - list.layer = layr.layers.length; // index to first layer in list - const blayers = list._layers.map((layer) => { + layr.forms.forEach((form) => { + form.layer = layr.layers.length; // index to first layer in form + const blayers = form._layers.map((layer) => { const blayer: BUILDER_LAYR_LAYER = { _id: layer.id.value, // original id id: build_strs_index(sect_strs, layer.id), @@ -130,7 +130,7 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l // sort the new layers blayers.sort((a, b) => StrsItem.binaryStringCompare(a._id, b._id)); blayers.forEach((layer) => { - layer.row = layr.rows.length; // index to first row in list + layer.row = layr.rows.length; // index to first row in form layer._rows.forEach((row) => { const brow: BUILDER_LAYR_ROW = { key: layr.keys.length, @@ -153,7 +153,7 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l layr.keyCount = layr.keys.length; const offset = constants.length_layr + - (constants.length_layr_list * layr.listCount) + + (constants.length_layr_form * layr.formCount) + (constants.length_layr_entry * layr.layerCount) + (constants.length_layr_row * layr.rowCount) + (constants.length_layr_key * layr.keyCount); diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts index baaf9282b54..4b53330975a 100644 --- a/developer/src/kmc-ldml/src/compiler/layr.ts +++ b/developer/src/kmc-ldml/src/compiler/layr.ts @@ -7,7 +7,7 @@ import { translateLayerAttrToModifier, validModifier } from '../util/util.js'; import DependencySections = KMXPlus.DependencySections; import Layr = KMXPlus.Layr; -import LayrList = KMXPlus.LayrList; +import LayrList = KMXPlus.LayrForm; import LayrRow = KMXPlus.LayrRow; export class LayrCompiler extends SectionCompiler { @@ -68,7 +68,7 @@ export class LayrCompiler extends SectionCompiler { public compile(sections: DependencySections): Layr { const sect = new Layr(); - sect.lists = this.keyboard3.layers.map((layers) => { + sect.forms = this.keyboard3.layers.map((layers) => { const hardware = sections.strs.allocString(layers.formId, {compileContext: layers}); // Already validated in validate const layerEntries = []; diff --git a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts index 480ac5add52..8a7382e4939 100644 --- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts @@ -60,13 +60,13 @@ export class LdmlKeyboardVisualKeyboardCompiler { let hasVisualKeyboard = false; - for(const layersList of source.layr.lists) { - const formId = layersList.hardware.value; + for(const layersForm of source.layr.forms) { + const formId = layersForm.hardware.value; if(formId == 'touch') { continue; } - for(const layer of layersList.layers) { + for(const layer of layersForm.layers) { const res = this.compileHardwareLayer(source, result, layer, formId); if(res === false) { // failed to compile the layer diff --git a/developer/src/kmc-ldml/src/util/serialize.ts b/developer/src/kmc-ldml/src/util/serialize.ts index 2a7391d0c6a..6f6fe365811 100644 --- a/developer/src/kmc-ldml/src/util/serialize.ts +++ b/developer/src/kmc-ldml/src/util/serialize.ts @@ -177,11 +177,11 @@ export function kmxToXml(kmx: KMXPlus.KMXPlusFile): string { } function getLayers() { - if (!layr?.lists?.length) { + if (!layr?.forms?.length) { return {}; } return { - layers: layr.lists.map(({ hardware, minDeviceWidth, layers }) => ({ + layers: layr.forms.map(({ hardware, minDeviceWidth, layers }) => ({ ...stringToAttr('formId', hardware), ...numberToAttr('minDeviceWidth', minDeviceWidth), layer: layers.map(({ id, mod, rows }) => ({ diff --git a/developer/src/kmc-ldml/test/fixtures/basic-17.txt b/developer/src/kmc-ldml/test/fixtures/basic-17.txt index 81748b400e3..fbd163fb237 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic-17.txt +++ b/developer/src/kmc-ldml/test/fixtures/basic-17.txt @@ -427,7 +427,7 @@ block(layr) # struct COMP_KMXPLUS_LAYR { # TODO-LDML: lots of comment-out ahead. Need to revisit. -block(list) # struct COMP_KMXPLUS_LAYR_LIST { +block(list) # struct COMP_KMXPLUS_LIST { 6c 69 73 74 # KMX_DWORD header.ident; // 0000 Section name - list diff(list,endList) # KMX_DWORD header.size; // 0004 Section length 03 00 00 00 # KMX_DWORD listCount (should be 2) diff --git a/developer/src/kmc-ldml/test/fixtures/basic-19.txt b/developer/src/kmc-ldml/test/fixtures/basic-19.txt index 8ffb67f25ff..f42b7470d87 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic-19.txt +++ b/developer/src/kmc-ldml/test/fixtures/basic-19.txt @@ -433,7 +433,7 @@ block(layr) # struct COMP_KMXPLUS_LAYR { # TODO-LDML: lots of comment-out ahead. Need to revisit. -block(list) # struct COMP_KMXPLUS_LAYR_LIST { +block(list) # struct COMP_KMXPLUS_LIST { 6c 69 73 74 # KMX_DWORD header.ident; // 0000 Section name - list diff(list,endList) # KMX_DWORD header.size; // 0004 Section length 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 diff --git a/developer/src/kmc-ldml/test/layr.tests.ts b/developer/src/kmc-ldml/test/layr.tests.ts index 64e4a3d373a..3f5e75a02d7 100644 --- a/developer/src/kmc-ldml/test/layr.tests.ts +++ b/developer/src/kmc-ldml/test/layr.tests.ts @@ -29,8 +29,8 @@ describe('layr', function () { assert.ok(layr); assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(layr.lists?.length, 1); - const list0 = layr.lists[0]; + assert.equal(layr.forms?.length, 1); + const list0 = layr.forms[0]; assert.ok(list0); assert.equal(list0.layers.length, 1); assert.equal(list0.hardware.value, 'us'); @@ -50,9 +50,9 @@ describe('layr', function () { subpath: 'sections/keys/maximal.xml', callback(sect) { const layr = sect; - assert.equal(layr.lists?.length, 2); + assert.equal(layr.forms?.length, 2); - const listHardware = layr.lists.find(v => v.hardware.value === 'iso'); + const listHardware = layr.forms.find(v => v.hardware.value === 'iso'); assert.ok(listHardware); assert.equal(listHardware.minDeviceWidth, 0); assert.equal(listHardware.layers.length, 2); @@ -74,7 +74,7 @@ describe('layr', function () { assert.equal(hardware1row0.keys.length, 3); allKeysOk(hardware1row0,'q w amarker', 'hardware1row0'); - const listTouch = layr.lists.find(v => v.hardware.value === constants.layr_list_hardware_touch); + const listTouch = layr.forms.find(v => v.hardware.value === constants.layr_form_hardware_touch); assert.ok(listTouch); assert.equal(listTouch.minDeviceWidth, 300); assert.equal(listTouch.layers.length, 1); @@ -123,8 +123,8 @@ describe('layr', function () { callback(sect) { const layr = sect; assert.ok(layr); - assert.equal(layr.lists.length, 1, 'layr.lists.length'); - const layers = layr.lists[0]; + assert.equal(layr.forms.length, 1, 'layr.lists.length'); + const layers = layr.forms[0]; const bymod = layers.layers.map(({id,mod,rows})=>([ id.value, mod, rows[0].keys[0].value, ])); @@ -149,8 +149,8 @@ describe('layr', function () { assert.ok(layr); assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(layr.lists?.length, 1); - const list0 = layr.lists[0]; + assert.equal(layr.forms?.length, 1); + const list0 = layr.forms[0]; assert.ok(list0); assert.equal(list0.layers.length, 1); assert.equal(list0.hardware.value, 'us'); diff --git a/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md index 6c252de5247..1327b6af66f 100644 --- a/docs/file-formats/kmx-plus-file-format.md +++ b/docs/file-formats/kmx-plus-file-format.md @@ -294,32 +294,35 @@ Represents layers on the keyboard. |---|------|------------|------------------------------------------| | 0 | 32 | ident | `layr` | | 4 | 32 | size | int: Length of section | -| 8 | 32 | listCount | int: Number of layer lists | +| 8 | 32 | formCount | int: Number of layer forms | |12 | 32 | layerCount | int: number of layer entries | |16 | 32 | rowCount | int: number of row entries | |20 | 32 | keyCount | int: number of key entries | -|24 | var | lists | layer list sub-table | +|24 | var | forms | layer form sub-table | | - | var | layers | layers sub-table | | - | var | rows | rows sub-table | | - | var | keys | keys sub-table | -### `layr.lists` subtable +### `layr.forms` subtable -Each layer list corresponds to one `` element. -There are `listCount` total lists. +Each layer form corresponds to one `` element. +There are `formCount` total forms. + +Note: renamed from `lists` to `forms` in Keyman 19, to increase clarity +and differentiate from other uses of `list`. | ∆ | Bits | Name | Description | |---|------|------------------|--------------------------------------------| | 0+| 32 | hardware | str: name of hardware layout. | | 4+| 32 | layer | int: index to first layer element | -| 8+| 32 | count | int: number of layer elements in this list | +| 8+| 32 | count | int: number of layer elements in this form | |12+| 32 | minDeviceWidth | int: min device width in millimeters, or 0 | - `hardware` is the name of a form, or the string `touch` See UTS #35 section 7 for details about these values. -Layer lists are sorted by `hardware` string, then minDeviceWidth ascending. +Layer forms are sorted by `hardware` string, then minDeviceWidth ascending. ### `layr.layers` subtable From 34e71c4df2a83652cb6d3624f6c6eedd874a140c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 13 Nov 2025 13:06:16 +0100 Subject: [PATCH 23/37] Apply suggestions from code review Co-authored-by: Eberhard Beilharz --- .../web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts index 47f604ee4b4..ac2c075a3f7 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts @@ -15,11 +15,11 @@ import StrsItem = KMXPlus.StrsItem; ------------------------------------------------------------------ */ /** - * A form that containts a set of layers, the element + * A form that contains a set of layers, the element */ interface BUILDER_LAYR_FORM { hardware: BUILDER_STR_REF; // hardware or 'touch' - layer: number; // index of first layer in the form, in the + layer: number; // index of first layer in the form, in the layers subtable count: number; // number of layer entries in the form minDeviceWidth: number; // width in millimeters _layers: LayrEntry[]; // original layer entry, for in-memory only From a53a1593ecaa6620ebe20bc3d7cd2d1767032dbc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 18 Nov 2025 13:45:06 +0100 Subject: [PATCH 24/37] feat(common): add v19 sections to KMX+ in Core and Developer The disp and layr sections have new v19 layouts, to support the Keyman OSK requirements for epic/embed-osk-in-kmx. This supports writing the new versions of these sections in Developer and loading them into Core, doing transformations where necessary so that Core always works with v19 structures after load. This does not implement the transformations required to support the OSK APIs; that will be implemented in a follow-up PR. Nor is support for writing the OSK data from source .kvks and .keyman-touch-layout supported; this is just the scaffolding for supporting the structures in the KMX+ data. Test-bot: skip --- .../types/src/kmx/kmx-plus/kmx-plus-file.ts | 64 +++++- common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 99 +++++++- core/include/ldml/keyman_core_ldml.h | 85 ++++++- core/include/ldml/keyman_core_ldml.ts | 213 +++++++++++++++--- core/src/kmx/kmx_plus.cpp | 167 ++++++++++++-- core/src/kmx/kmx_plus.h | 45 +++- .../keyman-touch-layout-to-kmx-plus.md | 177 +++++++++++++++ developer/docs/internal/kvk-to-kmx-plus.md | 99 ++++++++ developer/src/common/web/utils/src/index.ts | 2 +- .../web/utils/src/types/kmx/kmx-builder.ts | 39 ++-- .../types/kmx/kmx-plus-builder/build-disp.ts | 27 ++- .../types/kmx/kmx-plus-builder/build-layr.ts | 47 ++-- .../kmx/kmx-plus-builder/kmx-plus-builder.ts | 23 +- .../src/kmc-ldml/src/compiler/compiler.ts | 2 +- developer/src/kmc-ldml/src/compiler/disp.ts | 74 ++++-- .../kmc-ldml/src/compiler/empty-compiler.ts | 22 +- developer/src/kmc-ldml/src/compiler/layr.ts | 10 +- .../kmc-ldml/src/compiler/section-compiler.ts | 6 +- developer/src/kmc-ldml/src/compiler/tran.ts | 14 +- developer/src/kmc-ldml/src/compiler/vars.ts | 6 +- .../src/compiler/visual-keyboard-compiler.ts | 8 +- .../src/kmc-ldml/test/compiler-e2e.tests.ts | 71 +++++- .../src/kmc-ldml/test/dependencies.tests.ts | 4 +- developer/src/kmc-ldml/test/disp.tests.ts | 25 +- .../src/kmc-ldml/test/fixtures/README.md | 25 ++ .../src/kmc-ldml/test/fixtures/basic-17.txt | 23 +- .../src/kmc-ldml/test/fixtures/basic-19.txt | 57 +++-- developer/src/kmc-ldml/test/helpers/index.ts | 13 +- developer/src/kmc-ldml/test/keys.tests.ts | 18 +- developer/src/kmc-ldml/test/layr.tests.ts | 50 ++-- developer/src/kmc-ldml/test/loca.tests.ts | 7 +- developer/src/kmc-ldml/test/meta.tests.ts | 11 +- developer/src/kmc-ldml/test/tran.tests.ts | 6 +- developer/src/kmc-ldml/test/vars.tests.ts | 6 +- docs/build/windows.md | 28 +-- docs/file-formats/kmx-plus-file-format.md | 28 +-- 36 files changed, 1279 insertions(+), 322 deletions(-) create mode 100644 developer/docs/internal/keyman-touch-layout-to-kmx-plus.md create mode 100644 developer/docs/internal/kvk-to-kmx-plus.md create mode 100644 developer/src/kmc-ldml/test/fixtures/README.md diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts index a99ec0e074e..924f9b9f9dd 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus-file.ts @@ -2,6 +2,8 @@ * Keyman is copyright (C) SIL Global. MIT License. * * Created by mcdurdin on 2025-10-08 + * + * Binary file format Restructure structs for KMX+ */ import * as KMX from '../kmx.js'; import * as r from 'restructure'; @@ -24,8 +26,10 @@ export class KMXPlusFileFormat extends KMXFile { public readonly COMP_PLUS_BKSP_ITEM: any; public readonly COMP_PLUS_BKSP: any; - public readonly COMP_PLUS_DISP_ITEM: any; - public readonly COMP_PLUS_DISP: any; + public readonly COMP_PLUS_DISP_ITEM_v17: any; + public readonly COMP_PLUS_DISP_v17: any; + public readonly COMP_PLUS_DISP_ITEM_v19: any; + public readonly COMP_PLUS_DISP_v19: any; public readonly COMP_PLUS_ELEM_ELEMENT: any; public readonly COMP_PLUS_ELEM_STRING: any; @@ -35,9 +39,11 @@ export class KMXPlusFileFormat extends KMXFile { public readonly COMP_PLUS_LAYR_ENTRY: any; public readonly COMP_PLUS_LAYR_KEY: any; - public readonly COMP_PLUS_LAYR_FORM: any; + public readonly COMP_PLUS_LAYR_FORM_v17: any; + public readonly COMP_PLUS_LAYR_FORM_v19: any; public readonly COMP_PLUS_LAYR_ROW: any; - public readonly COMP_PLUS_LAYR: any; + public readonly COMP_PLUS_LAYR_v17: any; + public readonly COMP_PLUS_LAYR_v19: any; public readonly COMP_PLUS_KEYS_FLICK: any; public readonly COMP_PLUS_KEYS_FLICKS: any; @@ -74,7 +80,7 @@ export class KMXPlusFileFormat extends KMXFile { private readonly COMP_PLUS_SectionHeader: any; - constructor(public version: KMXPlusVersion) { + constructor(public readonly version: KMXPlusVersion) { super(); // Binary-correct structures matching kmx_plus.h @@ -123,17 +129,30 @@ export class KMXPlusFileFormat extends KMXFile { // 'bksp' - see 'tran' // 'disp' - this.COMP_PLUS_DISP_ITEM = new r.Struct({ + this.COMP_PLUS_DISP_ITEM_v17 = new r.Struct({ to: STR_REF, id: STR_REF, display: STR_REF, }); - this.COMP_PLUS_DISP = new r.Struct({ + this.COMP_PLUS_DISP_v17 = new r.Struct({ + header: this.COMP_PLUS_SectionHeader, + count: r.uint32le, + baseCharacter: CHAR32, + items: new r.Array(this.COMP_PLUS_DISP_ITEM_v17, 'count'), + }); + + this.COMP_PLUS_DISP_ITEM_v19 = new r.Struct({ + toId: STR_REF, + display: STR_REF, + flags: r.uint32le, + }); + + this.COMP_PLUS_DISP_v19 = new r.Struct({ header: this.COMP_PLUS_SectionHeader, count: r.uint32le, baseCharacter: CHAR32, - items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'), + items: new r.Array(this.COMP_PLUS_DISP_ITEM_v19, 'count'), }); // 'elem' @@ -172,25 +191,48 @@ export class KMXPlusFileFormat extends KMXFile { key: r.uint32le, // str: key id }); - this.COMP_PLUS_LAYR_FORM = new r.Struct({ + this.COMP_PLUS_LAYR_FORM_v17 = new r.Struct({ hardware: STR_REF, // str: hardware name layer: r.uint32le, // index into layers count: r.uint32le, minDeviceWidth: r.uint32le, // integer: millimeters }); + this.COMP_PLUS_LAYR_FORM_v19 = new r.Struct({ + hardware: STR_REF, // str: hardware name + layer: r.uint32le, // index into layers + count: r.uint32le, + minDeviceWidth: r.uint32le, // integer: millimeters + baseLayout: STR_REF, // v19: str: identifier for base layout (reserved) + fontFaceName: STR_REF, // v19: str: font face name + fontSizePct: r.uint32le, // v19: font size in % of default size + flags: r.uint32le, // v19: flags + }); + this.COMP_PLUS_LAYR_ROW = new r.Struct({ key: r.uint32le, count: r.uint32le, }); - this.COMP_PLUS_LAYR = new r.Struct({ + this.COMP_PLUS_LAYR_v17 = new r.Struct({ + header: this.COMP_PLUS_SectionHeader, + formCount: r.uint32le, + layerCount: r.uint32le, + rowCount: r.uint32le, + keyCount: r.uint32le, + forms: new r.Array(this.COMP_PLUS_LAYR_FORM_v17, 'formCount'), + layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'), + rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'), + keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'), + }); + + this.COMP_PLUS_LAYR_v19 = new r.Struct({ header: this.COMP_PLUS_SectionHeader, formCount: r.uint32le, layerCount: r.uint32le, rowCount: r.uint32le, keyCount: r.uint32le, - forms: new r.Array(this.COMP_PLUS_LAYR_FORM, 'formCount'), + forms: new r.Array(this.COMP_PLUS_LAYR_FORM_v19, 'formCount'), layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'), rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'), keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'), diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index b494c53f2fc..4ac971ee542 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -1,5 +1,7 @@ /* * Keyman is copyright (C) SIL Global. MIT License. + * + * KMX+ file format structures and helper functions */ import { constants } from '@keymanapp/ldml-keyboard-constants'; import { ElementString } from './element-string.js'; @@ -481,11 +483,93 @@ export class Bksp extends Tran { } }; +export enum DispItemFlags { + isId = constants.disp_item_flags_is_id, + isSvg = constants.disp_item_flags_is_svg, + + maskHint = constants.disp_item_flags_mask_hint, + + hintPrimary = constants.disp_item_hint_primary << constants.disp_item_flags_shift_hint, + hintNW = constants.disp_item_hint_nw << constants.disp_item_flags_shift_hint, + hintN = constants.disp_item_hint_n << constants.disp_item_flags_shift_hint, + hintNE = constants.disp_item_hint_ne << constants.disp_item_flags_shift_hint, + hintW = constants.disp_item_hint_w << constants.disp_item_flags_shift_hint, + hintE = constants.disp_item_hint_e << constants.disp_item_flags_shift_hint, + hintSW = constants.disp_item_hint_sw << constants.disp_item_flags_shift_hint, + hintS = constants.disp_item_hint_s << constants.disp_item_flags_shift_hint, + hintSE = constants.disp_item_hint_se << constants.disp_item_flags_shift_hint, + + maskKeyCapType = constants.disp_item_flags_mask_key_cap_type, + + keyCapShift = constants.disp_key_cap_shift << constants.disp_item_flags_shift_key_cap_type, + keyCapEnter = constants.disp_key_cap_enter << constants.disp_item_flags_shift_key_cap_type, + keyCapTab = constants.disp_key_cap_tab << constants.disp_item_flags_shift_key_cap_type, + keyCapBksp = constants.disp_key_cap_bksp << constants.disp_item_flags_shift_key_cap_type, + keyCapMenu = constants.disp_key_cap_menu << constants.disp_item_flags_shift_key_cap_type, + keyCapHide = constants.disp_key_cap_hide << constants.disp_item_flags_shift_key_cap_type, + keyCapAlt = constants.disp_key_cap_alt << constants.disp_item_flags_shift_key_cap_type, + keyCapCtrl = constants.disp_key_cap_ctrl << constants.disp_item_flags_shift_key_cap_type, + keyCapCaps = constants.disp_key_cap_caps << constants.disp_item_flags_shift_key_cap_type, + keyCapAbc_Upper = constants.disp_key_cap_abc_upper << constants.disp_item_flags_shift_key_cap_type, + keyCapAbc_Lower = constants.disp_key_cap_abc_lower << constants.disp_item_flags_shift_key_cap_type, + keyCap123 = constants.disp_key_cap_123 << constants.disp_item_flags_shift_key_cap_type, + keyCapSymbol = constants.disp_key_cap_symbol << constants.disp_item_flags_shift_key_cap_type, + keyCapCurrency = constants.disp_key_cap_currency << constants.disp_item_flags_shift_key_cap_type, + keyCapShifted = constants.disp_key_cap_shifted << constants.disp_item_flags_shift_key_cap_type, + keyCapAltgr = constants.disp_key_cap_altgr << constants.disp_item_flags_shift_key_cap_type, + keyCapTableft = constants.disp_key_cap_tableft << constants.disp_item_flags_shift_key_cap_type, + keyCapLalt = constants.disp_key_cap_lalt << constants.disp_item_flags_shift_key_cap_type, + keyCapRalt = constants.disp_key_cap_ralt << constants.disp_item_flags_shift_key_cap_type, + keyCapLctrl = constants.disp_key_cap_lctrl << constants.disp_item_flags_shift_key_cap_type, + keyCapRctrl = constants.disp_key_cap_rctrl << constants.disp_item_flags_shift_key_cap_type, + keyCapLaltctrl = constants.disp_key_cap_laltctrl << constants.disp_item_flags_shift_key_cap_type, + keyCapRaltctrl = constants.disp_key_cap_raltctrl << constants.disp_item_flags_shift_key_cap_type, + keyCapLaltctrlshift = constants.disp_key_cap_laltctrlshift << constants.disp_item_flags_shift_key_cap_type, + keyCapRaltctrlshift = constants.disp_key_cap_raltctrlshift << constants.disp_item_flags_shift_key_cap_type, + keyCapAltshift = constants.disp_key_cap_altshift << constants.disp_item_flags_shift_key_cap_type, + keyCapCtrlshift = constants.disp_key_cap_ctrlshift << constants.disp_item_flags_shift_key_cap_type, + keyCapAltctrlshift = constants.disp_key_cap_altctrlshift << constants.disp_item_flags_shift_key_cap_type, + keyCapLaltshift = constants.disp_key_cap_laltshift << constants.disp_item_flags_shift_key_cap_type, + keyCapRaltshift = constants.disp_key_cap_raltshift << constants.disp_item_flags_shift_key_cap_type, + keyCapLctrlshift = constants.disp_key_cap_lctrlshift << constants.disp_item_flags_shift_key_cap_type, + keyCapRctrlshift = constants.disp_key_cap_rctrlshift << constants.disp_item_flags_shift_key_cap_type, + keyCapLtrenter = constants.disp_key_cap_ltrenter << constants.disp_item_flags_shift_key_cap_type, + keyCapLtrbksp = constants.disp_key_cap_ltrbksp << constants.disp_item_flags_shift_key_cap_type, + keyCapRtlenter = constants.disp_key_cap_rtlenter << constants.disp_item_flags_shift_key_cap_type, + keyCapRtlbksp = constants.disp_key_cap_rtlbksp << constants.disp_item_flags_shift_key_cap_type, + keyCapShiftlock = constants.disp_key_cap_shiftlock << constants.disp_item_flags_shift_key_cap_type, + keyCapShiftedlock = constants.disp_key_cap_shiftedlock << constants.disp_item_flags_shift_key_cap_type, + keyCapZwnj = constants.disp_key_cap_zwnj << constants.disp_item_flags_shift_key_cap_type, + keyCapZwnjios = constants.disp_key_cap_zwnjios << constants.disp_item_flags_shift_key_cap_type, + keyCapZwnjandroid = constants.disp_key_cap_zwnjandroid << constants.disp_item_flags_shift_key_cap_type, + keyCapZwnjgeneric = constants.disp_key_cap_zwnjgeneric << constants.disp_item_flags_shift_key_cap_type, + keyCapSp = constants.disp_key_cap_sp << constants.disp_item_flags_shift_key_cap_type, + keyCapNbsp = constants.disp_key_cap_nbsp << constants.disp_item_flags_shift_key_cap_type, + keyCapNarnbsp = constants.disp_key_cap_narnbsp << constants.disp_item_flags_shift_key_cap_type, + keyCapEnq = constants.disp_key_cap_enq << constants.disp_item_flags_shift_key_cap_type, + keyCapEmq = constants.disp_key_cap_emq << constants.disp_item_flags_shift_key_cap_type, + keyCapEnsp = constants.disp_key_cap_ensp << constants.disp_item_flags_shift_key_cap_type, + keyCapEmsp = constants.disp_key_cap_emsp << constants.disp_item_flags_shift_key_cap_type, + keyCapPunctsp = constants.disp_key_cap_punctsp << constants.disp_item_flags_shift_key_cap_type, + keyCapThsp = constants.disp_key_cap_thsp << constants.disp_item_flags_shift_key_cap_type, + keyCapHsp = constants.disp_key_cap_hsp << constants.disp_item_flags_shift_key_cap_type, + keyCapZwsp = constants.disp_key_cap_zwsp << constants.disp_item_flags_shift_key_cap_type, + keyCapZwj = constants.disp_key_cap_zwj << constants.disp_item_flags_shift_key_cap_type, + keyCapWj = constants.disp_key_cap_wj << constants.disp_item_flags_shift_key_cap_type, + keyCapCgj = constants.disp_key_cap_cgj << constants.disp_item_flags_shift_key_cap_type, + keyCapLtrm = constants.disp_key_cap_ltrm << constants.disp_item_flags_shift_key_cap_type, + keyCapRtlm = constants.disp_key_cap_rtlm << constants.disp_item_flags_shift_key_cap_type, + keyCapSh = constants.disp_key_cap_sh << constants.disp_item_flags_shift_key_cap_type, + keyCapHtab = constants.disp_key_cap_htab << constants.disp_item_flags_shift_key_cap_type, +} + // 'disp' -export class DispItem { - to: StrsItem; - id: StrsItem; +export interface DispItem { + to: StrsItem; // not used in v19 + id: StrsItem; // not used in v19 display: StrsItem; + toId: StrsItem; // v19 + flags: DispItemFlags; // v19 }; export class Disp extends Section { @@ -495,6 +579,11 @@ export class Disp extends Section { // 'layr' +export enum LayrFormFlags { + showBaseLayout = constants.layr_form_flags_show_base_layout, + chiralSeparate = constants.layr_form_flags_chiral_separate, +}; + /** * In-memory `` */ @@ -502,6 +591,10 @@ export class LayrForm { hardware: StrsItem; layers: LayrEntry[] = []; minDeviceWidth: number; // millimeters + baseLayout: StrsItem; // v19 + fontFaceName: StrsItem; // v19 + fontSizePct: number; // v19 (integer percentage) + flags: LayrFormFlags; // v19 }; /** diff --git a/core/include/ldml/keyman_core_ldml.h b/core/include/ldml/keyman_core_ldml.h index 21a2e527905..9380dc84f62 100644 --- a/core/include/ldml/keyman_core_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -21,6 +21,86 @@ #define LDML_CLDR_IMPORT_BASE "cldr" #define LDML_CLDR_TEST_VERSION_LATEST "techpreview" #define LDML_CLDR_VERSION_LATEST "46" +#define LDML_DISP_ITEM_FLAGS_IS_BLANK_KEY 0x20 +#define LDML_DISP_ITEM_FLAGS_IS_DEADKEY 0x10 +#define LDML_DISP_ITEM_FLAGS_IS_FRAME_KEY 0x4 +#define LDML_DISP_ITEM_FLAGS_IS_HIGHLIGHTED 0x8 +#define LDML_DISP_ITEM_FLAGS_IS_ID 0x1 +#define LDML_DISP_ITEM_FLAGS_IS_SVG 0x2 +#define LDML_DISP_ITEM_FLAGS_MASK_FLAGS 0xFFF +#define LDML_DISP_ITEM_FLAGS_MASK_HINT 0xF000 +#define LDML_DISP_ITEM_FLAGS_MASK_KEY_CAP_TYPE 0xFFFF0000 +#define LDML_DISP_ITEM_FLAGS_SHIFT_HINT 0xC +#define LDML_DISP_ITEM_FLAGS_SHIFT_KEY_CAP_TYPE 0x10 +#define LDML_DISP_ITEM_HINT_E 0x5 +#define LDML_DISP_ITEM_HINT_N 0x2 +#define LDML_DISP_ITEM_HINT_NE 0x3 +#define LDML_DISP_ITEM_HINT_NW 0x1 +#define LDML_DISP_ITEM_HINT_PRIMARY 0x0 +#define LDML_DISP_ITEM_HINT_S 0x7 +#define LDML_DISP_ITEM_HINT_SE 0x8 +#define LDML_DISP_ITEM_HINT_SW 0x6 +#define LDML_DISP_ITEM_HINT_W 0x4 +#define LDML_DISP_KEY_CAP_123 0x13 +#define LDML_DISP_KEY_CAP_ABC_LOWER 0x11 +#define LDML_DISP_KEY_CAP_ABC_UPPER 0x10 +#define LDML_DISP_KEY_CAP_ALT 0x19 +#define LDML_DISP_KEY_CAP_ALTCTRLSHIFT 0x66 +#define LDML_DISP_KEY_CAP_ALTGR 0x2 +#define LDML_DISP_KEY_CAP_ALTSHIFT 0x64 +#define LDML_DISP_KEY_CAP_BKSP 0x4 +#define LDML_DISP_KEY_CAP_CAPS 0x3 +#define LDML_DISP_KEY_CAP_CGJ 0x7A +#define LDML_DISP_KEY_CAP_CTRL 0x1 +#define LDML_DISP_KEY_CAP_CTRLSHIFT 0x65 +#define LDML_DISP_KEY_CAP_CURRENCY 0x14 +#define LDML_DISP_KEY_CAP_EMQ 0x85 +#define LDML_DISP_KEY_CAP_EMSP 0x87 +#define LDML_DISP_KEY_CAP_ENQ 0x84 +#define LDML_DISP_KEY_CAP_ENSP 0x86 +#define LDML_DISP_KEY_CAP_ENTER 0x5 +#define LDML_DISP_KEY_CAP_HIDE 0xA +#define LDML_DISP_KEY_CAP_HSP 0x8E +#define LDML_DISP_KEY_CAP_HTAB 0xA2 +#define LDML_DISP_KEY_CAP_LALT 0x56 +#define LDML_DISP_KEY_CAP_LALTCTRL 0x60 +#define LDML_DISP_KEY_CAP_LALTCTRLSHIFT 0x62 +#define LDML_DISP_KEY_CAP_LALTSHIFT 0x67 +#define LDML_DISP_KEY_CAP_LCTRL 0x58 +#define LDML_DISP_KEY_CAP_LCTRLSHIFT 0x69 +#define LDML_DISP_KEY_CAP_LTRBKSP 0x4 +#define LDML_DISP_KEY_CAP_LTRENTER 0x5 +#define LDML_DISP_KEY_CAP_LTRM 0x90 +#define LDML_DISP_KEY_CAP_MENU 0xB +#define LDML_DISP_KEY_CAP_NARNBSP 0x83 +#define LDML_DISP_KEY_CAP_NBSP 0x82 +#define LDML_DISP_KEY_CAP_PUNCTSP 0x8C +#define LDML_DISP_KEY_CAP_RALT 0x57 +#define LDML_DISP_KEY_CAP_RALTCTRL 0x61 +#define LDML_DISP_KEY_CAP_RALTCTRLSHIFT 0x63 +#define LDML_DISP_KEY_CAP_RALTSHIFT 0x68 +#define LDML_DISP_KEY_CAP_RCTRL 0x59 +#define LDML_DISP_KEY_CAP_RCTRLSHIFT 0x70 +#define LDML_DISP_KEY_CAP_RTLBKSP 0x72 +#define LDML_DISP_KEY_CAP_RTLENTER 0x71 +#define LDML_DISP_KEY_CAP_RTLM 0x91 +#define LDML_DISP_KEY_CAP_SH 0xA1 +#define LDML_DISP_KEY_CAP_SHIFT 0x8 +#define LDML_DISP_KEY_CAP_SHIFTED 0x9 +#define LDML_DISP_KEY_CAP_SHIFTEDLOCK 0x74 +#define LDML_DISP_KEY_CAP_SHIFTLOCK 0x73 +#define LDML_DISP_KEY_CAP_SP 0x80 +#define LDML_DISP_KEY_CAP_SYMBOL 0x15 +#define LDML_DISP_KEY_CAP_TAB 0x6 +#define LDML_DISP_KEY_CAP_TABLEFT 0x7 +#define LDML_DISP_KEY_CAP_THSP 0x8D +#define LDML_DISP_KEY_CAP_WJ 0x78 +#define LDML_DISP_KEY_CAP_ZWJ 0x77 +#define LDML_DISP_KEY_CAP_ZWNJ 0x75 +#define LDML_DISP_KEY_CAP_ZWNJANDROID 0x76 +#define LDML_DISP_KEY_CAP_ZWNJGENERIC 0x79 +#define LDML_DISP_KEY_CAP_ZWNJIOS 0x75 +#define LDML_DISP_KEY_CAP_ZWSP 0x81 #define LDML_ELEM_FLAGS_ORDER_BITSHIFT 0x10 #define LDML_ELEM_FLAGS_ORDER_MASK 0xFF0000 #define LDML_ELEM_FLAGS_PREBASE 0x8 @@ -47,6 +127,8 @@ #define LDML_KEYS_MOD_SHIFT 0x10 #define LDML_KMXPLUS_VERSION_17 0x1100 #define LDML_KMXPLUS_VERSION_19 0x1300 +#define LDML_LAYR_FORM_FLAGS_CHIRAL_SEPARATE 0x2 +#define LDML_LAYR_FORM_FLAGS_SHOW_BASE_LAYOUT 0x1 #define LDML_LAYR_FORM_HARDWARE_TOUCH "touch" #define LDML_LAYR_MAX_MINDEVICEWIDTH 0x3E7 #define LDML_LAYR_MIN_MINDEVICEWIDTH 0x1 @@ -68,7 +150,8 @@ #define LDML_LENGTH_KEYS_KMAP 0xC #define LDML_LENGTH_LAYR 0x18 #define LDML_LENGTH_LAYR_ENTRY 0x10 -#define LDML_LENGTH_LAYR_FORM 0x10 +#define LDML_LENGTH_LAYR_FORM_V17 0x10 +#define LDML_LENGTH_LAYR_FORM_V19 0x20 #define LDML_LENGTH_LAYR_KEY 0x4 #define LDML_LENGTH_LAYR_ROW 0x8 #define LDML_LENGTH_LIST 0x10 diff --git a/core/include/ldml/keyman_core_ldml.ts b/core/include/ldml/keyman_core_ldml.ts index ee5a45c3be7..388cd348ed6 100644 --- a/core/include/ldml/keyman_core_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -5,7 +5,6 @@ to be shared between TypeScript and C++ via the generator (below) */ - // NOTICE! // // If you update this file, you *must* be sure to re-run @@ -103,8 +102,8 @@ class Constants { readonly kmxplus_version_19: KMXPlusVersion = KMXPlusVersion.Version19; /* ------------------------------------------------------------------ - * sect section - ------------------------------------------------------------------ */ + * sect section + * ------------------------------------------------------------------ */ /** * Minimum length of the 'sect' section, not including entries @@ -116,8 +115,8 @@ class Constants { readonly length_sect_item = 8; /* ------------------------------------------------------------------ - * bksp section - ------------------------------------------------------------------ */ + * bksp section + * ------------------------------------------------------------------ */ /** * Minimum length of the 'bksp' section, not including entries @@ -133,21 +132,165 @@ class Constants { readonly bksp_flags_error = 0x0001; /* ------------------------------------------------------------------ - * disp section - ------------------------------------------------------------------ */ + * disp section + * ------------------------------------------------------------------ */ /** * Minimum length of the 'disp' section, not including entries */ - readonly length_disp = 16; - /** - * Length of each entry in the 'disp' variable part - */ - readonly length_disp_item = 12; + readonly length_disp = 16; + /** + * Length of each entry in the 'disp' variable part; note size is same in v17 and v19 + */ + readonly length_disp_item = 12; + + + /** + * in disp.item.flags, mask for flag bits (v19+) + */ + readonly disp_item_flags_mask_flags = 0x00000FFF; + + /** + * in disp.item.flags, if set, then item toId value is an id. (v19+) + */ + readonly disp_item_flags_is_id = 0x00000001; + /** + * in disp.item.flags, if set, then item display string is an SVG file. (v19+) + */ + readonly disp_item_flags_is_svg = 0x00000002; + /** + * in disp.item.flags, if set, then draw the key as a frame key (v19+) + */ + readonly disp_item_flags_is_frame_key = 0x00000004; + /** + * in disp.item.flags, if set, then draw the key as highlighted (v19+) + */ + readonly disp_item_flags_is_highlighted = 0x00000008; + /** + * in disp.item.flags, if set, then draw the key as a deadkey (v19+) + */ + readonly disp_item_flags_is_deadkey = 0x00000010; + /** + * in disp.item.flags, if set, then the key is blank and non-interactive (v19+) + */ + readonly disp_item_flags_is_blank_key = 0x00000020; + + // reserved bits 6-11 for future flags + + /** + * in disp.item.flags, mask for hint position (v19+) + */ + readonly disp_item_flags_mask_hint = 0x0000F000; + + /** + * in disp.item.flags, right shift value for masked hint position (v19+) + */ + readonly disp_item_flags_shift_hint = 12; + + /** hint position for primary key cap (v19+) */ + readonly disp_item_hint_primary = 0; + /** hint position for north-west (top left) hint (v19+) */ + readonly disp_item_hint_nw = 1; + /** hint position for north (top) hint (v19+) */ + readonly disp_item_hint_n = 2; + /** hint position for north-east (top right) hint (v19+) */ + readonly disp_item_hint_ne = 3; + /** hint position for west (left) hint (v19+) */ + readonly disp_item_hint_w = 4; + /** hint position for east (right) hint (v19+) */ + readonly disp_item_hint_e = 5; + /** hint position for south west (bottom left) hint (v19+) */ + readonly disp_item_hint_sw = 6; + /** hint position for south (bottom) hint (v19+) */ + readonly disp_item_hint_s = 7; + /** hint position for south east (bottom right) hint (v19+) */ + readonly disp_item_hint_se = 8; + + /** + * in disp.item.flags, mask for key cap type. (v19+) + */ + readonly disp_item_flags_mask_key_cap_type = 0xFFFF0000; + + /** + * in disp.item.flags, right shift value for key cap type. + */ + readonly disp_item_flags_shift_key_cap_type = 16; + + // The following values match web/.../specialCharacters.ts; see + // developer/src/kmc-kmn/test/kmw/constants.tests.ts for more information. + + /** + * dis2.item.flags key cap type, sync with specialCharacters.ts (v19+) + */ + readonly disp_key_cap_shift = 8; + readonly disp_key_cap_enter = 5; + readonly disp_key_cap_tab = 6; + readonly disp_key_cap_bksp = 4; + readonly disp_key_cap_menu = 11; + readonly disp_key_cap_hide = 10; + readonly disp_key_cap_alt = 25; + readonly disp_key_cap_ctrl = 1; + readonly disp_key_cap_caps = 3; + readonly disp_key_cap_abc_upper = 16; // differentiate '*ABC*' and '*abc*' + readonly disp_key_cap_abc_lower = 17; // differentiate '*ABC*' and '*abc*' + readonly disp_key_cap_123 = 19; + readonly disp_key_cap_symbol = 21; + readonly disp_key_cap_currency = 20; + readonly disp_key_cap_shifted = 9; + readonly disp_key_cap_altgr = 2; + readonly disp_key_cap_tableft = 7; + readonly disp_key_cap_lalt = 0x56; + readonly disp_key_cap_ralt = 0x57; + readonly disp_key_cap_lctrl = 0x58; + readonly disp_key_cap_rctrl = 0x59; + readonly disp_key_cap_laltctrl = 0x60; + readonly disp_key_cap_raltctrl = 0x61; + readonly disp_key_cap_laltctrlshift = 0x62; + readonly disp_key_cap_raltctrlshift = 0x63; + readonly disp_key_cap_altshift = 0x64; + readonly disp_key_cap_ctrlshift = 0x65; + readonly disp_key_cap_altctrlshift = 0x66; + readonly disp_key_cap_laltshift = 0x67; + readonly disp_key_cap_raltshift = 0x68; + readonly disp_key_cap_lctrlshift = 0x69; + readonly disp_key_cap_rctrlshift = 0x70; + // Added in Keyman 14.0. + readonly disp_key_cap_ltrenter = 0x05; // Default alias of '*Enter*'. + readonly disp_key_cap_ltrbksp = 0x04; // Default alias of '*BkSp*'. + readonly disp_key_cap_rtlenter = 0x71; + readonly disp_key_cap_rtlbksp = 0x72; + readonly disp_key_cap_shiftlock = 0x73; + readonly disp_key_cap_shiftedlock = 0x74; + readonly disp_key_cap_zwnj = 0x75; // If this one is specified, auto-detection will kick in. + readonly disp_key_cap_zwnjios = 0x75; // The iOS version will be used by default, but the + readonly disp_key_cap_zwnjandroid = 0x76; // Android platform has its own default glyph. + // Added in Keyman 17.0. + // Reference: https://github.com/silnrsi/font-symchar/blob/v4.000/documentation/encoding.md + readonly disp_key_cap_zwnjgeneric = 0x79; // Generic version of ZWNJ (no override) + readonly disp_key_cap_sp = 0x80; // Space + readonly disp_key_cap_nbsp = 0x82; // No-break Space + readonly disp_key_cap_narnbsp = 0x83; // Narrow No-break Space + readonly disp_key_cap_enq = 0x84; // En Quad + readonly disp_key_cap_emq = 0x85; // Em Quad + readonly disp_key_cap_ensp = 0x86; // En Space + readonly disp_key_cap_emsp = 0x87; // Em Space + // TODO: Skipping #-per-em-space + readonly disp_key_cap_punctsp = 0x8c; // Punctuation Space + readonly disp_key_cap_thsp = 0x8d; // Thin Space + readonly disp_key_cap_hsp = 0x8e; // Hair Space + readonly disp_key_cap_zwsp = 0x81; // Zero Width Space + readonly disp_key_cap_zwj = 0x77; // Zero Width Joiner + readonly disp_key_cap_wj = 0x78; // Word Joiner + readonly disp_key_cap_cgj = 0x7a; // Combining Grapheme Joiner + readonly disp_key_cap_ltrm = 0x90; // Left-to-right Mark + readonly disp_key_cap_rtlm = 0x91; // Right-to-left Mark + readonly disp_key_cap_sh = 0xa1; // Soft Hyphen + readonly disp_key_cap_htab = 0xa2; // Horizontal Tabulation + /* ------------------------------------------------------------------ - * elem section - ------------------------------------------------------------------ */ + * elem section + * ------------------------------------------------------------------ */ /** * Minimum length of the 'elem' section, not including entries @@ -235,7 +378,7 @@ class Constants { /* ------------------------------------------------------------------ * finl section - ------------------------------------------------------------------ */ + * ------------------------------------------------------------------ */ /** * Minimum length of the 'finl' section, not including entries @@ -251,8 +394,8 @@ class Constants { readonly finl_flags_error = 0x0001; /* ------------------------------------------------------------------ - * keys section is now keys.kmap - ------------------------------------------------------------------ */ + * keys section is now keys.kmap + * ------------------------------------------------------------------ */ /** * Constant for no modifiers @@ -321,7 +464,7 @@ class Constants { /* ------------------------------------------------------------------ * keys section - ------------------------------------------------------------------ */ + * ------------------------------------------------------------------ */ /** * Minimum length of the 'keys' section not including variable parts @@ -356,16 +499,20 @@ class Constants { /* ------------------------------------------------------------------ * layr section - ------------------------------------------------------------------ */ + * ------------------------------------------------------------------ */ /** * Minimum length of the 'layr' section not including variable parts */ readonly length_layr = 24; /** - * Length of each layer form in the 'layr' section variable part + * Length of each layer form (renamed from 'list' in v19) in the 'layr' section variable part (v17) + */ + readonly length_layr_form_v17 = 16; + /** + * Length of each layer form in the 'layr' section variable part (v19+) */ - readonly length_layr_form = 16; + readonly length_layr_form_v19 = 32; /** * for the 'hardware' field indicating a touch keyboard, non-hardware */ @@ -382,6 +529,14 @@ class Constants { * Length of each key entry in the 'layr' section variable part */ readonly length_layr_key = 4; + /** + * in layr.form.flags, if set, then base layout key caps hints should be shown + */ + readonly layr_form_flags_show_base_layout = 0x00000001; + /** + * in layr.form.flags, if set, then left/righ tCtrl and Alt keys function independently + */ + readonly layr_form_flags_chiral_separate = 0x00000002; /** * Minimum allowed minDeviceWidth for a layer form @@ -395,7 +550,7 @@ class Constants { /* ------------------------------------------------------------------ * list section - ------------------------------------------------------------------ */ + * ------------------------------------------------------------------ */ /** * Minimum length of the 'list' section not including variable parts @@ -412,7 +567,7 @@ class Constants { /* ------------------------------------------------------------------ * loca section - ------------------------------------------------------------------ */ + * ------------------------------------------------------------------ */ /** * Minimum length of the 'loca' section not including variable parts @@ -424,8 +579,8 @@ class Constants { readonly length_loca_item = 4; /* ------------------------------------------------------------------ - * meta section - ------------------------------------------------------------------ */ + * meta section + ------------------------------------------------------------------ */ /** * length of the 'meta' section @@ -437,8 +592,8 @@ class Constants { readonly meta_settings_normalization_disabled = 1; /* ------------------------------------------------------------------ - * strs section - ------------------------------------------------------------------ */ + * strs section + ------------------------------------------------------------------ */ /** * Minimum length of the 'strs' section not including variable parts @@ -450,8 +605,8 @@ class Constants { readonly length_strs_item = 8; /* ------------------------------------------------------------------ - * tran section - ------------------------------------------------------------------ */ + * tran section + * ------------------------------------------------------------------ */ /** * Minimum length of the 'tran' section, not including entries diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp index a5c68ffbd36..11a0b4b1193 100644 --- a/core/src/kmx/kmx_plus.cpp +++ b/core/src/kmx/kmx_plus.cpp @@ -495,19 +495,72 @@ COMP_KMXPLUS_META::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unuse } bool -COMP_KMXPLUS_DISP::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { +COMP_KMXPLUS_DISP::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const { + if(header.version == LDML_KMXPLUS_VERSION_17) { + return valid_17(header, length); + } + + if(header.version == LDML_KMXPLUS_VERSION_19) { + return valid_19(header, length); + } + + assert(false); + return false; +} + +bool +COMP_KMXPLUS_DISP::valid_19(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(header.version != LDML_KMXPLUS_VERSION_19) { + assert(false); + return false; + } + DebugLog("disp: count 0x%X\n", count); + if (!is_block_valid(header, header.headerSize(), sizeof(*this)+(sizeof(entries[0])*count))) { DebugLog("header.size < expected size"); assert(false); return false; } + if (baseCharacter != 0) { DebugLog("disp: baseCharacter str#0x%X", baseCharacter); } for (KMX_DWORD i=0; i str0x%X", i, entries[i].id, entries[i].to, entries[i].display); - if ((entries[i].to == 0 && entries[i].id == 0) || entries[i].display == 0) { + DebugLoad("disp#%d: toId: str0x%X -> str0x%X, flags: 0x%x", i, entries[i].toId, entries[i].display, entries[i].flags); + if (entries[i].toId == 0 || entries[i].display == 0) { + DebugLog("disp must have output/keyId, and must have display"); + assert(false); + return false; + } + //TODO-EMBED-OSK-IN-KMX: validate flags + } + return true; +} + +bool +COMP_KMXPLUS_DISP::valid_17(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(header.version != LDML_KMXPLUS_VERSION_17) { + assert(false); + return false; + } + + DebugLog("disp: count 0x%X\n", count); + + if (!is_block_valid(header, header.headerSize(), sizeof(*this)+(sizeof(entries[0])*count))) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + + if (baseCharacter != 0) { + DebugLog("disp: baseCharacter str#0x%X", baseCharacter); + } + + const COMP_KMXPLUS_DISP_ENTRY_17 *entries17 = reinterpret_cast(&entries[0]); + for (KMX_DWORD i=0; i str0x%X", i, entries17[i].id, entries17[i].to, entries17[i].display); + if ((entries17[i].to == 0 && entries17[i].id == 0) || entries17[i].display == 0) { DebugLog("disp must have either keyId/output, and must have display"); assert(false); return false; @@ -516,6 +569,21 @@ COMP_KMXPLUS_DISP::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unuse return true; } +bool +COMP_KMXPLUS_DISP_Helper::set(const COMP_KMXPLUS_DISP *newDisp) { + if(header.version != LDML_KMXPLUS_VERSION_17 && header.version != LDML_KMXPLUS_VERSION_19) { + return false; + } + + // TODO-EMBED-OSK-IN-KMX - more transforms required for in-memory representation of OSK; convert v17 to v19 in memory + + if(!COMP_KMXPLUS_Section_Helper::set(newDisp)) { + return false; + } + + return true; +} + bool COMP_KMXPLUS_STRS::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { DebugLog("strs: count 0x%X\n", count); @@ -898,18 +966,57 @@ COMP_KMXPLUS_TRAN_Helper::set(const COMP_KMXPLUS_TRAN *newTran) { return is_valid; } +bool +COMP_KMXPLUS_LAYR::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const { + if(header.version == LDML_KMXPLUS_VERSION_17) { + return valid_17(header, length); + } + if(header.version == LDML_KMXPLUS_VERSION_19) { + return valid_19(header, length); + } + assert(false); + return false; +} + +bool +COMP_KMXPLUS_LAYR::valid_19(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(header.version != LDML_KMXPLUS_VERSION_19) { + assert(false); + return false; + } + + if(!is_block_valid(header, header.headerSize(), + sizeof(*this) + + (formCount * sizeof(COMP_KMXPLUS_LAYR_FORM_V19)) + + (layerCount * sizeof(COMP_KMXPLUS_LAYR_ENTRY)) + + (rowCount * sizeof(COMP_KMXPLUS_LAYR_ROW)) + + (keyCount * sizeof(COMP_KMXPLUS_LAYR_KEY)))) { + return false; + } + DebugLog("layr header is valid"); + // Note: We only do minimal validation here because of the + // dynamic structure. See COMP_KMXPLUS_LAYR_Helper.set() (below) + // all remaining checks + return true; +} bool -COMP_KMXPLUS_LAYR::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { +COMP_KMXPLUS_LAYR::valid_17(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unused(length)) const { + if(header.version != LDML_KMXPLUS_VERSION_17) { + assert(false); + return false; + } + if(!is_block_valid(header, header.headerSize(), sizeof(*this) - + (formCount * sizeof(COMP_KMXPLUS_LAYR_FORM)) + + (formCount * sizeof(COMP_KMXPLUS_LAYR_FORM_V17)) + (layerCount * sizeof(COMP_KMXPLUS_LAYR_ENTRY)) + (rowCount * sizeof(COMP_KMXPLUS_LAYR_ROW)) + (keyCount * sizeof(COMP_KMXPLUS_LAYR_KEY)))) { return false; } + DebugLog("layr header is valid"); // Note: We only do minimal validation here because of the // dynamic structure. See COMP_KMXPLUS_LAYR_Helper.set() (below) @@ -917,7 +1024,7 @@ COMP_KMXPLUS_LAYR::valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD _kmn_unuse return true; } -COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : is_valid(false) { +COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : is_valid(false), own_forms(false) { } bool @@ -928,6 +1035,10 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { rows = nullptr; keys = nullptr; + if(header.version != LDML_KMXPLUS_VERSION_17 && header.version != LDML_KMXPLUS_VERSION_19) { + return false; + } + if(!COMP_KMXPLUS_Section_Helper::set(newLayr)) { return false; } @@ -940,11 +1051,34 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { } KMX_DWORD offset = this->header.calculateBaseSize(LDML_LENGTH_LAYR); // skip past non-dynamic portion - // forms (required) - is_valid = is_valid && get_required_section_data_at_offset_and_increment( - data(), header, data()->formCount, offset, forms); - assert(is_valid); - + own_forms = false; + if(header.version == LDML_KMXPLUS_VERSION_17) { + // forms (required) + const COMP_KMXPLUS_LAYR_FORM_V17 *forms_v17; + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->formCount, offset, forms_v17); + assert(is_valid); + if(is_valid) { + own_forms = true; + COMP_KMXPLUS_LAYR_FORM_V19 *localForms = new COMP_KMXPLUS_LAYR_FORM_V19[data()->formCount]; + for(KMX_DWORD i = 0; i < data()->formCount; i++) { + localForms[i].hardware = forms_v17[i].hardware; + localForms[i].layer = forms_v17[i].layer; + localForms[i].count = forms_v17[i].count; + localForms[i].minDeviceWidth = forms_v17[i].minDeviceWidth; + + localForms[i].baseLayout = 0; + localForms[i].fontFaceName = 0; + localForms[i].fontSizePct = 100; + localForms[i].flags = 0; + } + forms = localForms; + } + } else { // header.version == LDML_KMXPLUS_VERSION_19 + is_valid = is_valid && get_required_section_data_at_offset_and_increment( + data(), header, data()->formCount, offset, forms); + assert(is_valid); + } // entries (required) - note, "entryCount" is called "layerCount" in COMP_KMXPLUS_LAYR is_valid = is_valid && get_required_section_data_at_offset_and_increment( data(), header, data()->layerCount, offset, entries); @@ -963,7 +1097,7 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { // Now, validate offsets by walking if (is_valid) { for(KMX_DWORD i = 0; is_valid && i < data()->formCount; i++) { - const COMP_KMXPLUS_LAYR_FORM &form = forms[i]; + const COMP_KMXPLUS_LAYR_FORM_V19 &form = forms[i]; // is the count off the end? DebugLog( " %d: hardware s#0x%X, layers %d..%d, minDeviceWidth %.1fmm", i, form.hardware, form.layer, @@ -974,6 +1108,7 @@ COMP_KMXPLUS_LAYR_Helper::set(const COMP_KMXPLUS_LAYR *newLayr) { is_valid = false; assert(is_valid); } + // TODO-EMBED-OSK-IN-KMX: other validations needed? } for(KMX_DWORD i = 0; is_valid && i < data()->layerCount; i++) { const COMP_KMXPLUS_LAYR_ENTRY &entry = entries[i]; @@ -1013,7 +1148,7 @@ bool COMP_KMXPLUS_LAYR_Helper::valid() const { return is_valid; } -const COMP_KMXPLUS_LAYR_FORM * +const COMP_KMXPLUS_LAYR_FORM_V19 * COMP_KMXPLUS_LAYR_Helper::getForm(KMX_DWORD form) const { if (!valid() || form >= data()->formCount) { assert(false); @@ -1518,7 +1653,11 @@ kmx_plus::kmx_plus(const COMP_KEYBOARD *keyboard, size_t length) assert(valid); return; } - if ( ex->kmxplus.dpKMXPlus + ex->kmxplus.dwKMXPlusSize > length) { + // check individual components to avoid overflow on sum (we'll never get even + // a 2GB file so if both components are < length then we are okay to sum) + if (ex->kmxplus.dpKMXPlus > length || + ex->kmxplus.dwKMXPlusSize > length || + ex->kmxplus.dpKMXPlus + ex->kmxplus.dwKMXPlusSize > length) { DebugLog("dpKMXPlus + dwKMXPlusSize is past the end of the file"); valid = false; assert(valid); diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index 139b5ee74c2..ca73505680d 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -497,42 +497,64 @@ class COMP_KMXPLUS_VARS_Helper : public COMP_KMXPLUS_Section_Helper {}; +class COMP_KMXPLUS_DISP_Helper : public COMP_KMXPLUS_Section_Helper { + virtual bool set(const COMP_KMXPLUS_DISP *newDisp); +}; /* ------------------------------------------------------------------ * layr section ------------------------------------------------------------------ */ -struct COMP_KMXPLUS_LAYR_FORM { +struct COMP_KMXPLUS_LAYR_FORM_V17 { KMX_DWORD_unaligned hardware; KMX_DWORD_unaligned layer; KMX_DWORD_unaligned count; KMX_DWORD_unaligned minDeviceWidth; }; -static_assert(sizeof(struct COMP_KMXPLUS_LAYR_FORM) == LDML_LENGTH_LAYR_FORM, "mismatched size of COMP_KMXPLUS_LAYR_FORM"); +struct COMP_KMXPLUS_LAYR_FORM_V19 { + KMX_DWORD_unaligned hardware; + KMX_DWORD_unaligned layer; + KMX_DWORD_unaligned count; + KMX_DWORD_unaligned minDeviceWidth; + KMXPLUS_STR baseLayout; // v19: str: identifier for base layout (reserved) + KMXPLUS_STR fontFaceName; // v19: str: font face name + KMX_DWORD_unaligned fontSizePct; // v19: font size in % of default size + KMX_DWORD_unaligned flags; // v19: flags +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LAYR_FORM_V17) == LDML_LENGTH_LAYR_FORM_V17, "mismatched size of COMP_KMXPLUS_LAYR_FORM_V17"); +static_assert(sizeof(struct COMP_KMXPLUS_LAYR_FORM_V19) == LDML_LENGTH_LAYR_FORM_V19, "mismatched size of COMP_KMXPLUS_LAYR_FORM_V19"); struct COMP_KMXPLUS_LAYR_ENTRY { KMXPLUS_STR id; @@ -576,6 +598,8 @@ struct COMP_KMXPLUS_LAYR { * @param length length of the section in bytes */ bool valid(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; + bool valid_19(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; + bool valid_17(COMP_KMXPLUS_HEADER const &header, KMX_DWORD length) const; }; /** @@ -584,6 +608,12 @@ struct COMP_KMXPLUS_LAYR { class COMP_KMXPLUS_LAYR_Helper : public COMP_KMXPLUS_Section_Helper { public: COMP_KMXPLUS_LAYR_Helper(); + ~COMP_KMXPLUS_LAYR_Helper() { + if(own_forms) { + delete [] forms; + forms = nullptr; + } + } /** * Initialize the helper to point at a layr section. * @return true if valid @@ -594,7 +624,7 @@ class COMP_KMXPLUS_LAYR_Helper : public COMP_KMXPLUS_Section_HelperformCount */ - const COMP_KMXPLUS_LAYR_FORM *getForm(KMX_DWORD form) const; + const COMP_KMXPLUS_LAYR_FORM_V19 *getForm(KMX_DWORD form) const; /** * @param entry index value: COMP_KMXPLUS_LAYR_FORM.layer but less than COMP_KMXPLUS_LAYR_FORM.layer+COMP_KMXPLUS_LAYR_FORM.count */ @@ -604,7 +634,8 @@ class COMP_KMXPLUS_LAYR_Helper : public COMP_KMXPLUS_Section_Helper determines minDeviceWidth or hardware + font: meta.fontFaceName + fontsize: meta.fontSize + displayUnderlying: lay2.forms.flags.showBaseLayout + defaultHint: resolved at compile time to generate hints for each key + layer[] + id: layr.layers.id + row[] + id (numeric) + key[] + id: key2.keys.id (along with `layer`) + text: key2.keys.text / dis2.display.display + layer: key2.keys.id + nextlayer: key2.switch + font: not supported + fontsize: not supported + sp: map to various 'special' keys by id; see below + pad: adds a "gap" key2.keys + width: map to key2.keys.width / 10 + sk[] + id: key2.keys.id + text: key2.keys.to + layer: key2.keys.id + nextlayer: key2.switch + font: not supported + fontsize: not supported + sp: map to various 'special' keys by id; see below + pad: adds a "gap" key2.keys + width: map to key2.keys.width / 10 + flick{} - see sk + multitap[] - see sk + hint: add to dis2 for the given key +``` + +## `tablet` | `phone` | `desktop` element + +The name of the element drives the selection of the corresponding form data: + + * `tablet` -> sets `minDeviceWidth` to `100` (mm), `hardware` = `touch` + * `phone` -> sets `minDeviceWidth` to `0` (mm), `hardware` = `touch` + * `desktop` -> ignore, as this is not really supported in Keyman at present; + alternative: `hardware` = `us` (4 keyboards in repo that currently use this: + karambolpoular, orma, sxava, sxava_eo) + +## `id` and `layer` properties + +These two properties together will form the identifier of the key. This +identifier will have semantic meaning, and will be used in event generation. + +## `nextlayer` property + +This can be mapped to `key2.switch`. + +## `sp` property + +The value of the `sp` property will drive slightly different behaviors: + + * `0` Default -> Default key type, no additional metadata + * `1` Special -> Sets `dis2.display.flags.isFrameKey` flag + * `2` Special (active) -> Sets `dis2.display.flags.isHighlighted` and + `dis2.display.flags.isFrameKey` flags + * `8`: Deadkey -> Sets `dis2.display.flags.isDeadKey` flag (styling only) + * `9`: Blank -> Sets `dis2.display.flags.isBlankKey` flag (styling and + interactivity) + * `10`: Spacer -> Adds a `gap` key with the required width + +# LDML table notes + +## `key2` + +### C7043.2.15 `key2`—Extended keybag + +| ∆ | Bits | Name | Description | Content +|---|------|-------------|------------------------------------------|----------------------- +| 0 | 32 | ident | `key2` | `key2` +| 4 | 32 | size | int: Length of section | size in bytes +| 8 | 32 | keyCount | int: Number of keys | # keys from .ktl +|12 | 32 | flicksCount | int: Number of flick lists | +|16 | 32 | flickCount | int: Number of flick elements | +|20 | 32 | kmapCount | int: Number of kmap elements | `0` +|24 | var | keys | keys sub-table | +| - | var | flicks | flick lists sub-table | +| - | var | flick | flick elements sub-table | +| - | var | kmap | key map sub-table | + +#### `key2.keys` subtable + +For each `key` element in .ktl: + +| ∆ | Bits | Name | Description | Content +|---|------|---------------- |----------------------------------------------------------|----------------------- +| 0+| 32 | to | str: output string OR UTF-32LE codepoint | `key.text`* +| 4+| 32 | flags | int: per-key flags | bit 1 if gap +| 8+| 32 | id | str: key id | `key.layer` + ':' + `key.id`? +|12+| 32 | switch | str: layer id to switch to | `key.nextlayer` +|16+| 32 | width | int: key width*10 (supports 0.1 as min width) | `key.width / 10` +|20+| 32 | longPress | list: index into `list` with longPress key id list or 0 | +|24+| 32 | longPressDefault | str: default longpress key id or 0 | +|28+| 32 | multiTap | list: index into `list` sect multiTap key id list or 0 | +|32+| 32 | flicks | int: index into `key2.flicks` subtable | + +* specials will be mapped to the corresponding `dis2`.flags.specialKeyCap value + +## `dis2` + + + +## `layr` + +| ∆ | Bits | Name | Description | Content +|---|------|------------|------------------------------------------|----------------------- +| 0 | 32 | ident | `layr` | `layr` +| 4 | 32 | size | int: Length of section | size in bytes +| 8 | 32 | listCount | int: Number of layer lists | # layer lists +|12 | 32 | layerCount | int: number of layer entries | # layer entries +|16 | 32 | rowCount | int: number of row entries | # row entries +|20 | 32 | keyCount | int: number of key entries | # key entries +|24 | var | lists | layer list sub-table | +| - | var | layers | layers sub-table | +| - | var | rows | rows sub-table | +| - | var | keys | keys sub-table | + +### `layr.lists` + +| ∆ | Bits | Name | Description | Content +|---|------|------------------|--------------------------------------------|----------------------- +| 0+| 32 | hardware | str: name of hardware layout. | `us` (default) or `iso` <- kvkh102 +| 4+| 32 | layer | int: index to first layer element | `0` +| 8+| 32 | count | int: number of layer elements in this list | # layer elements <- modifiers +|12+| 32 | minDeviceWidth | int: min device width in millimeters, or 0 | `0` + +### `layr.layers` + +Each layer corresponds to a .kvks `visualkeyboard/encoding[@name=unicode]/layer` element. + +The `layer[@shift]` mapping is from [`VisualKeyboardLegalShiftStates\[\].name`](https://github.com/keymanapp/keyman/blob/7ac6bfc189333c5758fb14ef1cc0c810e1460b59/common/web/types/src/kvk/visual-keyboard.ts#L63) to keys.key.mod +and `K_MODIFIERFLAG` bitmask (this is not the same as the KVK bitmasks, which shall be deprecated) + +The compiler starts by filling out a blank representation of each modifier layer, then +iterates through the .kvk `visualkeyboard/encoding/layer/key`, keying off the attribute `vkey`. + +| ∆ | Bits | Name | Description | Content +|---|------|------------|------------------------------------------------|----------------------- +| 0+| 32 | id | str: layer id such as `base` or `shift` | <-- modifier name +| 4+| 32 | mod | int: modifier key flags | <-- `K_MODIFIERFLAG` bitmask +| 8+| 32 | row | int: index into rows area (next section) | index to rows +|12+| 32 | count | int: number of `rows` elements for this layer | # rows + +### `layr.rows` + +| ∆ | Bits | Name | Description | Content +|---|------|------------|----------------------------------------|----------------------- +| 0+| 32 | key | int: index into key element | index to keys +| 4+| 32 | count | int: count of key elements in this row | # keys + +Each row should have the full set of 'white' keys, even if they are not all used. Note that `us` +and `iso` keyboards have different row counts for rows 2-4. + +### `layr.keys` + +| ∆ | Bits | Name | Description | Content +|---|------|---------|------------------------------------------|----------------------- +| 0+| 32 | key | str: key id | key id + +? this implies that key.id is required, though key2.keys subtable says it is optional. diff --git a/developer/docs/internal/kvk-to-kmx-plus.md b/developer/docs/internal/kvk-to-kmx-plus.md new file mode 100644 index 00000000000..1a04b7cd847 --- /dev/null +++ b/developer/docs/internal/kvk-to-kmx-plus.md @@ -0,0 +1,99 @@ +--- +title: Mapping from .kvk to LDML +--- + +Note that counts and headers will need to merge both .kvks and .keyman-touch-layout. + +# .kvks notes + +``` +visualkeyboard + header + version: ignore + kbdname: ignore + flags + key102: determines lay2.forms.hardware `us` or `iso`, and the number of keys in row 4 of hardware + displayunderlying: lay2.forms.flags.showBaseLayout + usealtgr: lay2.forms.flags.chiralSeparate + useunderlying: ignore (unused) + layout: ignore + encoding + @name: only recognize 'unicode' encoding, ignore 'ansi' + fontname, fontsize: meta.fontFaceName, meta.fontFaceSize + layer + @shift: see layr.layers discussion + key + @vkey: map to string +``` + +# LDML table notes + +## `key2` + +### C7043.2.15 `key2`—Extended keybag + +| ∆ | Bits | Name | Description | Content +|---|------|-------------|------------------------------------------|----------------------- +| 0 | 32 | ident | `key2` | `key2` +| 4 | 32 | size | int: Length of section | size in bytes +| 8 | 32 | keyCount | int: Number of keys | # keys from .kvks +|12 | 32 | flicksCount | int: Number of flick lists | `0` +|16 | 32 | flickCount | int: Number of flick elements | `0` +|20 | 32 | kmapCount | int: Number of kmap elements | `0`? +|24 | var | keys | keys sub-table | +| - | var | flicks | flick lists sub-table | +| - | var | flick | flick elements sub-table | +| - | var | kmap | key map sub-table | + +#### `key2.keys` subtable + +For each `key` element in .kvks: + +| ∆ | Bits | Name | Description | Content +|---|------|---------------- |----------------------------------------------------------|----------------------- +| 0+| 32 | to | str: output string OR UTF-32LE codepoint | content of the `key` element +| 4+| 32 | flags | int: per-key flags | (optimization only) +| 8+| 32 | id | str: key id | key vk name + modifier +|12+| 32 | switch | str: layer id to switch to | `0` +|16+| 32 | width | int: key width*10 (supports 0.1 as min width) | `1` +|20+| 32 | longPress | list: index into `list` with longPress key id list or 0 | `0` +|24+| 32 | longPressDefault | str: default longpress key id or 0 | `0` +|28+| 32 | multiTap | list: index into `list` sect multiTap key id list or 0 | `0` +|32+| 32 | flicks | int: index into `key2.flicks` subtable | `0` + +Graphical keys should have a corresponding element added to `dis2`. + +### `layr.layers` + +Each layer corresponds to a .kvks `visualkeyboard/encoding[@name=unicode]/layer` element. + +The `layer[@shift]` mapping is from [`VisualKeyboardLegalShiftStates[].name`](https://github.com/keymanapp/keyman/blob/7ac6bfc189333c5758fb14ef1cc0c810e1460b59/common/web/types/src/kvk/visual-keyboard.ts#L63) to keys.key.mod +and `K_MODIFIERFLAG` bitmask (this is not the same as the KVK bitmasks, which shall be deprecated) + +The compiler starts by filling out a blank representation of each modifier layer, then +iterates through the .kvk `visualkeyboard/encoding/layer/key`, keying off the attribute `vkey`. + +| ∆ | Bits | Name | Description | Content +|---|------|------------|------------------------------------------------|----------------------- +| 0+| 32 | id | str: layer id such as `base` or `shift` | <-- modifier name +| 4+| 32 | mod | int: modifier key flags | <-- `K_MODIFIERFLAG` bitmask +| 8+| 32 | row | int: index into rows area (next section) | index to rows +|12+| 32 | count | int: number of `rows` elements for this layer | # rows + +### `layr.rows` + +| ∆ | Bits | Name | Description | Content +|---|------|------------|----------------------------------------|----------------------- +| 0+| 32 | key | int: index into key element | index to keys +| 4+| 32 | count | int: count of key elements in this row | # keys + +Each row should have the full set of 'white' keys, even if they are not all used. Note that `us` +and `iso` keyboards have different row counts for rows 2-4. + +### `layr.keys` + +| ∆ | Bits | Name | Description | Content +|---|------|---------|------------------------------------------|----------------------- +| 0+| 32 | key | str: key id | key id + +? this implies that key.id is required, though key2.keys subtable says it is optional. diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index a4141395844..32f55e1294b 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -24,7 +24,7 @@ export { TouchLayoutFileReader } from './types/keyman-touch-layout/keyman-touch- export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './types/keyman-touch-layout/keyman-touch-layout-file-writer.js'; export { default as KMXBuilder } from './types/kmx/kmx-builder.js'; -export { default as KMXPlusBuilder} from './types/kmx/kmx-plus-builder/kmx-plus-builder.js'; +export { default as KMXPlusBuilder, SectionBuilders } from './types/kmx/kmx-plus-builder/kmx-plus-builder.js'; export * as LDMLKeyboard from './types/ldml-keyboard/ldml-keyboard-xml.js'; export { LDMLKeyboardTestDataXMLSourceFile } from './types/ldml-keyboard/ldml-keyboard-testdata-xml.js'; diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts index c19fdb368d9..f9118b4ba59 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts @@ -1,5 +1,10 @@ -import { KMXPlus, KMX } from '@keymanapp/common-types'; +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Build a full KMX+ file + */ import * as r from 'restructure'; +import { KMXPlus, KMX } from '@keymanapp/common-types'; import KMXPlusBuilder from './kmx-plus-builder/kmx-plus-builder.js'; import KMXPlusFile = KMXPlus.KMXPlusFile; @@ -14,22 +19,18 @@ import BUILDER_COMP_GROUP = KMX.BUILDER_COMP_GROUP; import BUILDER_COMP_KEY = KMX.BUILDER_COMP_KEY; export default class KMXBuilder { - file: KMXFile; - base_keyboard: number = 0; - base_kmxplus: number = 0; - comp_header: BUILDER_COMP_KEYBOARD; - comp_kmxplus: BUILDER_COMP_KEYBOARD_KMXPLUSINFO; - comp_stores: {base: number, store: STORE, obj: BUILDER_COMP_STORE}[] = []; - comp_groups: {base: number, group: GROUP, obj: BUILDER_COMP_GROUP, keys: {base: number, key: KEY, obj: BUILDER_COMP_KEY}[]}[] = []; - comp_kmxplus_data: Uint8Array; - writeDebug: boolean = false; - - constructor(file: KMXFile, writeDebug: boolean) { - this.file = file; - this.writeDebug = writeDebug; + private base_keyboard: number = 0; + private base_kmxplus: number = 0; + private comp_header: BUILDER_COMP_KEYBOARD; + private comp_kmxplus: BUILDER_COMP_KEYBOARD_KMXPLUSINFO; + private comp_stores: {base: number, store: STORE, obj: BUILDER_COMP_STORE}[] = []; + private comp_groups: {base: number, group: GROUP, obj: BUILDER_COMP_GROUP, keys: {base: number, key: KEY, obj: BUILDER_COMP_KEY}[]}[] = []; + private comp_kmxplus_data: Uint8Array; + + constructor(private file: KMXFile, private writeDebug: boolean) { } - calculateStringOffsetAndSize(string: string, base: number, requireString: boolean = false) { + private calculateStringOffsetAndSize(string: string, base: number, requireString: boolean = false) { if(string.length == 0 && !requireString) { // Zero length strings take up no space in the file, and // are treated as a 'null string' @@ -150,17 +151,17 @@ export default class KMXBuilder { return size; } - buildBitmap() { + private buildBitmap() { // TODO return 0; } - buildKMXPlus(base: number) { + private buildKMXPlus(base: number) { if(!(this.file instanceof KMXPlusFile)) { return 0; } - const plusbuilder: KMXPlusBuilder = new KMXPlusBuilder(this.file, this.writeDebug); + const plusbuilder: KMXPlusBuilder = new KMXPlusBuilder(this.file); this.comp_kmxplus_data = plusbuilder.compile(); this.comp_kmxplus = { dpKMXPlus: base, @@ -170,7 +171,7 @@ export default class KMXBuilder { return this.comp_kmxplus.dwKMXPlusSize; } - setString(file: Uint8Array, pos: number, str: string, requireString: boolean = false): void { + private setString(file: Uint8Array, pos: number, str: string, requireString: boolean = false): void { if(requireString && !str.length) { // Just write zero terminator, as r.String for a zero-length string // seems to fail. diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts index 86e4b33367c..3e7dca1efec 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts @@ -1,4 +1,4 @@ -import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { constants, KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; import { KMXPlus } from "@keymanapp/common-types"; import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js"; import { BUILDER_SECTION } from './builder-section.js'; @@ -13,9 +13,14 @@ import KMXPlusData = KMXPlus.KMXPlusData; * Builder for the 'disp' section */ interface BUILDER_DISP_ITEM { - to: BUILDER_STR_REF; - id: BUILDER_STR_REF; + // v17 + to: BUILDER_STR_REF; // not used in v19 + id: BUILDER_STR_REF; // not used in v19 display: BUILDER_STR_REF; + + // v19+ + toId: BUILDER_STR_REF; + flags: KMXPlus.DispItemFlags; }; export interface BUILDER_DISP extends BUILDER_SECTION { @@ -24,7 +29,7 @@ export interface BUILDER_DISP extends BUILDER_SECTION { items: BUILDER_DISP_ITEM[]; }; -export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_DISP { +export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, version: KMXPlusVersion): BUILDER_DISP { if(!kmxplus.disp.disps.length && !kmxplus.disp.baseCharacter.value) { return null; } @@ -32,7 +37,8 @@ export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD const disp: BUILDER_DISP = { header: { ident: constants.hex_section_id(constants.section.disp), - size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length, + size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length, // note size is same in v17 and v19 but content differs + version: version == KMXPlusVersion.Version17 ? KMXPlusVersion.Version17 : KMXPlusVersion.Version19, }, _offset: 0, count: kmxplus.disp.disps.length, @@ -41,10 +47,17 @@ export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD }; for(const item of kmxplus.disp.disps) { + // Only allocate strings that we are actually going to use + const to = version == KMXPlusVersion.Version17 ? build_strs_index(sect_strs, item.to) : 0; + const id = version == KMXPlusVersion.Version17 ? build_strs_index(sect_strs, item.id) : 0; + const toId = version == KMXPlusVersion.Version19 ? build_strs_index(sect_strs, item.toId) : 0; disp.items.push({ - to: build_strs_index(sect_strs, item.to), - id: build_strs_index(sect_strs, item.id), + to, + id, display: build_strs_index(sect_strs, item.display), + // v19 + toId, + flags: item.flags, }); } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts index 47f604ee4b4..40f52926dc5 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts @@ -1,8 +1,7 @@ -import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { constants, KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus } from "@keymanapp/common-types"; import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js"; -import { BUILDER_LIST } from "./build-list.js"; import { BUILDER_SECTION } from "./builder-section.js"; import KMXPlusData = KMXPlus.KMXPlusData; @@ -15,7 +14,7 @@ import StrsItem = KMXPlus.StrsItem; ------------------------------------------------------------------ */ /** - * A form that containts a set of layers, the element + * A layer form, containing a set of layers, the element */ interface BUILDER_LAYR_FORM { hardware: BUILDER_STR_REF; // hardware or 'touch' @@ -23,6 +22,10 @@ interface BUILDER_LAYR_FORM { count: number; // number of layer entries in the form minDeviceWidth: number; // width in millimeters _layers: LayrEntry[]; // original layer entry, for in-memory only + baseLayout: BUILDER_STR_REF; // v19: base layout (e.g. 'us') + fontFaceName: BUILDER_STR_REF; // v19: face name of font for key caps + fontSizePct: number; // v19: font size in % of default size for implementation, typically 100 + flags: KMXPlus.LayrFormFlags; // v19: flags }; /** @@ -56,17 +59,17 @@ interface BUILDER_LAYR_KEY { * Builder for the 'keys' section */ export interface BUILDER_LAYR extends BUILDER_SECTION { - formCount: number, // number of entries in forms subtable + formCount: number, // number of entries in forms subtable (renamed in v19, formerly, listCount) layerCount: number, // number of entries in layers subtable rowCount: number, // number of entries in rows subtable keyCount: number, // number of entries in keys subtable - forms: BUILDER_LAYR_FORM[], // subtable of elements + forms: BUILDER_LAYR_FORM[], // subtable of elements (renamed in v19, formerly, lists) layers: BUILDER_LAYR_LAYER[], // subtable of elements rows: BUILDER_LAYR_ROW[], // subtable of elements keys: BUILDER_LAYR_KEY[], // subtable of key entries }; -export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_list: BUILDER_LIST): BUILDER_LAYR { +export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, version: KMXPlusVersion): BUILDER_LAYR { if (!kmxplus.layr?.forms) { return null; // if there aren't any layers at all (which should be an invalid keyboard) } @@ -74,7 +77,8 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l const layr: BUILDER_LAYR = { header: { ident: constants.hex_section_id(constants.section.layr), - size: constants.length_layr, + size: 0, // calculated below + version: version == KMXPlusVersion.Version17 ? KMXPlusVersion.Version17 : KMXPlusVersion.Version19, }, _offset: 0, formCount: kmxplus.layr.forms.length, @@ -84,19 +88,23 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l forms: [], layers: [], rows: [], - keys: [] + keys: [], }; - layr.forms = kmxplus.layr.forms.map((form) => { - const bform: BUILDER_LAYR_FORM = { + layr.forms = kmxplus.layr.forms.map((form) => { + return { hardware: build_strs_index(sect_strs, form.hardware), layer: null, // to be set below _layers: form.layers, count: form.layers.length, minDeviceWidth: form.minDeviceWidth, + baseLayout: build_strs_index(sect_strs, form.baseLayout), + fontFaceName: build_strs_index(sect_strs, form.fontFaceName), + fontSizePct: form.fontSizePct, + flags: form.flags, }; - return bform; }); + // now sort the forms layr.forms.sort((a, b) => { // sort by string # @@ -152,11 +160,16 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l layr.rowCount = layr.rows.length; layr.keyCount = layr.keys.length; - const offset = constants.length_layr + - (constants.length_layr_form * layr.formCount) + - (constants.length_layr_entry * layr.layerCount) + - (constants.length_layr_row * layr.rowCount) + - (constants.length_layr_key * layr.keyCount); - layr.header.size = offset; + layr.header.size = version == KMXPlusVersion.Version17 + ? constants.length_layr + + (constants.length_layr_form_v17 * layr.formCount) + + (constants.length_layr_entry * layr.layerCount) + + (constants.length_layr_row * layr.rowCount) + + (constants.length_layr_key * layr.keyCount) + : constants.length_layr + + (constants.length_layr_form_v19 * layr.formCount) + + (constants.length_layr_entry * layr.layerCount) + + (constants.length_layr_row * layr.rowCount) + + (constants.length_layr_key * layr.keyCount); return layr; } diff --git a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts index f5b2b867b06..c5cb7330ab3 100644 --- a/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts +++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts @@ -20,7 +20,7 @@ import KMXPlusFile = KMXPlus.KMXPlusFile; type BUILDER_BKSP = BUILDER_TRAN; // type BUILDER_FINL = BUILDER_TRAN; -type SectionBuilders = { +export type SectionBuilders = { // [id in SectionIdent]: BUILDER_SECTION; sect?: BUILDER_SECT; bksp?: BUILDER_BKSP; @@ -38,16 +38,12 @@ type SectionBuilders = { }; export default class KMXPlusBuilder { - private file: KMXPlusFile; - //private writeDebug: boolean; - sect : SectionBuilders = { }; - constructor(file: KMXPlusFile, _writeDebug: boolean) { + constructor(private file: KMXPlusFile) { this.file = file; - //this.writeDebug = _writeDebug; } public compile(): Uint8Array { @@ -57,11 +53,18 @@ export default class KMXPlusBuilder { this.emitSection(file, this.file.COMP_PLUS_SECT, this.sect.sect); // Keep the rest of these in order. this.emitSection(file, this.file.COMP_PLUS_BKSP, this.sect.bksp); - this.emitSection(file, this.file.COMP_PLUS_DISP, this.sect.disp); + this.emitSection( + file, + this.file.version == KMXPlusVersion.Version17 ? this.file.COMP_PLUS_DISP_v17 : this.file.COMP_PLUS_DISP_v19, + this.sect.disp + ); this.emitSection(file, this.file.COMP_PLUS_ELEM, this.sect.elem); this.emitElements(file); this.emitSection(file, this.file.COMP_PLUS_KEYS, this.sect.keys); - this.emitSection(file, this.file.COMP_PLUS_LAYR, this.sect.layr); + this.emitSection(file, + this.file.version == KMXPlusVersion.Version17 ? this.file.COMP_PLUS_LAYR_v17 : this.file.COMP_PLUS_LAYR_v19, + this.sect.layr + ); this.emitSection(file, this.file.COMP_PLUS_LIST, this.sect.list); this.emitSection(file, this.file.COMP_PLUS_LOCA, this.sect.loca); this.emitSection(file, this.file.COMP_PLUS_META, this.sect.meta); @@ -87,9 +90,9 @@ export default class KMXPlusBuilder { const build_bksp = build_tran; this.sect.bksp = build_bksp(this.file.kmxplus.bksp, this.sect.strs, this.sect.elem); - this.sect.disp = build_disp(this.file.kmxplus, this.sect.strs); + this.sect.disp = build_disp(this.file.kmxplus, this.sect.strs, this.file.version); this.sect.keys = build_keys(this.file.kmxplus, this.sect.strs, this.sect.list); - this.sect.layr = build_layr(this.file.kmxplus, this.sect.strs, this.sect.list); + this.sect.layr = build_layr(this.file.kmxplus, this.sect.strs, this.file.version); this.sect.loca = build_loca(this.file.kmxplus, this.sect.strs); this.sect.meta = build_meta(this.file.kmxplus, this.sect.strs); this.sect.tran = build_tran(this.file.kmxplus.tran, this.sect.strs, this.sect.elem); diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index be9ba5c5115..e0b616be4ea 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -241,7 +241,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { } private buildSections(source: LDMLKeyboardXMLSourceFile) { - return SECTION_COMPILERS.map(c => new c(source, this.callbacks)); + return SECTION_COMPILERS.map(c => new c(source, this.callbacks, this.kmxPlusTargetVersion)); } /** diff --git a/developer/src/kmc-ldml/src/compiler/disp.ts b/developer/src/kmc-ldml/src/compiler/disp.ts index b38ef725312..3e402bc64c9 100644 --- a/developer/src/kmc-ldml/src/compiler/disp.ts +++ b/developer/src/kmc-ldml/src/compiler/disp.ts @@ -1,4 +1,4 @@ -import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { constants, KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { LDMLKeyboard } from '@keymanapp/developer-utils'; import { KMXPlus } from "@keymanapp/common-types"; @@ -65,32 +65,56 @@ export class DispCompiler extends SectionCompiler { result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, { unescape: true, compileContext:this.keyboard3?.displays?.displayOptions }); // displays - result.disps = this.keyboard3.displays?.display.map(display => ({ - to: sections.strs.allocString(display.output, { - stringVariables: true, - markers: true, - unescape: true, - compileContext: display, - }, sections), - id: sections.strs.allocString(display.keyId, { compileContext: display }), // not escaped, not substituted - display: sections.strs.allocString(display.display, { - stringVariables: true, - unescape: true, - compileContext: display, - }, sections), - })) || []; // TODO-LDML: need coverage for the [] + if(this.targetVersion == KMXPlusVersion.Version17) { + result.disps = this.keyboard3.displays?.display.map(display => ({ + to: sections.strs.allocString(display.output, { + stringVariables: true, + markers: true, + unescape: true, + compileContext: display, + }, sections), + id: sections.strs.allocString(display.keyId, { compileContext: display }), // not escaped, not substituted + display: sections.strs.allocString(display.display, { + stringVariables: true, + unescape: true, + compileContext: display, + }, sections), + flags: 0, + toId: null as KMXPlus.StrsItem, + })) || []; // TODO-LDML: need coverage for the [] - result.disps.sort((a: DispItem, b: DispItem) => { - // sort 'id' first (empty string will be lower) - const idDiff = a.id.compareTo(b.id); - if (idDiff != 0) { - return idDiff; - } else { - // sort by 'to' - return a.to.compareTo(b.to); - } - }); + result.disps.sort((a: DispItem, b: DispItem) => { + // sort 'id' first (empty string will be lower) + const idDiff = a.id.compareTo(b.id); + if (idDiff != 0) { + return idDiff; + } else { + // sort by 'to' + return a.to.compareTo(b.to); + } + }); + } else { // v19 + result.disps = this.keyboard3.displays?.display.map(display => ({ + to: null as KMXPlus.StrsItem, + id: null as KMXPlus.StrsItem, + toId: display.output + ? sections.strs.allocString(display.output, { + stringVariables: true, + markers: true, + unescape: true, + compileContext: display, + }, sections) + : sections.strs.allocString(display.keyId, { compileContext: display }), // not escaped, not substituted + display: sections.strs.allocString(display.display, { + stringVariables: true, + unescape: true, + compileContext: display, + }, sections), + flags: display.output ? 0 : constants.disp_item_flags_is_id, // TODO-EMBED-OSK-IN-KMX: we need to provide other flags, such as frame keys + })) || []; // TODO-LDML: need coverage for the [] + result.disps.sort((a: DispItem, b: DispItem) => a.toId.compareTo(b.toId)); + } return result; } } diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts index a88e861f582..c267092bf5c 100644 --- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts @@ -1,4 +1,4 @@ -import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; +import { KMXPlusVersion, SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; import { SectionCompiler } from "./section-compiler.js"; import { util, KMXPlus, LdmlKeyboardTypes } from "@keymanapp/common-types"; import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils"; @@ -11,8 +11,8 @@ import { LdmlCompilerMessages } from './ldml-compiler-messages.js'; */ export abstract class EmptyCompiler extends SectionCompiler { private _id: SectionIdent; - constructor(id: SectionIdent, source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(source, callbacks); + constructor(id: SectionIdent, source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(source, callbacks, targetVersion); this._id = id; } @@ -25,8 +25,8 @@ export abstract class EmptyCompiler extends SectionCompiler { } export class StrsCompiler extends EmptyCompiler { - constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(constants.section.strs, source, callbacks); + constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(constants.section.strs, source, callbacks, targetVersion); } public compile(sections: KMXPlus.DependencySections): KMXPlus.Section { return new KMXPlus.Strs(); @@ -99,8 +99,8 @@ export class StrsCompiler extends EmptyCompiler { } export class ElemCompiler extends EmptyCompiler { - constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(constants.section.elem, source, callbacks); + constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(constants.section.elem, source, callbacks, targetVersion); } public compile(sections: KMXPlus.DependencySections): KMXPlus.Section { return new KMXPlus.Elem(sections); @@ -112,8 +112,8 @@ export class ElemCompiler extends EmptyCompiler { } export class ListCompiler extends EmptyCompiler { - constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(constants.section.list, source, callbacks); + constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(constants.section.list, source, callbacks, targetVersion); } public compile(sections: KMXPlus.DependencySections): KMXPlus.Section { return new KMXPlus.List(sections.strs); @@ -125,8 +125,8 @@ export class ListCompiler extends EmptyCompiler { } export class UsetCompiler extends EmptyCompiler { - constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(constants.section.uset, source, callbacks); + constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(constants.section.uset, source, callbacks, targetVersion); } public compile(sections: KMXPlus.DependencySections): KMXPlus.Section { return new KMXPlus.Uset(); diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts index 4b53330975a..4e0069200f8 100644 --- a/developer/src/kmc-ldml/src/compiler/layr.ts +++ b/developer/src/kmc-ldml/src/compiler/layr.ts @@ -7,7 +7,7 @@ import { translateLayerAttrToModifier, validModifier } from '../util/util.js'; import DependencySections = KMXPlus.DependencySections; import Layr = KMXPlus.Layr; -import LayrList = KMXPlus.LayrForm; +import LayrForm = KMXPlus.LayrForm; import LayrRow = KMXPlus.LayrRow; export class LayrCompiler extends SectionCompiler { @@ -90,12 +90,16 @@ export class LayrCompiler extends SectionCompiler { }); } } - const list: LayrList = { + const form: LayrForm = { hardware, minDeviceWidth: layers.minDeviceWidth || 0, layers: layerEntries, + baseLayout: sections.strs.allocString('', {compileContext: sect}), // TODO-EMBED-OSK-IN-KMX + fontFaceName: sections.strs.allocString('', {compileContext: sect}), // TODO-EMBED-OSK-IN-KMX + fontSizePct: 100, // TODO-EMBED-OSK-IN-KMX + flags: 0, // TODO-EMBED-OSK-IN-KMX }; - return list; + return form; }); return sect; } diff --git a/developer/src/kmc-ldml/src/compiler/section-compiler.ts b/developer/src/kmc-ldml/src/compiler/section-compiler.ts index dd83a9deda7..000bfe62fc2 100644 --- a/developer/src/kmc-ldml/src/compiler/section-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/section-compiler.ts @@ -1,14 +1,14 @@ import { KMXPlus } from "@keymanapp/common-types"; import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils"; -import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; +import { KMXPlusVersion, SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; /** newable interface to SectionCompiler c'tor */ -export type SectionCompilerNew = new (source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) => SectionCompiler; +export type SectionCompilerNew = new (source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) => SectionCompiler; export abstract class SectionCompiler { protected readonly keyboard3: LDMLKeyboard.LKKeyboard; protected readonly callbacks: CompilerCallbacks; - constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { + constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, public readonly targetVersion: KMXPlusVersion) { this.keyboard3 = source.keyboard3; this.callbacks = callbacks; } diff --git a/developer/src/kmc-ldml/src/compiler/tran.ts b/developer/src/kmc-ldml/src/compiler/tran.ts index 0a3a781f6f4..bf0d0e4f37e 100644 --- a/developer/src/kmc-ldml/src/compiler/tran.ts +++ b/developer/src/kmc-ldml/src/compiler/tran.ts @@ -1,4 +1,4 @@ -import { constants, SectionIdent } from "@keymanapp/ldml-keyboard-constants"; +import { constants, KMXPlusVersion, SectionIdent } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus, LdmlKeyboardTypes, util } from '@keymanapp/common-types'; import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils"; import { ObjectWithCompileContext } from "@keymanapp/common-types"; @@ -53,8 +53,8 @@ export abstract class TransformCompiler { - constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(source, callbacks); + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(source, callbacks, targetVersion); this.type = 'simple'; } protected newTran(): Tran { @@ -479,8 +479,8 @@ export class TranCompiler extends TransformCompiler<'simple', Tran /*, TranItem* }; export class BkspCompiler extends TransformCompiler<'backspace', Bksp /*, BkspItem*/> { - constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(source, callbacks); + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(source, callbacks, targetVersion); this.type = 'backspace'; } protected newTran(): Bksp { diff --git a/developer/src/kmc-ldml/src/compiler/vars.ts b/developer/src/kmc-ldml/src/compiler/vars.ts index ed27393312f..673ae2467ea 100644 --- a/developer/src/kmc-ldml/src/compiler/vars.ts +++ b/developer/src/kmc-ldml/src/compiler/vars.ts @@ -1,4 +1,4 @@ -import { SectionIdent, constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlusVersion, SectionIdent, constants } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus, LdmlKeyboardTypes } from '@keymanapp/common-types'; import { ObjectWithCompileContext } from "@keymanapp/common-types"; import { LDMLKeyboard, CompilerCallbacks } from '@keymanapp/developer-utils'; @@ -29,8 +29,8 @@ export class VarsCompiler extends SectionCompiler { return defaults; } - constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { - super(source, callbacks); + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks, targetVersion: KMXPlusVersion) { + super(source, callbacks, targetVersion); } public validate(): boolean { diff --git a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts index 8a7382e4939..46d4ff4ccb9 100644 --- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts @@ -8,6 +8,7 @@ import { ModifierKeyConstants, KMXPlus, VisualKeyboard } from "@keymanapp/common-types"; import { CompilerCallbacks } from "@keymanapp/developer-utils"; import { LdmlCompilerMessages } from "./ldml-compiler-messages.js"; +import { constants } from "@keymanapp/ldml-keyboard-constants"; // This is a partial polyfill for findLast, so not polluting Array.prototype // https://medium.com/@stheodorejohn/findlast-method-polyfill-in-javascript-bridging-browser-gaps-c3baf6aabae1 @@ -136,7 +137,12 @@ export class LdmlKeyboardVisualKeyboardCompiler { } private getDisplayFromKey(keydef: KMXPlus.KeysKeys, source: KMXPlus.KMXPlusData) { - const display = source.disp?.disps?.find(d => d.id.value == keydef.id.value || d.to.value == keydef.to.value); + // if(source.disp.disps[0].toId) + const display = source.disp?.disps?.find(d => + d.toId !== null + ? (d.toId.value == keydef.id.value && d.flags & constants.disp_item_flags_is_id) || + (d.toId.value == keydef.to.value && !(d.flags & constants.disp_item_flags_is_id)) + : d.id.value == keydef.id.value || d.to.value == keydef.to.value); const value = display?.display.value ?? keydef.to.value; // strip markers from the output (these are valid in keydef.to, but not in display.display, nor in kvk) return value.replaceAll(/\uffff\u0008./g, ''); diff --git a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts index 5b2c4bd6318..24cdbb53885 100644 --- a/developer/src/kmc-ldml/test/compiler-e2e.tests.ts +++ b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts @@ -8,8 +8,9 @@ import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js'; import { kmxToXml } from '../src/util/serialize.js'; import { writeFileSync } from 'node:fs'; import { LdmlCompilerMessages } from '../src/main.js'; -import { util } from '@keymanapp/common-types'; -import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; +import { KMX, util } from '@keymanapp/common-types'; +import { KMXPlusBuilder, SectionBuilders } from '@keymanapp/developer-utils'; +import { constants, KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; const debug=false; @@ -28,10 +29,10 @@ describe('compiler-tests', function() { }); [ - [17, KMXPlusVersion.Version17], - [19, KMXPlusVersion.Version19], + [17, KMX.KMX_Version.VERSION_170], + [19, KMX.KMX_Version.VERSION_190], ].forEach(([vernum, version]) => { - it(`should-build-fixtures for ${vernum}`, async function() { + it(`should-build-fixtures for v${vernum}.0`, async function() { this.timeout(4000); // Let's build basic.xml // It should match basic.kmx (built from basic.txt) @@ -50,7 +51,7 @@ describe('compiler-tests', function() { const { artifacts } = await k.run(inputFilename, "basic-xml.kmx"); // need the exact name passed to build-fixtures assert.isNotNull(artifacts); - const { kmx, kvk } = artifacts; + const { kmx, kvk } = artifacts; assert.isNotNull(kmx); assert.isNotNull(kmx.data); if(debug) { @@ -64,6 +65,64 @@ describe('compiler-tests', function() { }); }); + it('should not build a v19 file with incorrect section versions for sect, disp, and layr', async function() { + const inputFilename = makePathToFixture('basic.xml'); + const kmxPlusBuilder = await runKmxPlusCompiler(inputFilename, KMX.KMX_Version.VERSION_190); + + assert.equal(kmxPlusBuilder.sect.sect?.header.ident, constants.sectionid_sec2); + + // verify sections that should be v19 - sect (aka sec2), disp, and layr + assertSectionVersions( + kmxPlusBuilder.sect, + ['sect','disp','layr'], + KMXPlusVersion.Version19 + ); + + // verify sections that should be v17 - all should be present + assertSectionVersions( + kmxPlusBuilder.sect, + ['bksp','elem','keys','list','loca','meta','strs','tran','uset','vars'], + KMXPlusVersion.Version17 + ); + }); + + it('should not build a v17 file with incorrect section versions', async function() { + const inputFilename = makePathToFixture('basic.xml'); + const kmxPlusBuilder = await runKmxPlusCompiler(inputFilename, KMX.KMX_Version.VERSION_170); + + assert.equal(kmxPlusBuilder.sect.sect?.header.ident, constants.hex_section_id(constants.section.sect)); + + assertSectionVersions( + kmxPlusBuilder.sect, + ['bksp','disp','elem','keys','layr','list','loca','meta','sect','strs','tran','uset','vars'], + KMXPlusVersion.Version17 + ); + }); + + // Helper functions + + function assertSectionVersions(sect: SectionBuilders, idents: (keyof SectionBuilders)[], version: KMXPlusVersion) { + idents.forEach(ident => { + assert.isNotNull(sect[ident]); + assert.isNotNull(sect[ident].header); + assert.equal(sect[ident].header.version, version); + }); + } + + async function runKmxPlusCompiler(inputFilename: string, version: KMX.KMX_Version) { + const compiler = new LdmlKeyboardCompiler(); + assert.isTrue(await compiler.init(compilerTestCallbacks, { ...compilerTestOptions, targetVersion: version })); + const source = compiler.load(inputFilename); + assert.isNotNull(source); + const kmxPlusFile = await compiler.compile(source, true); + assert.isNotNull(kmxPlusFile); + + const kmxPlusBuilder = new KMXPlusBuilder(kmxPlusFile); + assert.isNotNull(kmxPlusBuilder.compile()); + return kmxPlusBuilder; + } + + it('should-validate-on-run compiling sections/strs/invalid-illegal.xml', async function() { this.timeout(4000); const inputFilename = makePathToFixture('sections/strs/invalid-illegal.xml'); diff --git a/developer/src/kmc-ldml/test/dependencies.tests.ts b/developer/src/kmc-ldml/test/dependencies.tests.ts index 5171648c4a6..ec600737a86 100644 --- a/developer/src/kmc-ldml/test/dependencies.tests.ts +++ b/developer/src/kmc-ldml/test/dependencies.tests.ts @@ -1,13 +1,13 @@ import 'mocha'; import {assert} from 'chai'; import { SECTION_COMPILERS } from '../src/compiler/compiler.js'; -import { SectionIdent } from '@keymanapp/ldml-keyboard-constants'; +import { KMXPlusVersion, SectionIdent } from '@keymanapp/ldml-keyboard-constants'; describe('test of section compiler dependencies', () => { it('should have dependencies in the right order', () => { const sects : Set = new Set(); for (const sect of SECTION_COMPILERS) { // construct the compiler - const c = new sect({ keyboard3: null }, null); // For now, this is OK for the inspection + const c = new sect({ keyboard3: null }, null, KMXPlusVersion.Version17); // For now, this is OK for the inspection const id = c.id; assert.ok(id); assert.isFalse(sects.has(id), `Duplicate compiler ${id} in SECTION_COMPILERS`); diff --git a/developer/src/kmc-ldml/test/disp.tests.ts b/developer/src/kmc-ldml/test/disp.tests.ts index 8b17b35c55c..13618570299 100644 --- a/developer/src/kmc-ldml/test/disp.tests.ts +++ b/developer/src/kmc-ldml/test/disp.tests.ts @@ -7,19 +7,22 @@ import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js' import Disp = KMXPlus.Disp; import { withOffset } from '@keymanapp/developer-utils'; +import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; describe('disp', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while + // TODO-EMBED-OSK-IN-KMX: add v19 tests + it('should compile minimal disp', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/minimal.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/minimal.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.equal(compilerTestCallbacks.messages.length, 0); assert.ok(disp?.disps); assert.equal(disp.disps.length, 0); }); it('should compile typical disp', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/typical.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/typical.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.sameDeepMembers(compilerTestCallbacks.messages, []); assert.ok(disp?.disps); @@ -28,7 +31,7 @@ describe('disp', function () { assert.equal(disp.disps[0].display?.value, '`'); }); it('should compile maximal disp', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/maximal.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/maximal.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(disp?.baseCharacter?.value, 'x'); assert.ok(disp?.disps); @@ -41,7 +44,7 @@ describe('disp', function () { assert.equal(disp.disps[2].display?.value, '(g)'); }); it('should compile escaped disp', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/escaped.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/escaped.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(disp?.baseCharacter?.value, 'x'); assert.ok(disp?.disps); @@ -52,38 +55,38 @@ describe('disp', function () { assert.equal(disp.disps[1].display?.value, '(f)'); }); it('should compile options-only disp', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/maximal.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/maximal.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(disp?.baseCharacter?.value, 'x'); assert.ok(disp?.disps); }); it('should compile disp without converting markers', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/not-a-marker.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/not-a-marker.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.equal(compilerTestCallbacks.messages.length, 0); assert.ok(disp?.disps); assert.equal(disp.disps.length, 1); assert.equal(disp.disps[0].display?.value, '\\m{hat}'); }); it('should reject duplicate tos', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupto.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupto.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.isNull(disp); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayIsRepeated({ display: '(e)' }, withOffset(330))); }); it('should reject duplicate ids', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupid.xml', compilerTestCallbacks) as Disp;1 + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupid.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.isNull(disp); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayIsRepeated({ display: '(e)' }, withOffset(329))); }); it('should reject if neither to nor id', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-none.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-none.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.isNull(disp); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayNeedsToOrId({ display: '(f)'}, withOffset(182))); }); it('should reject if both to and id', async function() { - const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-both.xml', compilerTestCallbacks) as Disp; + const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-both.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Disp; assert.isNull(disp); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayNeedsToOrId({ display: '(e)' }, withOffset(182))); @@ -96,6 +99,6 @@ describe('disp', function () { LdmlCompilerMessages.Error_MissingStringVariable({id: "missingoutput"}), ], }, - ]); + ], KMXPlusVersion.Version17); }); diff --git a/developer/src/kmc-ldml/test/fixtures/README.md b/developer/src/kmc-ldml/test/fixtures/README.md new file mode 100644 index 00000000000..69351c595a3 --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/README.md @@ -0,0 +1,25 @@ +# Fixtures for KMX+ / LDML format files and kmc-ldml compiler + +## basic-xx.txt fixtures + +The set of files named basic-xx.txt describe the expected output of running kmc +against basic.xml with version xx schema and target version. They are used in +the end-to-end test test-compiler-e2e.ts. + +Any changes to the compiler or basic.xml will likely result in changes to the +compiled file. Structural differences should be updated manually in the txt +files to ensure that we are getting the expected result for the e2e test. The +following may be helpful for working with these files when updating the binary +format: + + ```sh + cd developer/src/kmc + ./build.sh configure build # if needed + ./build.sh build-fixtures + ``` + +This will compile both the .xml and the .txt to build/test/fixtures. + +For the format of the files, see “hextobin.ts”. + +P.S. surprisingly, the Dart language highlighter in VSCode does a helpful job on these files. diff --git a/developer/src/kmc-ldml/test/fixtures/basic-17.txt b/developer/src/kmc-ldml/test/fixtures/basic-17.txt index fbd163fb237..3d9891c9cb1 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic-17.txt +++ b/developer/src/kmc-ldml/test/fixtures/basic-17.txt @@ -1,25 +1,12 @@ # # Keyman is copyright (C) SIL International. MIT License. # -# basic.txt describes the expected output of running kmc against basic.xml. It is used in -# the end-to-end test test-compiler-e2e.ts. +# basic-17.txt describes the expected output of running kmc against basic.xml +# with v17 schema and v17 target version. It is used in the end-to-end test +# test-compiler-e2e.ts. # -# Any changes to the compiler or basic.xml will likely result in changes to the compiled file. -# While structural differences should be updated manually in this file to ensure that we are -# getting the expected result for the e2e test, the checksum can be safely retrieved from the -# updated compilation result. The following may be helpful for working with this file when -# updating the binary format: +# See README.md for more information. # -# cd developer/src/kmc -# ./build.sh configure build # if needed -# ./build.sh build-fixtures -# -# This will compile both the .xml and the .txt to build/test/fixtures and also emit the -# checksum for basic-xml.kmx so you can patch that into this file. -# -# For the format of this file, see “hextobin.ts” -# -# P.S. surprisingly, the Dart language highlighter in VSCode does a helpful job on this file. block(kmxheader) # struct COMP_KEYBOARD { 4b 58 54 53 # KMX_DWORD dwIdentifier; // 0000 Keyman compiled keyboard id @@ -96,7 +83,7 @@ block(store_vk_path_string) 62 00 61 00 73 00 69 00 63 00 2d 00 78 00 6d 00 6c 00 2e 00 6b 00 76 00 6b 00 00 00 # 'basic-xml.kvk' block(sect) # struct COMP_KMXPLUS_SECT { - 73 65 63 74 # KMX_DWORD header.ident; // 0000 Section name + 73 65 63 74 # KMX_DWORD header.ident; // 0000 Section name 'sect' diff(sect,endsect) # KMX_DWORD header.size; // 0004 Section length diff(sect,eof) # KMX_DWORD total; // 0008 KMXPlus entire length sizeof(sectitems,8) # KMX_DWORD count; // 000C number of section headers diff --git a/developer/src/kmc-ldml/test/fixtures/basic-19.txt b/developer/src/kmc-ldml/test/fixtures/basic-19.txt index f42b7470d87..75806e72934 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic-19.txt +++ b/developer/src/kmc-ldml/test/fixtures/basic-19.txt @@ -1,25 +1,19 @@ # # Keyman is copyright (C) SIL International. MIT License. # -# basic-v19.txt describes the expected output of running kmc against basic.xml -# with v19 schema. It is used in the end-to-end test test-compiler-e2e.ts. +# basic-19.txt describes the expected output of running kmc against basic.xml +# with v19 schema and v19 target version. It is used in the end-to-end test +# test-compiler-e2e.ts. # -# Any changes to the compiler or basic.xml will likely result in changes to the compiled file. -# While structural differences should be updated manually in this file to ensure that we are -# getting the expected result for the e2e test, the checksum can be safely retrieved from the -# updated compilation result. The following may be helpful for working with this file when -# updating the binary format: +# Differences from v17: +# * KMX: COMP_KEYBOARD.dwFileVersion +# * KMX+: `sec2` section vs `sect` section +# * KMX+: each section has an additional version header field +# * KMX+: `disp` section is v19 +# * KMX+: `layr` section is v19 # -# cd developer/src/kmc -# ./build.sh configure build # if needed -# ./build.sh build-fixtures +# See README.md for more information. # -# This will compile both the .xml and the .txt to build/test/fixtures and also emit the -# checksum for basic-xml.kmx so you can patch that into this file. -# -# For the format of this file, see “hextobin.ts” -# -# P.S. surprisingly, the Dart language highlighter in VSCode does a helpful job on this file. block(kmxheader) # struct COMP_KEYBOARD { 4b 58 54 53 # KMX_DWORD dwIdentifier; // 0000 Keyman compiled keyboard id @@ -96,7 +90,7 @@ block(store_vk_path_string) 62 00 61 00 73 00 69 00 63 00 2d 00 78 00 6d 00 6c 00 2e 00 6b 00 76 00 6b 00 00 00 # 'basic-xml.kvk' block(sec2) # struct COMP_KMXPLUS_SEC2 { - 73 65 63 32 # KMX_DWORD header.ident; // 0000 Section name + 73 65 63 32 # KMX_DWORD header.ident; // 0000 Section name 'sec2' diff(sec2,endsec2) # KMX_DWORD header.size; // 0004 Section length 00 13 00 00 # KMX_DWORD header.version; // 0008 Section version, 19 = 0x13 diff(sec2,eof) # KMX_DWORD total; // 000C KMXPlus entire length @@ -171,26 +165,26 @@ block(bksp) # ---------------------------------------------------------------------------------------------------- -# disp +# disp (v19) # ---------------------------------------------------------------------------------------------------- block(disp) # struct COMP_KMXPLUS_DISP { 64 69 73 70 # KMX_DWORD header.ident; // 0000 Section name - disp sizeof(disp) # KMX_DWORD header.size; // 0004 Section length - 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 + 00 13 00 00 # KMX_DWORD header.version; // 0008 Section version, 19 = 0x13 02 00 00 00 # KMX_DWORD count; // 000C number of entries index(strNull,strElemBkspFrom2,2) # KMX_DWORD baseCharacter // 0010 baseCharacter = 'e' # }; # entry 0 - index(strNull,strA,2) # KMX_DWORD to // 000C baseCharacter = 'a' - index(strNull,strNull,2) # KMX_DWORD id 0 - index(strNull,strElemTranFrom1,2) # KMX_DWORD display // 000C baseCharacter = '^' + index(strNull,strA,2) # KMX_DWORD toId // 0000 baseCharacter = 'a' + index(strNull,strElemTranFrom1,2) # KMX_DWORD display // 0004 display = '^' + 00 00 00 00 # KMX_DWORD flags // 0008 isId=0 # entry 1 - index(strNull,strNull,2) # KMX_DWORD to // 000C baseCharacter = 'a' - index(strNull,strElemBkspFrom2,2) # KMX_DWORD id 'e' - index(strNull,strElemTranFrom1b,2) # KMX_DWORD display '^e' + index(strNull,strElemBkspFrom2,2) # KMX_DWORD toId // 0000 id = 'e' + index(strNull,strElemTranFrom1b,2) # KMX_DWORD display // 0004 '^e' + 01 00 00 00 # KMX_DWORD flags // 0008 isId=1 @@ -405,16 +399,21 @@ block(keys) # struct COMP_KMXPLUS_KEYS { block(layr) # struct COMP_KMXPLUS_LAYR { 6c 61 79 72 # KMX_DWORD header.ident; // 0000 Section name - layr sizeof(layr) # KMX_DWORD header.size; // 0004 Section length - 00 11 00 00 # KMX_DWORD header.version; // 0008 Section version, 17 = 0x11 - 01 00 00 00 # KMX_DWORD listCount + 00 13 00 00 # KMX_DWORD header.version; // 0008 Section version, 19 = 0x13 + 01 00 00 00 # KMX_DWORD formCount 01 00 00 00 # KMX_DWORD layerCount 01 00 00 00 # KMX_DWORD rowCount 02 00 00 00 # KMX_DWORD keyCount - # list 0 + # form 0 index(strNull,strUs,2) # KMXPLUS_STR hardware = 'us' 00 00 00 00 # KMX_DWORD layer; - 01 00 00 00 # count + 01 00 00 00 # KMX_DWORD count 7B 00 00 00 # KMX_DWORD minDeviceWidth; // 123 + 00 00 00 00 # KMXPLUS_STR baseLayout v19 + 00 00 00 00 # KMXPLUS_STR fontFaceName v19 + 64 00 00 00 # KMX_DWORD fontSizePct v19 // 100% + 00 00 00 00 # KMX_DWORD flags v19 + # layers 0 00 00 00 00 # KMXPLUS_STR id; 00 00 00 00 # KMX_DWORD mod diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index be6216f9c18..8b89a580034 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -22,6 +22,7 @@ import Section = KMXPlus.Section; import { ElemCompiler, ListCompiler, StrsCompiler } from '../../src/compiler/empty-compiler.js'; import { KmnCompiler } from '@keymanapp/kmc-kmn'; import { VarsCompiler } from '../../src/compiler/vars.js'; +import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; /** * Builds a path to the fixture with the given path components. @@ -54,7 +55,7 @@ afterEach(function() { }); -export async function loadSectionFixture(compilerClass: SectionCompilerNew, filename: string, callbacks: TestCompilerCallbacks, dependencies?: SectionCompilerNew[], postValidateFail?: boolean): Promise
{ +export async function loadSectionFixture(compilerClass: SectionCompilerNew, filename: string, callbacks: TestCompilerCallbacks, targetVersion: KMXPlusVersion, dependencies?: SectionCompilerNew[], postValidateFail?: boolean): Promise
{ callbacks.messages = []; const inputFilename = makePathToFixture(filename); const data = callbacks.loadFile(inputFilename); @@ -74,7 +75,7 @@ export async function loadSectionFixture(compilerClass: SectionCompilerNew, file return null; // mimic kmc behavior - bail if validate fails } - const compiler = new compilerClass(source, callbacks); + const compiler = new compilerClass(source, callbacks, targetVersion); if(!compiler.validate()) { return null; @@ -111,7 +112,7 @@ async function loadDepsFor(sections: DependencySections, parentCompiler: Section dependencies = [ StrsCompiler, ListCompiler, ElemCompiler, VarsCompiler ]; } for (const dep of dependencies) { - const compiler = new dep(source, callbacks); + const compiler = new dep(source, callbacks, parentCompiler.targetVersion); assert.notEqual(compiler.id, parentId, `${parentId} depends on itself`); const didValidate = compiler.validate(); if (!callbacks.hasError()) { @@ -285,7 +286,7 @@ export function scrubContextFromMessages(messages: CompilerEvent[]): CompilerEve * @param compiler argument to loadSectionFixture() * @param callbacks argument to loadSectionFixture() */ -export function testCompilationCases(compiler: SectionCompilerNew, cases : CompilationCase[], dependencies?: (SectionCompilerNew)[]) { +export function testCompilationCases(compiler: SectionCompilerNew, cases : CompilationCase[], targetVersion: KMXPlusVersion, dependencies?: (SectionCompilerNew)[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); for (const testcase of cases) { @@ -296,10 +297,10 @@ export function testCompilationCases(compiler: SectionCompilerNew, cases : Compi callbacks.clear(); // special case for an expected exception if (testcase.throws) { - assert.throws(async () => await loadSectionFixture(compiler, testcase.subpath, callbacks, testcase.dependencies || dependencies), testcase.throws, 'expected exception from compilation'); + assert.throws(async () => await loadSectionFixture(compiler, testcase.subpath, callbacks, targetVersion, testcase.dependencies || dependencies), testcase.throws, 'expected exception from compilation'); return; } - const section = await loadSectionFixture(compiler, testcase.subpath, callbacks, testcase.dependencies || dependencies); + const section = await loadSectionFixture(compiler, testcase.subpath, callbacks, targetVersion, testcase.dependencies || dependencies); let messagesToCheck = callbacks.messages; // scrub offsets from messages to reduce churn in the test casws if (!testcase.retainOffsetInMessages && callbacks.messages) { diff --git a/developer/src/kmc-ldml/test/keys.tests.ts b/developer/src/kmc-ldml/test/keys.tests.ts index 97171cf6fcf..57edb7f257c 100644 --- a/developer/src/kmc-ldml/test/keys.tests.ts +++ b/developer/src/kmc-ldml/test/keys.tests.ts @@ -4,7 +4,7 @@ import { KeysCompiler } from '../src/compiler/keys.js'; import { assertCodePoints, compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js'; import { KMXPlus, Constants, LdmlKeyboardTypes } from '@keymanapp/common-types'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; -import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { constants, KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; import { MetaCompiler } from '../src/compiler/meta.js'; const keysDependencies = [ ...BASIC_DEPENDENCIES, MetaCompiler ]; import Keys = KMXPlus.Keys; @@ -213,14 +213,14 @@ describe('keys', function () { assert.equal(interrobang.to.value, `‽`, `Interrobang's value`); }, }, - ], keysDependencies); + ], KMXPlusVersion.Version17, keysDependencies); }); describe('keys.kmap', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal kmap data', async function() { - const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks, keysDependencies) as Keys; + const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks, KMXPlusVersion.Version17, keysDependencies) as Keys; assert.isNotNull(keys); assert.equal(compilerTestCallbacks.messages.length, 0); // skip reserved (gap) keys @@ -443,10 +443,10 @@ describe('keys.kmap', function () { ], 'modifiers for c'); }, }, - ], keysDependencies); + ], KMXPlusVersion.Version17, keysDependencies); it('should reject layouts with too many hardware rows', async function() { - const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-rows.xml', compilerTestCallbacks, keysDependencies) as Keys; + const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-rows.xml', compilerTestCallbacks, KMXPlusVersion.Version17, keysDependencies) as Keys; assert.isNull(keys); assert.equal(compilerTestCallbacks.messages.length, 1); @@ -454,7 +454,7 @@ describe('keys.kmap', function () { }); it('should reject layouts with too many hardware keys', async function() { - const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-keys.xml', compilerTestCallbacks, keysDependencies) as Keys; + const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-keys.xml', compilerTestCallbacks, KMXPlusVersion.Version17, keysDependencies) as Keys; assert.isNull(keys); assert.equal(compilerTestCallbacks.messages.length, 1); @@ -462,14 +462,14 @@ describe('keys.kmap', function () { }); it('should reject layouts with undefined keys', async function() { - const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-undefined-key.xml', compilerTestCallbacks, keysDependencies) as Keys; + const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-undefined-key.xml', compilerTestCallbacks, KMXPlusVersion.Version17, keysDependencies) as Keys; assert.isNull(keys); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_KeyNotFoundInKeyBag({col: 1, form: 'hardware', keyId: 'foo', layer: 'base', row: 1}, withOffset(271) as LDMLKeyboard.LKRow)); }); it('should reject layouts with invalid keys', async function() { - const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-key-missing-attrs.xml', compilerTestCallbacks, keysDependencies) as Keys; + const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-key-missing-attrs.xml', compilerTestCallbacks, KMXPlusVersion.Version17, keysDependencies) as Keys; assert.isNull(keys); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_KeyMissingToGapOrSwitch( @@ -478,7 +478,7 @@ describe('keys.kmap', function () { )); }); it('should accept layouts with gap/switch keys', async function() { - const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks, keysDependencies) as Keys; + const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks, KMXPlusVersion.Version17, keysDependencies) as Keys; assert.isNotNull(keys); assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(keys.keys.length, 4 + KeysCompiler.reserved_count); diff --git a/developer/src/kmc-ldml/test/layr.tests.ts b/developer/src/kmc-ldml/test/layr.tests.ts index 3f5e75a02d7..d7fbab7a249 100644 --- a/developer/src/kmc-ldml/test/layr.tests.ts +++ b/developer/src/kmc-ldml/test/layr.tests.ts @@ -4,7 +4,7 @@ import { LayrCompiler } from '../src/compiler/layr.js'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; import { compilerTestCallbacks, testCompilationCases } from './helpers/index.js'; import { KMXPlus } from '@keymanapp/common-types'; -import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { constants, KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; import Layr = KMXPlus.Layr; import LayrRow = KMXPlus.LayrRow; @@ -21,6 +21,8 @@ function allKeysOk(row : LayrRow, str : string, msg? : string) { describe('layr', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while + // TODO-EMBED-OSK-IN-KMX: add v19 tests + testCompilationCases(LayrCompiler, [ { subpath: 'sections/keys/minimal.xml', @@ -30,11 +32,11 @@ describe('layr', function () { assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(layr.forms?.length, 1); - const list0 = layr.forms[0]; - assert.ok(list0); - assert.equal(list0.layers.length, 1); - assert.equal(list0.hardware.value, 'us'); - const layer0 = list0.layers[0]; + const form0 = layr.forms[0]; + assert.ok(form0); + assert.equal(form0.layers.length, 1); + assert.equal(form0.hardware.value, 'us'); + const layer0 = form0.layers[0]; assert.ok(layer0); assert.equal(layer0.rows.length, 1); const row0 = layer0.rows[0]; @@ -52,11 +54,11 @@ describe('layr', function () { const layr = sect; assert.equal(layr.forms?.length, 2); - const listHardware = layr.forms.find(v => v.hardware.value === 'iso'); - assert.ok(listHardware); - assert.equal(listHardware.minDeviceWidth, 0); - assert.equal(listHardware.layers.length, 2); - const hardware0 = listHardware.layers[0]; + const formHardware = layr.forms.find(v => v.hardware.value === 'iso'); + assert.ok(formHardware); + assert.equal(formHardware.minDeviceWidth, 0); + assert.equal(formHardware.layers.length, 2); + const hardware0 = formHardware.layers[0]; assert.ok(hardware0); assert.equal(hardware0.id.value, 'base'); assert.equal(hardware0.mod, constants.keys_mod_none); @@ -64,7 +66,7 @@ describe('layr', function () { assert.ok(hardware0row0); assert.equal(hardware0row0.keys.length, 2); allKeysOk(hardware0row0,'Q W', 'hardware0row0'); - const hardware1 = listHardware.layers[1]; + const hardware1 = formHardware.layers[1]; assert.ok(hardware1); assert.equal(hardware1.rows.length, 1); assert.equal(hardware1.id.value, 'shift'); @@ -74,11 +76,11 @@ describe('layr', function () { assert.equal(hardware1row0.keys.length, 3); allKeysOk(hardware1row0,'q w amarker', 'hardware1row0'); - const listTouch = layr.forms.find(v => v.hardware.value === constants.layr_form_hardware_touch); - assert.ok(listTouch); - assert.equal(listTouch.minDeviceWidth, 300); - assert.equal(listTouch.layers.length, 1); - const touch0 = listTouch.layers[0]; + const formTouch = layr.forms.find(v => v.hardware.value === constants.layr_list_hardware_touch); + assert.ok(formTouch); + assert.equal(formTouch.minDeviceWidth, 300); + assert.equal(formTouch.layers.length, 1); + const touch0 = formTouch.layers[0]; assert.ok(touch0); assert.equal(touch0.rows.length, 1); assert.equal(touch0.id.value, 'base'); @@ -123,7 +125,7 @@ describe('layr', function () { callback(sect) { const layr = sect; assert.ok(layr); - assert.equal(layr.forms.length, 1, 'layr.lists.length'); + assert.equal(layr.forms.length, 1, 'layr.forms.length'); const layers = layr.forms[0]; const bymod = layers.layers.map(({id,mod,rows})=>([ id.value, mod, rows[0].keys[0].value, @@ -150,11 +152,11 @@ describe('layr', function () { assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(layr.forms?.length, 1); - const list0 = layr.forms[0]; - assert.ok(list0); - assert.equal(list0.layers.length, 1); - assert.equal(list0.hardware.value, 'us'); - const layer0 = list0.layers[0]; + const form0 = layr.forms[0]; + assert.ok(form0); + assert.equal(form0.layers.length, 1); + assert.equal(form0.hardware.value, 'us'); + const layer0 = form0.layers[0]; assert.ok(layer0); assert.equal(layer0.rows.length, 2); assert.equal(layer0.id.value, 'base'); @@ -180,5 +182,5 @@ describe('layr', function () { LdmlCompilerMessages.Error_InvalidLayerWidth({ minDeviceWidth }), ] })), - ]); + ], KMXPlusVersion.Version17); }); diff --git a/developer/src/kmc-ldml/test/loca.tests.ts b/developer/src/kmc-ldml/test/loca.tests.ts index ffe4f656c79..0d794992ac9 100644 --- a/developer/src/kmc-ldml/test/loca.tests.ts +++ b/developer/src/kmc-ldml/test/loca.tests.ts @@ -7,12 +7,13 @@ import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js' import Loca = KMXPlus.Loca; import { withOffset } from '@keymanapp/developer-utils'; +import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; describe('loca', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal loca data', async function() { - const loca = await loadSectionFixture(LocaCompiler, 'sections/loca/minimal.xml', compilerTestCallbacks) as Loca; + const loca = await loadSectionFixture(LocaCompiler, 'sections/loca/minimal.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Loca; assert.isObject(loca); assert.equal(compilerTestCallbacks.messages.length, 0); @@ -22,7 +23,7 @@ describe('loca', function () { }); it('should compile multiple locales', async function() { - const loca = await loadSectionFixture(LocaCompiler, 'sections/loca/multiple.xml', compilerTestCallbacks) as Loca; + const loca = await loadSectionFixture(LocaCompiler, 'sections/loca/multiple.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Loca; assert.isObject(loca); // Note: multiple.xml includes fr-FR twice, with differing case, which should be canonicalized @@ -42,7 +43,7 @@ describe('loca', function () { }); it('should reject structurally invalid locales', async function() { - const loca = await loadSectionFixture(LocaCompiler, 'sections/loca/invalid-locale.xml', compilerTestCallbacks) as Loca; + const loca = await loadSectionFixture(LocaCompiler, 'sections/loca/invalid-locale.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Loca; assert.isNull(loca); assert.equal(compilerTestCallbacks.messages.length, 1); // We'll only test one invalid BCP 47 tag to verify that we are properly calling BCP 47 validation routines. diff --git a/developer/src/kmc-ldml/test/meta.tests.ts b/developer/src/kmc-ldml/test/meta.tests.ts index beeed509154..d0f729ddd11 100644 --- a/developer/src/kmc-ldml/test/meta.tests.ts +++ b/developer/src/kmc-ldml/test/meta.tests.ts @@ -8,12 +8,13 @@ import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js' import KeyboardSettings = KMXPlus.KeyboardSettings; import Meta = KMXPlus.Meta; import { LDMLKeyboard, withOffset } from '@keymanapp/developer-utils'; +import { KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; describe('meta', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal metadata', async function() { - const meta = await loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', compilerTestCallbacks) as Meta; + const meta = await loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Meta; assert.equal(compilerTestCallbacks.messages.length, 0); assert.isEmpty(meta.author.value); // TODO-LDML: default author string "unknown"? @@ -25,7 +26,7 @@ describe('meta', function () { }); it('should compile maximal metadata', async function() { - const meta = await loadSectionFixture(MetaCompiler, 'sections/meta/maximal.xml', compilerTestCallbacks) as Meta; + const meta = await loadSectionFixture(MetaCompiler, 'sections/meta/maximal.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Meta; assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(meta.author.value, 'The Keyman Team'); @@ -37,19 +38,19 @@ describe('meta', function () { }); it('should hint when normalization=disabled', async function() { - const meta = await loadSectionFixture(MetaCompiler, 'sections/meta/hint-normalization.xml', compilerTestCallbacks) as Meta; + const meta = await loadSectionFixture(MetaCompiler, 'sections/meta/hint-normalization.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Meta; assert.isNotNull(meta); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Hint_NormalizationDisabled(withOffset(177) as LDMLKeyboard.LKSettings)); }); it('should reject invalid version', async function() { - let meta = await loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-1.0.xml', compilerTestCallbacks) as Meta; + let meta = await loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-1.0.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Meta; assert.isNull(meta); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_InvalidVersion({version:'1.0'}, withOffset(40) as LDMLKeyboard.LKKeyboard)); - meta = await loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-v1.0.3.xml', compilerTestCallbacks) as Meta; + meta = await loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-v1.0.3.xml', compilerTestCallbacks, KMXPlusVersion.Version17) as Meta; assert.isNull(meta); assert.equal(compilerTestCallbacks.messages.length, 1); assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_InvalidVersion({version:'v1.0.3'}, withOffset(40) as LDMLKeyboard.LKKeyboard)); diff --git a/developer/src/kmc-ldml/test/tran.tests.ts b/developer/src/kmc-ldml/test/tran.tests.ts index 111b6962671..3df4c944b68 100644 --- a/developer/src/kmc-ldml/test/tran.tests.ts +++ b/developer/src/kmc-ldml/test/tran.tests.ts @@ -9,7 +9,7 @@ import { KMXPlus, LdmlKeyboardTypes } from '@keymanapp/common-types'; import Tran = KMXPlus.Tran;// for tests… import Bksp = KMXPlus.Bksp;// for tests… -import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { constants, KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; import { MetaCompiler } from '../src/compiler/meta.js'; const tranDependencies = [ ...BASIC_DEPENDENCIES, UsetCompiler, MetaCompiler ]; const bkspDependencies = tranDependencies; @@ -437,7 +437,7 @@ describe('tran', function () { }, } - ], tranDependencies); + ], KMXPlusVersion.Version17, tranDependencies); }); describe('bksp', function () { @@ -471,6 +471,6 @@ describe('bksp', function () { }), ], }, - ], bkspDependencies); + ], KMXPlusVersion.Version17, bkspDependencies); }); diff --git a/developer/src/kmc-ldml/test/vars.tests.ts b/developer/src/kmc-ldml/test/vars.tests.ts index bb0ee5d389e..5f078ddd391 100644 --- a/developer/src/kmc-ldml/test/vars.tests.ts +++ b/developer/src/kmc-ldml/test/vars.tests.ts @@ -6,7 +6,7 @@ import { KmnCompilerMessages } from '@keymanapp/kmc-kmn'; import { testCompilationCases } from './helpers/index.js'; import { KMXPlus, KMX } from '@keymanapp/common-types'; import { BASIC_DEPENDENCIES } from '../src/compiler/empty-compiler.js'; -import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { constants, KMXPlusVersion } from '@keymanapp/ldml-keyboard-constants'; // now that 'everything' depends on vars, we need an explicit dependency here const varsDependencies = BASIC_DEPENDENCIES.filter(c => c !== VarsCompiler); @@ -209,7 +209,7 @@ describe('vars', function () { ], strictErrors: true }, -], varsDependencies); +], KMXPlusVersion.Version17, varsDependencies); describe('should match some marker constants', () => { // neither of these live here, but, common/web/types does not import ldml-keyboard-constants otherwise. @@ -242,6 +242,6 @@ describe('vars', function () { }), ], }, - ], varsDependencies); + ], KMXPlusVersion.Version17, varsDependencies); }); }); diff --git a/docs/build/windows.md b/docs/build/windows.md index f485605f589..c497d07efb9 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -184,8 +184,8 @@ In bash, run the following commands: cd /c/Projects/keyman git clone https://github.com/emscripten-core/emsdk cd emsdk -emsdk install 3.1.58 -emsdk activate 3.1.58 +./emsdk install 3.1.58 +./emsdk activate 3.1.58 cd upstream/emscripten npm install ``` @@ -360,23 +360,23 @@ git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4 * Ant * Gradle * Maven -* JDK 11 (Temurin11) +* JDK 21 #### JDK 11 -Use Powershell + Chocolatey to install JDK 11: +Use Powershell + winget to install JDK: ```ps1 # Elevated PowerShell - -# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) -$ProgressPreference = 'SilentlyContinue' -choco install temurin11 +# For Keyman 17: +winget install Microsoft.OpenJDK.11 +# For Keyman 18+: +winget install Microsoft.OpenJDK.21 ``` -**Multiple versions of Java:** If you need to build Keyman for Android 16.0 or +**Multiple versions of Java:** If you need to build Keyman for Android 17.0 or older versions, you can set `JAVA_HOME_11` to the JDK 11 path and -`JAVA_HOME` to the JDK 8 path. This will build both versions correctly +`JAVA_HOME_21` to the JDK 21 path. This will build both versions correctly from command line. But note that you do need to update your `JAVA_HOME` env var to the associated version before opening Android Studio and loading any Android projects. `JAVA_HOME_11` is mostly used by CI. @@ -411,9 +411,5 @@ certificates for the build. * sentry-cli (optional) - Uploading symbols for Sentry-based error reporting - - bash: - ```bash - # bash - curl -sL https://sentry.io/get-cli/ | bash - ``` + - https://github.com/getsentry/sentry-cli/releases/tag/1.70.0 + (TODO: update to newer version) \ No newline at end of file diff --git a/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md index 1327b6af66f..29f7ddfd725 100644 --- a/docs/file-formats/kmx-plus-file-format.md +++ b/docs/file-formats/kmx-plus-file-format.md @@ -120,7 +120,7 @@ Each element string is made up of elements with the following item structure: ### C7043.2.5 Removed: old-format `keys` -_this section has been merged into the new `keys.kmap`_ +_this section has been merged into the new `key2.kmap`_ ### C7043.2.6 `loca`—Locales @@ -150,7 +150,7 @@ locale IDs (starting at offset 16) are in sorted binary order. | 8 | 32 | author | str: Keyboard author | |12 | 32 | conform | str: CLDR 'conformsTo' version | |16 | 32 | layout | str: layout type | -|20 | 32 | name | str: keyboard nme | +|20 | 32 | name | str: keyboard name | |24 | 32 | indicator | str: indicator | |28 | 32 | version | str: keyboard version | |32 | 32 | settings | int: keyboard settings | @@ -318,7 +318,7 @@ and differentiate from other uses of `list`. | 8+| 32 | count | int: number of layer elements in this form | |12+| 32 | minDeviceWidth | int: min device width in millimeters, or 0 | -- `hardware` is the name of a form, or the string `touch` +- `hardware` is the name of a form, or the string `"touch"`. See UTS #35 section 7 for details about these values. @@ -339,7 +339,7 @@ both with `id="abc"`, but one with key flags of 0x0000 and one with keyflags of | 8+| 32 | row | int: index into rows area (next section) | |12+| 32 | count | int: number of `rows` elements for this layer | -- for `mod` see `keys.key.mod` +- for `mod` see `key2.kmap.mod` ### `layr.rows` subtable @@ -403,17 +403,17 @@ on the `id` field. For each key: -| ∆ | Bits | Name | Description | -|---|------|---------------- |----------------------------------------------------------| -| 0+| 32 | to | str: output string OR UTF-32LE codepoint | -| 4+| 32 | flags | int: per-key flags | -| 8+| 32 | id | str: key id | -|12+| 32 | switch | str: layer id to switch to | -|16+| 32 | width | int: key width*10 (supports 0.1 as min width) | +| ∆ | Bits | Name | Description | +|---|------|---------------- |-----------------------------------------------------------------| +| 0+| 32 | to | str: output string OR UTF-32LE codepoint | +| 4+| 32 | flags | int: per-key flags | +| 8+| 32 | id | str: key id | +|12+| 32 | switch | str: layer id to switch to | +|16+| 32 | width | int: key width*10 (supports 0.1 as min width) | |20+| 32 | longPress | list: index into `list` section with longPress key id list or 0 | -|24+| 32 | longPressDefault | str: default longpress key id or 0 | +|24+| 32 | longPressDefault | str: default longpress key id or 0 | |28+| 32 | multiTap | list: index into `list` section with multiTap key id list or 0 | -|32+| 32 | flicks | int: index into `key2.flicks` subtable | +|32+| 32 | flicks | int: index into `key2.flicks` subtable | - `id`: The original string id from XML. This may be 0 to save space (i.e. omit the string id). @@ -494,7 +494,7 @@ For each key: | 0x0020 | `ctrl` | `K_CTRLFLAG` | Either Control | | 0x0040 | `alt` | `K_ALTFLAG` | Either Alt | | 0x0100 | `caps` | `CAPITALFLAG` | Caps lock | -| 0x10000 | `other` | n/a | Other (not used in conjunction with others) | +| 0x10000 | `other` | n/a | Other (not used in conjunction with others) | TODO-LDML: Note that conforming to other keyman values, left versus right shift cannot be distinguished. From ede398cf7b0623c4f9554959240d14811806d6d9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 18 Nov 2025 15:51:44 +0100 Subject: [PATCH 25/37] chore(developer): rename lists to forms Missed this in earlier refactor. --- developer/src/kmc-ldml/test/layr.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc-ldml/test/layr.tests.ts b/developer/src/kmc-ldml/test/layr.tests.ts index d7fbab7a249..42766c44830 100644 --- a/developer/src/kmc-ldml/test/layr.tests.ts +++ b/developer/src/kmc-ldml/test/layr.tests.ts @@ -76,7 +76,7 @@ describe('layr', function () { assert.equal(hardware1row0.keys.length, 3); allKeysOk(hardware1row0,'q w amarker', 'hardware1row0'); - const formTouch = layr.forms.find(v => v.hardware.value === constants.layr_list_hardware_touch); + const formTouch = layr.forms.find(v => v.hardware.value === constants.layr_form_hardware_touch); assert.ok(formTouch); assert.equal(formTouch.minDeviceWidth, 300); assert.equal(formTouch.layers.length, 1); From 4932790ac058498f117ff85079fc212a96f93cb5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 18 Nov 2025 15:47:49 +0100 Subject: [PATCH 26/37] feat(developer): reserve space for KMX+ header in KMX written from kmcmplib For keyboards that are v19 or later, always reserve space for the KMX+ header that will be written out in kmc-kmn, to avoid rewriting the whole file. At this time, zero out that space. Test-bot: skip Build-bot: skip build:developer --- .../app/src/main/assets/keyboardharness.kpj | 2 +- common/test/keyboards/embed_osk/.gitignore | 1 + common/test/keyboards/embed_osk/embed_osk.kpj | 12 ++++ .../keyboards/embed_osk/source/embed_osk.kps | 33 +++++++++++ .../embed_osk/source/test_v19_kmxplus.kmn | 7 +++ developer/src/kmc-kmn/test/embed-osk.tests.ts | 56 +++++++++++++++++++ developer/src/kmcmplib/src/Compiler.cpp | 28 +++++----- 7 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 common/test/keyboards/embed_osk/.gitignore create mode 100644 common/test/keyboards/embed_osk/embed_osk.kpj create mode 100644 common/test/keyboards/embed_osk/source/embed_osk.kps create mode 100644 common/test/keyboards/embed_osk/source/test_v19_kmxplus.kmn create mode 100644 developer/src/kmc-kmn/test/embed-osk.tests.ts diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kpj b/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kpj index 80a3e1568a2..5df60525f84 100644 --- a/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kpj +++ b/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kpj @@ -7,6 +7,6 @@ True True True - lexicalmodel + keyboard diff --git a/common/test/keyboards/embed_osk/.gitignore b/common/test/keyboards/embed_osk/.gitignore new file mode 100644 index 00000000000..d16386367f7 --- /dev/null +++ b/common/test/keyboards/embed_osk/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/common/test/keyboards/embed_osk/embed_osk.kpj b/common/test/keyboards/embed_osk/embed_osk.kpj new file mode 100644 index 00000000000..2b5513fc525 --- /dev/null +++ b/common/test/keyboards/embed_osk/embed_osk.kpj @@ -0,0 +1,12 @@ + + + + $PROJECTPATH\build + $PROJECTPATH\source + True + True + False + keyboard + 2.0 + + diff --git a/common/test/keyboards/embed_osk/source/embed_osk.kps b/common/test/keyboards/embed_osk/source/embed_osk.kps new file mode 100644 index 00000000000..5ae723b1327 --- /dev/null +++ b/common/test/keyboards/embed_osk/source/embed_osk.kps @@ -0,0 +1,33 @@ + + + + 19.0.0.0 + 7.0 + + + + + + + test_v19_kmxplus + test_v19_kmxplus + + + + ..\build\test_v19_kmxplus.kmx + 0 + .kmx + + + + + test_v19_kmxplus + test_v19_kmxplus + 1.0 + + English + + + + + diff --git a/common/test/keyboards/embed_osk/source/test_v19_kmxplus.kmn b/common/test/keyboards/embed_osk/source/test_v19_kmxplus.kmn new file mode 100644 index 00000000000..7a4f6cad131 --- /dev/null +++ b/common/test/keyboards/embed_osk/source/test_v19_kmxplus.kmn @@ -0,0 +1,7 @@ +store(&VERSION) '19.0' +store(&NAME) 'Test KMX+ space reserved in header' +store(&TARGETS) 'desktop' + +begin Unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc-kmn/test/embed-osk.tests.ts b/developer/src/kmc-kmn/test/embed-osk.tests.ts new file mode 100644 index 00000000000..cf1c7cfbb90 --- /dev/null +++ b/developer/src/kmc-kmn/test/embed-osk.tests.ts @@ -0,0 +1,56 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import 'mocha'; +import { assert } from 'chai'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { KMX } from '@keymanapp/common-types'; +import { KmnCompiler } from '../src/main.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/'); +const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/'; + +describe('Compiler OSK Embedding', function() { + + it('should compile a keyboard and reserve space for the KMX+ data for v19+', async function() { + const compiler = new KmnCompiler(); + const callbacks = new TestCompilerCallbacks(); + assert.isTrue(await compiler.init(callbacks, { + saveDebug: false, + shouldAddCompilerVersion: false, + })); + assert.isTrue(compiler.verifyInitialized()); + + const fixtureDir = keyboardsDir + 'embed_osk/source/'; + const infile = fixtureDir + 'test_v19_kmxplus.kmn'; + const resultingKmxfile = __dirname + '/test_v19_kmxplus.kmx'; + const result = await compiler.run(infile, resultingKmxfile); + assert.isNotNull(result); + assert.isNotNull(result.artifacts.kmx); + + const expectedHeaderSize = KMX.KMXFile.COMP_KEYBOARD_SIZE + KMX.KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE; + + // We'll check that hte stores and groups start after the KMX+ struct + + assert.isAtLeast(result.artifacts.kmx.data.byteLength, expectedHeaderSize); + + const kmx = new KMX.KMXFile(); + const binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(result.artifacts.kmx.data); + assert.isAtLeast(binaryKeyboard.dpStoreArray, expectedHeaderSize); + assert.isAtLeast(binaryKeyboard.dpGroupArray, expectedHeaderSize); + + // Verify that the KMX+ flags are not set + + assert.equal(binaryKeyboard.dwFlags & KMX.KMXFile.KF_KMXPLUS, 0); + assert.equal(binaryKeyboard.dwFlags & KMX.KMXFile.KF_KMXPLUSOSK, 0); + + // the KMX+ struct itself should be empty at this point + + const bufferKmxPlus = new Uint8Array(result.artifacts.kmx.data.buffer, KMX.KMXFile.COMP_KEYBOARD_SIZE); + const binaryKmxPlus = kmx.COMP_KEYBOARD_KMXPLUSINFO.fromBuffer(bufferKmxPlus); + assert.equal(binaryKmxPlus.dpKMXPlus, 0); + assert.equal(binaryKmxPlus.dwKMXPlusSize, 0); + }); +}); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index b8b1aa6cb8c..e9b43fe05e8 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -3363,6 +3363,12 @@ KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataS wcslen(fk->szMessage)*2 + 2 +*/ fk->dwBitmapSize; + if(fk->version >= VERSION_190) { + // For version 19+, we always reserve space for the KMX+ + // header, but we'll write it out as null in kmcmplib + size += sizeof(COMP_KEYBOARD_KMXPLUSINFO); + } + for (i = 0, fgp = fk->dpGroupArray; i < fk->cxGroupArray; i++, fgp++) { if (kmcmp::FSaveDebug) size += u16len(fgp->szName) * 2 + 2; @@ -3404,21 +3410,13 @@ KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataS offset = sizeof(COMP_KEYBOARD); - /*ck->dpLanguageName = offset; - wcscpy((PWSTR)(buf + offset), fk->szLanguageName); - offset += wcslen(fk->szLanguageName)*2 + 2; - - ck->dpName = offset; - wcscpy((PWSTR)(buf + offset), fk->szName); - offset += wcslen(fk->szName)*2 + 2; - - ck->dpCopyright = offset; - wcscpy((PWSTR)(buf + offset), fk->szCopyright); - offset += wcslen(fk->szCopyright)*2 + 2; - - ck->dpMessage = offset; - wcscpy((PWSTR)(buf + offset), fk->szMessage); - offset += wcslen(fk->szMessage)*2 + 2;*/ + if(fk->version >= VERSION_190) { + // Reserved space for KMX+ data + COMP_KEYBOARD_KMXPLUSINFO *kmxPlusInfo = reinterpret_cast(buf + offset); + kmxPlusInfo->dpKMXPlus = 0; + kmxPlusInfo->dwKMXPlusSize = 0; + offset += sizeof(COMP_KEYBOARD_KMXPLUSINFO); + } ck->dpStoreArray = (KMX_DWORD)offset; sp = (PCOMP_STORE)(buf + offset); From f11803651d31cf6bf6e1b6fb6e2a4b1f979d58c6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 20 Nov 2025 10:47:55 +0100 Subject: [PATCH 27/37] refactor(developer): pass extra data from kmcmplib and move helpers for embed Move some modifier key conversion utilities to a shared unit and also add touchLayoutFilename and targetVersion to the kmcmplib extra data, in preparation for generating and embedding OSK data. Test-bot: skip --- .../src/consts/modifier-key-constants.ts | 58 +++++++++++++++++++ common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 2 +- common/web/types/src/kmx/kmx.ts | 6 +- common/web/types/src/main.ts | 2 +- .../src/kmc-kmn/src/compiler/compiler.ts | 50 ++++------------ developer/src/kmc-kmn/src/compiler/osk.ts | 42 +++++++++++++- .../src/compiler/visual-keyboard-compiler.ts | 37 +----------- developer/src/kmcmplib/include/kmcmplibapi.h | 2 + .../kmcmplib/src/CompileKeyboardBuffer.cpp | 3 + developer/src/kmcmplib/src/Compiler.cpp | 2 + docs/file-formats/kmx-plus-file-format.md | 2 +- 11 files changed, 125 insertions(+), 81 deletions(-) diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts index 245cbf70f37..59a40159a4d 100644 --- a/common/web/types/src/consts/modifier-key-constants.ts +++ b/common/web/types/src/consts/modifier-key-constants.ts @@ -4,6 +4,8 @@ * Modifier key bit-flags */ +import { VisualKeyboardShiftState } from "../kvk/visual-keyboard.js"; + export const ModifierKeyConstants = { // Define Keyman Developer modifier bit-flags (exposed for use by other modules) // Compare against /common/include/kmx_file.h. CTRL+F "#define LCTRLFLAG" to find the secton. @@ -30,4 +32,60 @@ export const ModifierKeyConstants = { // Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the // other modifier flag in layers, > 16 bit so not available here. // See keys_mod_other in keyman_core_ldml.ts +}; + +export const LDML_MODIFIER_TO_KVK_MODIFIER = new Map(); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboardShiftState.KVKS_LCTRL); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboardShiftState.KVKS_RCTRL); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LALTFLAG, VisualKeyboardShiftState.KVKS_LALT); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RALTFLAG, VisualKeyboardShiftState.KVKS_RALT); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKeyboardShiftState.KVKS_SHIFT); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboardShiftState.KVKS_CTRL); +LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboardShiftState.KVKS_ALT); + +export const KVK_MODIFIER_TO_LDML_MODIFIER = new Map(); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_LCTRL, ModifierKeyConstants.LCTRLFLAG); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_RCTRL, ModifierKeyConstants.RCTRLFLAG); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_LALT, ModifierKeyConstants.LALTFLAG); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_RALT, ModifierKeyConstants.RALTFLAG); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_SHIFT, ModifierKeyConstants.K_SHIFTFLAG); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_CTRL, ModifierKeyConstants.K_CTRLFLAG); +KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_ALT, ModifierKeyConstants.K_ALTFLAG); + +export function translateLdmlModifiersToVisualKeyboardShift(modifiers: number): VisualKeyboardShiftState { + if(modifiers == 0) { + return VisualKeyboardShiftState.KVKS_NORMAL; + } + + if(modifiers & + (ModifierKeyConstants.CAPITALFLAG | ModifierKeyConstants.NUMLOCKFLAG | ModifierKeyConstants.SCROLLFLAG) + ) { + // Caps/Num/Scroll are not supported in .kvk, in combination or alone + return null; + } + + let shift: VisualKeyboardShiftState = 0; + + for(const mod of LDML_MODIFIER_TO_KVK_MODIFIER.keys()) { + if(modifiers & mod) { + shift |= LDML_MODIFIER_TO_KVK_MODIFIER.get(mod); + } + } + + return shift; +} + +export function translateVisualKeyboardShiftToLdmlModifiers(shift: VisualKeyboardShiftState): number { + if(shift == 0) { + return 0; + } + + let mod = 0; + for(const state of KVK_MODIFIER_TO_LDML_MODIFIER.keys()) { + if(shift & state) { + mod |= KVK_MODIFIER_TO_LDML_MODIFIER.get(state); + } + } + + return mod; } diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 4ac971ee542..7864dd59de5 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -712,7 +712,7 @@ export { ListItem as ListItem }; * `KMXPlusFile`. */ export interface KMXPlusData { - sect?: Strs; // sect is ignored in-memory + sect?: Sect; // sect is ignored in-memory bksp?: Bksp; disp?: Disp; elem?: Elem; // elem is ignored in-memory diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts index 04901401a03..54732725fd5 100644 --- a/common/web/types/src/kmx/kmx.ts +++ b/common/web/types/src/kmx/kmx.ts @@ -52,8 +52,8 @@ export function versionStringToKmxVersion(version: string): KMX_Version { return null; } - // Version number is 16 bit number with MINOR in lower 8 bits, - // MAJOR in upper 8 bits. In practice, we now only use MAJOR + // Version number is 16 bit number with MINOR in lower 8 bits, + // MAJOR in upper 8 bits. In practice, we now only use MAJOR // version for Keyman versions. const num = major << 8; @@ -510,7 +510,7 @@ export class KMXFile { dpGroupArray: r.uint32le, // 0024 [LPGROUP] address of first item in group array StartGroup_ANSI: r.uint32le, // 0028 index of starting ANSI group - StartGroup_Unicode: r.uint32le, // 0028 index of starting Unicode groups + StartGroup_Unicode: r.uint32le, // 002C index of starting Unicode groups dwFlags: r.uint32le, // 0030 Flags for the keyboard file diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 779af42ba57..5c017273585 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -10,7 +10,7 @@ export * as KvkFile from './kvk/kvk-file.js'; export { USVirtualKeyCodes } from './consts/virtual-key-constants.js'; export * as Constants from './consts/virtual-key-constants.js'; -export { ModifierKeyConstants } from './consts/modifier-key-constants.js'; +export { ModifierKeyConstants, LDML_MODIFIER_TO_KVK_MODIFIER, KVK_MODIFIER_TO_LDML_MODIFIER, translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers } from './consts/modifier-key-constants.js'; export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js'; diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index c85eddc1641..26e51ea36b2 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -6,14 +6,15 @@ TODO: implement additional interfaces: */ // TODO: rename wasm-host? -import { VisualKeyboard, KvkFileReader, LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter, ObjectWithCompileContext } from '@keymanapp/common-types'; +import { LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter, ObjectWithCompileContext, KMX } from '@keymanapp/common-types'; import { CompilerCallbacks, CompilerEvent, CompilerOptions, KeymanCompiler, KeymanCompilerArtifacts, - KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, KvksFileReader, + KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, CompilerError } from '@keymanapp/developer-utils'; import * as Osk from './osk.js'; import loadWasmHost from '../import/kmcmplib/wasm-host.js'; +import { loadKvkFile } from './osk.js'; import { KmnCompilerMessages } from './kmn-compiler-messages.js'; import { WriteCompiledKeyboard } from '../kmw-compiler/kmw-compiler.js'; @@ -56,9 +57,11 @@ export interface KmnCompilerResultExtra { */ targets: number; kvksFilename?: string; + touchLayoutFilename?: string; displayMapFilename?: string; stores: CompilerResultExtraStore[]; groups: CompilerResultExtraGroup[]; + targetVersion: KMX.KMX_Version; }; /** @internal */ @@ -224,9 +227,11 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet extra: { targets: wasm_result.extra.targets, displayMapFilename: wasm_result.extra.displayMapFilename, + touchLayoutFilename: wasm_result.extra.touchLayoutFilename, kvksFilename: wasm_result.extra.kvksFilename, stores: [], groups: [], + targetVersion: wasm_result.extra.targetVersion, }, displayMap: null }; @@ -362,7 +367,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet // KeymanWeb compiler // - if(wasm_result.extra.targets & COMPILETARGETS_JS) { + if(result.extra.targets & COMPILETARGETS_JS) { wasm_options.target = 1; // CKF_KEYMANWEB TODO use COMPILETARGETS_JS // We always want debug data in the intermediate .kmx, so that error @@ -424,45 +429,11 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet private runKvkCompiler(kvksFilename: string, kmnFilename: string, kmxFilename: string, displayMap?: Osk.PuaMap) { // The compiler detected a .kvks file, which needs to be captured - kvksFilename = this.callbacks.resolveFilename(kmnFilename, kvksFilename); - const data = this.callbacks.loadFile(kvksFilename); - if(!data) { - this.callbacks.reportMessage(KmnCompilerMessages.Error_FileNotFound({filename: kvksFilename})); + const vk = loadKvkFile(this.callbacks.resolveFilename(kmnFilename, kvksFilename), this.callbacks); + if(!vk) { return null; } - const filename = this.callbacks.path.basename(kvksFilename); - let basename = null; - let vk: VisualKeyboard.VisualKeyboard = null; - if(filename.endsWith('.kvk')) { - /* Legacy keyboards may reference a binary .kvk. That's not an error */ - // TODO: (lowpri) add hint to convert to .kvks? - basename = this.callbacks.path.basename(kvksFilename, KeymanFileTypes.Binary.VisualKeyboard); - const reader = new KvkFileReader(); - try { - vk = reader.read(data); - } catch(e) { - this.callbacks.reportMessage(KmnCompilerMessages.Error_InvalidKvkFile({filename, e})); - return null; - } - } else { - basename = this.callbacks.path.basename(kvksFilename, KeymanFileTypes.Source.VisualKeyboard); - const reader = new KvksFileReader(); - let kvks = null; - try { - kvks = reader.read(data); - reader.validate(kvks); - } catch(e) { - this.callbacks.reportMessage(KmnCompilerMessages.Error_InvalidKvksFile({filename, e})); - return null; - } - const invalidVkeys: string[] = []; - vk = reader.transform(kvks, invalidVkeys); - for(const invalidVkey of invalidVkeys) { - this.callbacks.reportMessage(KmnCompilerMessages.Warn_InvalidVkeyInKvksFile({filename, invalidVkey})); - } - } - // Make sure that we maintain the correspondence between source keyboard and // .kvk. Appears to be used currently only by Windows package installer. vk.header.associatedKeyboard = this.keyboardIdFromKmnFilename(kmnFilename); @@ -472,6 +443,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet Osk.remapVisualKeyboard(vk, displayMap); } + const basename = this.callbacks.path.basename(kvksFilename, this.callbacks.path.extname(kvksFilename)); const writer = new KvkFileWriter(); return { filename: this.callbacks.path.join(this.callbacks.path.dirname(kmxFilename), diff --git a/developer/src/kmc-kmn/src/compiler/osk.ts b/developer/src/kmc-kmn/src/compiler/osk.ts index e26c124b15b..81f97280859 100644 --- a/developer/src/kmc-kmn/src/compiler/osk.ts +++ b/developer/src/kmc-kmn/src/compiler/osk.ts @@ -1,6 +1,8 @@ -import { TouchLayout } from "@keymanapp/common-types"; +import { KvkFileReader, TouchLayout } from "@keymanapp/common-types"; import { VisualKeyboard } from "@keymanapp/common-types"; import { SchemaValidators } from "@keymanapp/common-types"; +import { KmnCompilerMessages } from "./kmn-compiler-messages.js"; +import { CompilerCallbacks, KvksFileReader } from "@keymanapp/developer-utils"; export interface StringRefUsage { filename: string; @@ -123,3 +125,41 @@ export function remapTouchLayout(source: TouchLayout.TouchLayoutFile, map: PuaMa return dirty; } + +export function loadKvkFile(kvksFilename: string, callbacks: CompilerCallbacks): VisualKeyboard.VisualKeyboard { + const data = callbacks.loadFile(kvksFilename); + if(!data) { + callbacks.reportMessage(KmnCompilerMessages.Error_FileNotFound({filename: kvksFilename})); + return null; + } + + const filename = callbacks.path.basename(kvksFilename); + let vk: VisualKeyboard.VisualKeyboard = null; + if(filename.endsWith('.kvk')) { + /* Legacy keyboards may reference a binary .kvk. That's not an error */ + // TODO: (lowpri) add hint to convert to .kvks? + const reader = new KvkFileReader(); + try { + vk = reader.read(data); + } catch(e) { + callbacks.reportMessage(KmnCompilerMessages.Error_InvalidKvkFile({filename, e})); + return null; + } + } else { + const reader = new KvksFileReader(); + let kvks = null; + try { + kvks = reader.read(data); + reader.validate(kvks); + } catch(e) { + callbacks.reportMessage(KmnCompilerMessages.Error_InvalidKvksFile({filename, e})); + return null; + } + const invalidVkeys: string[] = []; + vk = reader.transform(kvks, invalidVkeys); + for(const invalidVkey of invalidVkeys) { + callbacks.reportMessage(KmnCompilerMessages.Warn_InvalidVkeyInKvksFile({filename, invalidVkey})); + } + } + return vk; +} \ No newline at end of file diff --git a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts index 46d4ff4ccb9..53e73336262 100644 --- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts @@ -5,7 +5,7 @@ * to .kvk format. This is an interim solution until Keyman Core supports * interrogation of the KMX+ data for OSK. */ -import { ModifierKeyConstants, KMXPlus, VisualKeyboard } from "@keymanapp/common-types"; +import { KMXPlus, VisualKeyboard, translateLdmlModifiersToVisualKeyboardShift } from "@keymanapp/common-types"; import { CompilerCallbacks } from "@keymanapp/developer-utils"; import { LdmlCompilerMessages } from "./ldml-compiler-messages.js"; import { constants } from "@keymanapp/ldml-keyboard-constants"; @@ -27,16 +27,6 @@ function findLast(arr: any, callback: any) { return undefined; } - -const LDML_MODIFIER_TO_KVK_MODIFIER = new Map(); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_LCTRL); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_RCTRL); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_LALT); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_ALT); - export class LdmlKeyboardVisualKeyboardCompiler { public constructor(private callbacks: CompilerCallbacks) { } @@ -98,7 +88,7 @@ export class LdmlKeyboardVisualKeyboardCompiler { hardware = 'us'; // TODO-LDML: US Only. We need to clean this up for other hardware forms - const shift = this.translateLayerModifiersToVisualKeyboardShift(layer.mod); + const shift = translateLdmlModifiersToVisualKeyboardShift(layer.mod); if(shift === null) { // Caps (num, scroll) is not a supported shift state in .kvk return null; @@ -148,27 +138,4 @@ export class LdmlKeyboardVisualKeyboardCompiler { return value.replaceAll(/\uffff\u0008./g, ''); } - private translateLayerModifiersToVisualKeyboardShift(modifiers: number): VisualKeyboard.VisualKeyboardShiftState { - - if(modifiers == 0) { - return VisualKeyboard.VisualKeyboardShiftState.KVKS_NORMAL; - } - - if(modifiers & - (ModifierKeyConstants.CAPITALFLAG | ModifierKeyConstants.NUMLOCKFLAG | ModifierKeyConstants.SCROLLFLAG) - ) { - // Caps/Num/Scroll are not supported in .kvk, in combination or alone - return null; - } - - let shift: VisualKeyboard.VisualKeyboardShiftState = 0; - - for(const mod of LDML_MODIFIER_TO_KVK_MODIFIER.keys()) { - if(modifiers & mod) { - shift |= LDML_MODIFIER_TO_KVK_MODIFIER.get(mod); - } - } - - return shift; - } } diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index e4700ff0002..a0087f3d4c7 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -72,9 +72,11 @@ struct KMCMP_COMPILER_RESULT_EXTRA { int targets; /// COMPILETARGETS__MASK = COMPILETARGETS_KMX | COMPILETARGETS_JS std::string kmnFilename; std::string kvksFilename; + std::string touchLayoutFilename; std::string displayMapFilename; std::vector stores; std::vector groups; + int targetVersion; }; struct KMCMP_COMPILER_RESULT { diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp index d9e570c5cbe..1714149c403 100644 --- a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp @@ -53,6 +53,7 @@ bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) fk->extra->displayMapFilename = ""; fk->extra->stores.clear(); fk->extra->groups.clear(); + fk->extra->targetVersion = VERSION_60; /* fk->szMessage[0] = 0; fk->szLanguageName[0] = 0;*/ fk->dwBitmapSize = 0; @@ -197,5 +198,7 @@ namespace kmcmp { extraGroup.name = string_from_u16string(group->szName); fk->extra->groups.push_back(extraGroup); } + + fk->extra->targetVersion = fk->version; } } diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index e9b43fe05e8..c278bbd2902 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -1208,6 +1208,7 @@ KMX_BOOL ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE s sp->dpString = q; } break; + case TSS_KMW_RTL: case TSS_KMW_HELPTEXT: if(!VerifyKeyboardVersion(fk, VERSION_70)) { @@ -1298,6 +1299,7 @@ KMX_BOOL ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE s return FALSE; } // Used by KMW compiler + fk->extra->touchLayoutFilename = string_from_u16string(sp->dpString); break; case TSS_KEYBOARDVERSION: // I4140 diff --git a/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md index 29f7ddfd725..72237425f20 100644 --- a/docs/file-formats/kmx-plus-file-format.md +++ b/docs/file-formats/kmx-plus-file-format.md @@ -9,7 +9,7 @@ This document discusses the binary format for KMXPlus, which contains keyboards converted from source LDML data. -Draft spec PR: +Specification: ## C7043.1 Principles From 75847a389f87210779feba6d1a8cc180a6471b6f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 21 Nov 2025 10:48:11 +0100 Subject: [PATCH 28/37] Apply suggestions from code review Co-authored-by: Eberhard Beilharz --- core/include/ldml/keyman_core_ldml.ts | 2 +- developer/docs/internal/keyman-touch-layout-to-kmx-plus.md | 7 ++----- developer/docs/internal/kvk-to-kmx-plus.md | 5 +++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/core/include/ldml/keyman_core_ldml.ts b/core/include/ldml/keyman_core_ldml.ts index 388cd348ed6..c9c14935dee 100644 --- a/core/include/ldml/keyman_core_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -534,7 +534,7 @@ class Constants { */ readonly layr_form_flags_show_base_layout = 0x00000001; /** - * in layr.form.flags, if set, then left/righ tCtrl and Alt keys function independently + * in layr.form.flags, if set, then left/right Ctrl and Alt keys function independently */ readonly layr_form_flags_chiral_separate = 0x00000002; diff --git a/developer/docs/internal/keyman-touch-layout-to-kmx-plus.md b/developer/docs/internal/keyman-touch-layout-to-kmx-plus.md index 054f4482437..09f24e5bece 100644 --- a/developer/docs/internal/keyman-touch-layout-to-kmx-plus.md +++ b/developer/docs/internal/keyman-touch-layout-to-kmx-plus.md @@ -10,7 +10,7 @@ Note that counts and headers will need to merge both .kvks and .keyman-touch-lay ``` touch-layout - tablet|phone|desktop -> determines minDeviceWidth or hardware + tablet|phone|desktop -> determines minDeviceWidth and hardware font: meta.fontFaceName fontsize: meta.fontSize displayUnderlying: lay2.forms.flags.showBaseLayout @@ -102,7 +102,7 @@ For each `key` element in .ktl: | ∆ | Bits | Name | Description | Content |---|------|---------------- |----------------------------------------------------------|----------------------- | 0+| 32 | to | str: output string OR UTF-32LE codepoint | `key.text`* -| 4+| 32 | flags | int: per-key flags | bit 1 if gap +| 4+| 32 | flags | int: per-key flags | bit 1 set if gap | 8+| 32 | id | str: key id | `key.layer` + ':' + `key.id`? |12+| 32 | switch | str: layer id to switch to | `key.nextlayer` |16+| 32 | width | int: key width*10 (supports 0.1 as min width) | `key.width / 10` @@ -113,9 +113,6 @@ For each `key` element in .ktl: * specials will be mapped to the corresponding `dis2`.flags.specialKeyCap value -## `dis2` - - ## `layr` diff --git a/developer/docs/internal/kvk-to-kmx-plus.md b/developer/docs/internal/kvk-to-kmx-plus.md index 1a04b7cd847..6f440895aeb 100644 --- a/developer/docs/internal/kvk-to-kmx-plus.md +++ b/developer/docs/internal/kvk-to-kmx-plus.md @@ -67,8 +67,9 @@ Graphical keys should have a corresponding element added to `dis2`. Each layer corresponds to a .kvks `visualkeyboard/encoding[@name=unicode]/layer` element. -The `layer[@shift]` mapping is from [`VisualKeyboardLegalShiftStates[].name`](https://github.com/keymanapp/keyman/blob/7ac6bfc189333c5758fb14ef1cc0c810e1460b59/common/web/types/src/kvk/visual-keyboard.ts#L63) to keys.key.mod -and `K_MODIFIERFLAG` bitmask (this is not the same as the KVK bitmasks, which shall be deprecated) +The `layer[@shift]` mapping is from [`VisualKeyboardLegalShiftStates[].name`](https://github.com/keymanapp/keyman/blob/7ac6bfc189333c5758fb14ef1cc0c810e1460b59/common/web/types/src/kvk/visual-keyboard.ts#L63) +to keys.key.mod and `K_MODIFIERFLAG` bitmask (this is not the same as the KVK bitmasks, +which shall be deprecated) The compiler starts by filling out a blank representation of each modifier layer, then iterates through the .kvk `visualkeyboard/encoding/layer/key`, keying off the attribute `vkey`. From d033c634d736b18fe07a68a7c74008e075c31722 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 21 Nov 2025 10:48:51 +0100 Subject: [PATCH 29/37] Update developer/src/kmc-kmn/test/embed-osk.tests.ts Co-authored-by: Eberhard Beilharz --- developer/src/kmc-kmn/test/embed-osk.tests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/developer/src/kmc-kmn/test/embed-osk.tests.ts b/developer/src/kmc-kmn/test/embed-osk.tests.ts index cf1c7cfbb90..5c68c30a53e 100644 --- a/developer/src/kmc-kmn/test/embed-osk.tests.ts +++ b/developer/src/kmc-kmn/test/embed-osk.tests.ts @@ -32,7 +32,8 @@ describe('Compiler OSK Embedding', function() { const expectedHeaderSize = KMX.KMXFile.COMP_KEYBOARD_SIZE + KMX.KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE; - // We'll check that hte stores and groups start after the KMX+ struct + // We'll check that the stores and groups start after the KMX+ struct + assert.isAtLeast(result.artifacts.kmx.data.byteLength, expectedHeaderSize); From cecc518db0db0c7a62e7087143f6863a9682e234 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 21 Nov 2025 14:45:29 +0100 Subject: [PATCH 30/37] Update common/web/types/src/consts/modifier-key-constants.ts Co-authored-by: Eberhard Beilharz --- common/web/types/src/consts/modifier-key-constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts index 59a40159a4d..6475a3444cd 100644 --- a/common/web/types/src/consts/modifier-key-constants.ts +++ b/common/web/types/src/consts/modifier-key-constants.ts @@ -76,7 +76,7 @@ export function translateLdmlModifiersToVisualKeyboardShift(modifiers: number): } export function translateVisualKeyboardShiftToLdmlModifiers(shift: VisualKeyboardShiftState): number { - if(shift == 0) { + if(shift == VisualKeyboardShiftState.KVKS_NORMAL) { return 0; } From c546bf3bd6bd412bdba1c1fb65e762e4ccff2d72 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 24 Nov 2025 05:34:27 +0100 Subject: [PATCH 31/37] feat(developer): inject KMX+ into .kmx Add a function to inject KMX+ data into a .kmx file, along with unit tests. Groundwork for embedding the OSK into KMX. Test-bot: skip --- .../src/compiler/embed-osk/embed-osk.ts | 82 ++++++ developer/src/kmc-kmn/test/embed-osk.tests.ts | 253 ++++++++++++++++-- 2 files changed, 306 insertions(+), 29 deletions(-) create mode 100644 developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts new file mode 100644 index 00000000000..e3881778893 --- /dev/null +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts @@ -0,0 +1,82 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-11-24 + * + * Convert Keyman .kvks and .keyman-touch-layout files to KMX+ format and embed + * in .kmx. + */ +import { CompilerCallbacks } from "@keymanapp/developer-utils"; +import { KmnCompilerOptions } from "../compiler.js"; +import { KMX } from "@keymanapp/common-types"; + +export class EmbedOskInKmx { + constructor( + public callbacks: CompilerCallbacks, //TODO-EMBED-OSK-IN-KMX: private + public options: KmnCompilerOptions //TODO-EMBED-OSK-IN-KMX: private + ) { + } + + /** + * Takes an existing KMX file, which must have a version >= 19.0, and must + * have space pre-allocated for the KMX+ header, and injects the prebuilt KMX+ + * kmxPlusBinary data blob, and updates the header flag to indicate that the + * file now includes OSK data. + * @param inputFile + * @param kmxPlusBinary + * @returns + */ + private injectKmxPlusIntoKmxFile(inputFile: Uint8Array, kmxPlusBinary: Uint8Array): Uint8Array { + const kmx = new KMX.KMXFile(); + + const HEADER_SIZE = KMX.KMXFile.COMP_KEYBOARD_SIZE + KMX.KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE; + if(inputFile.byteLength < HEADER_SIZE) { + throw new Error(`Expected inputFile to have space for COMP_KEYBOARD_KMXPLUSINFO, but was only ${inputFile.byteLength} bytes`); + } + + const keyboardHeader = kmx.COMP_KEYBOARD.fromBuffer(inputFile); + if(keyboardHeader.dwFileVersion < KMX.KMX_Version.VERSION_190) { + throw new Error(`Expected inputFile to be at least VERSION_190, but it was ${keyboardHeader.dwFileVersion}`); + } + + // Verify that the input file has space reserved by looking at the two + // fields with offsets in the header. kmcmplib should have reserved space + // for the header for v19.0+ keyboards + + if(keyboardHeader.dpStoreArray < HEADER_SIZE || keyboardHeader.dpGroupArray < HEADER_SIZE) { + throw new Error( + `Expected reservation for COMP_KEYBOARD_KMXPLUSINFO, but store (${keyboardHeader.dpStoreArray})`+ + ` or group (${keyboardHeader.dpGroupArray}) offsets are too low` + ); + } + + const comp_kmxplus: KMX.BUILDER_COMP_KEYBOARD_KMXPLUSINFO = { + dpKMXPlus: inputFile.byteLength, + dwKMXPlusSize: kmxPlusBinary.byteLength + }; + + const outputFile = new Uint8Array(comp_kmxplus.dpKMXPlus + comp_kmxplus.dwKMXPlusSize); + + // Copy input KMX data + outputFile.set(inputFile, 0); + + // KMX+ header COMP_KEYBOARD_KMXPLUSINFO is written immediately after COMP_KEYBOARD + const kmxPlusHeaderBinary: Uint8Array = kmx.COMP_KEYBOARD_KMXPLUSINFO.toBuffer(comp_kmxplus); + outputFile.set(kmxPlusHeaderBinary, KMX.KMXFile.COMP_KEYBOARD_SIZE); + + // Poke the KF_KMXPLUSOSK flag into COMP_KEYBOARD.dwFlags + keyboardHeader.dwFlags = keyboardHeader.dwFlags | KMX.KMXFile.KF_KMXPLUSOSK; + const keyboardHeaderBinary = kmx.COMP_KEYBOARD.toBuffer(keyboardHeader); + outputFile.set(keyboardHeaderBinary, 0); + + // Append KMX+ data at the end of the existing file + outputFile.set(kmxPlusBinary, inputFile.byteLength); + + return outputFile; + } + + public readonly unitTestEndpoints = { + injectKmxPlusIntoKmxFile: this.injectKmxPlusIntoKmxFile.bind(this), + }; + +}; diff --git a/developer/src/kmc-kmn/test/embed-osk.tests.ts b/developer/src/kmc-kmn/test/embed-osk.tests.ts index 5c68c30a53e..c0c9de77b25 100644 --- a/developer/src/kmc-kmn/test/embed-osk.tests.ts +++ b/developer/src/kmc-kmn/test/embed-osk.tests.ts @@ -8,50 +8,245 @@ import { assert } from 'chai'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { KMX } from '@keymanapp/common-types'; import { KmnCompiler } from '../src/main.js'; +import { EmbedOskInKmx } from '../src/compiler/embed-osk/embed-osk.js'; const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/'); const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/'; describe('Compiler OSK Embedding', function() { - it('should compile a keyboard and reserve space for the KMX+ data for v19+', async function() { - const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); - assert.isTrue(await compiler.init(callbacks, { - saveDebug: false, - shouldAddCompilerVersion: false, - })); - assert.isTrue(compiler.verifyInitialized()); + const callbacks = new TestCompilerCallbacks(); - const fixtureDir = keyboardsDir + 'embed_osk/source/'; - const infile = fixtureDir + 'test_v19_kmxplus.kmn'; - const resultingKmxfile = __dirname + '/test_v19_kmxplus.kmx'; - const result = await compiler.run(infile, resultingKmxfile); - assert.isNotNull(result); - assert.isNotNull(result.artifacts.kmx); + this.beforeEach(function() { + callbacks.clear(); + }); + + this.afterEach(function() { + if(this.currentTest.isFailed()) { + callbacks.printMessages(); + } + }); + + describe('kmcmplib infrastructure', function() { + + it('should compile a keyboard and reserve space for the KMX+ data for v19+', async function() { + // This tests kmcmplib, but is implemented in kmc-kmn for simplicity + + const compiler = new KmnCompiler(); + assert.isTrue(await compiler.init(callbacks, { + saveDebug: false, + shouldAddCompilerVersion: false, + // TODO-EMBED-OSK-IN-KMX: test_skip_osk_embed: true --> or mock stub for EmbedOskInKmx function + })); + assert.isTrue(compiler.verifyInitialized()); + + const fixtureDir = keyboardsDir + 'embed_osk/source/'; + const infile = fixtureDir + 'test_v19_kmxplus.kmn'; + const resultingKmxfile = __dirname + '/test_v19_kmxplus.kmx'; + const result = await compiler.run(infile, resultingKmxfile); + assert.isNotNull(result); + assert.isNotNull(result.artifacts.kmx); + + const expectedHeaderSize = KMX.KMXFile.COMP_KEYBOARD_SIZE + KMX.KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE; + + // We'll check that the stores and groups start after the KMX+ struct + + assert.isAtLeast(result.artifacts.kmx.data.byteLength, expectedHeaderSize); + + const kmx = new KMX.KMXFile(); + const binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(result.artifacts.kmx.data); + assert.isAtLeast(binaryKeyboard.dpStoreArray, expectedHeaderSize); + assert.isAtLeast(binaryKeyboard.dpGroupArray, expectedHeaderSize); + + // Verify that the KMX+ flags are not set + + assert.equal(binaryKeyboard.dwFlags & KMX.KMXFile.KF_KMXPLUS, 0); + assert.equal(binaryKeyboard.dwFlags & KMX.KMXFile.KF_KMXPLUSOSK, 0); + + // the KMX+ struct itself should be empty at this point + + const bufferKmxPlus = new Uint8Array(result.artifacts.kmx.data.buffer, KMX.KMXFile.COMP_KEYBOARD_SIZE); + const binaryKmxPlus = kmx.COMP_KEYBOARD_KMXPLUSINFO.fromBuffer(bufferKmxPlus); + assert.equal(binaryKmxPlus.dpKMXPlus, 0); + assert.equal(binaryKmxPlus.dwKMXPlusSize, 0); + }); + }); + + describe('EmbedOskInKmx', function() { + + describe('EmbedOskInKmx.injectKmxPlusIntoKmxFile', function() { + const embedder = new EmbedOskInKmx(callbacks, {}); + + // This is not a valid KMX+ blob, but the function does not do any + // validation of the contents of the KMX+ blob, but just injects what it + // is given, so we can inject this and look for it at EOF + const sentinelKmxPlusBlob = new TextEncoder().encode('SENTINEL'); + + // None of these should ever be possible on outputs from kmcmplib in our + // code paths, so we'll treat these as internal errors rather than a + // compiler error + + it('should throw if passed a zero-byte KMX file', function() { + const zeroByteFile = new Uint8Array(0); + assert.throws(function() { embedder.unitTestEndpoints.injectKmxPlusIntoKmxFile(zeroByteFile, sentinelKmxPlusBlob) }); + }); + + it('should throw if passed a version 17 KMX file', function() { + // This file has a v17 header but is not a full file, just for test purposes + // taken from k_000__null_keyboard.kmx and tweaked to v.17, made it long + // enough for the KMX+ header but did not adjust offsets + const invalid17KmxFile = new Uint8Array([ + // COMP_KEYBOARD + 0x4B, 0x58, 0x54, 0x53, // dwIdentifier = 'KXTS' + 0x00, 0x11, 0x00, 0x00, // dwFileVersion = 17.0 + 0x00, 0x00, 0x00, 0x00, // dwCheckSum + 0x00, 0x00, 0x00, 0x00, // KeyboardID + 0x01, 0x00, 0x00, 0x00, // IsRegistered + 0x00, 0x00, 0x00, 0x00, // version (unused) + 0x06, 0x00, 0x00, 0x00, // cxStoreArray + 0x01, 0x00, 0x00, 0x00, // cxGroupArray + 0x40, 0x00, 0x00, 0x00, // dpStoreArray + 0x1A, 0x01, 0x00, 0x00, // dpGroupArray + 0xFF, 0xFF, 0xFF, 0xFF, // StartGroup[ansi] + 0x00, 0x00, 0x00, 0x00, // StartGroup[unicode] + 0x00, 0x00, 0x00, 0x00, // dwFlags + 0x00, 0x00, 0x00, 0x00, // dwHotKey + 0x3C, 0x01, 0x00, 0x00, // dpBitmapOffset + 0x00, 0x00, 0x00, 0x00, // dwBitmapSize + // COMP_KEYBOARD_KMXPLUSINFO + 0x00, 0x00, 0x00, 0x00, // dpKMXPlus + 0x00, 0x00, 0x00, 0x00, // dwKMXPlusSize + ]); + + assert.throws(function() { embedder.unitTestEndpoints.injectKmxPlusIntoKmxFile(invalid17KmxFile, sentinelKmxPlusBlob) }, 'Expected inputFile to be at least VERSION_190'); + }); + + it('should throw if passed a version 19 KMX file that is too short', function() { + // This file has a v19 header but is not a full file, just for test purposes + // taken from k_000__null_keyboard.kmx and tweaked to v.19. Missing space + // for the KMX+ header + const invalid19KmxFileShort = new Uint8Array([ + // COMP_KEYBOARD + 0x4B, 0x58, 0x54, 0x53, // dwIdentifier = 'KXTS' + 0x00, 0x13, 0x00, 0x00, // dwFileVersion = 19.0 + 0x00, 0x00, 0x00, 0x00, // dwCheckSum + 0x00, 0x00, 0x00, 0x00, // KeyboardID + 0x01, 0x00, 0x00, 0x00, // IsRegistered + 0x00, 0x00, 0x00, 0x00, // version (unused) + 0x06, 0x00, 0x00, 0x00, // cxStoreArray + 0x01, 0x00, 0x00, 0x00, // cxGroupArray + 0x40, 0x00, 0x00, 0x00, // dpStoreArray + 0x1A, 0x01, 0x00, 0x00, // dpGroupArray + 0xFF, 0xFF, 0xFF, 0xFF, // StartGroup[ansi] + 0x00, 0x00, 0x00, 0x00, // StartGroup[unicode] + 0x00, 0x00, 0x00, 0x00, // dwFlags + 0x00, 0x00, 0x00, 0x00, // dwHotKey + 0x3C, 0x01, 0x00, 0x00, // dpBitmapOffset + 0x00, 0x00, 0x00, 0x00, // dwBitmapSize + ]); + assert.throws(function() { embedder.unitTestEndpoints.injectKmxPlusIntoKmxFile(invalid19KmxFileShort, sentinelKmxPlusBlob) }, 'Expected inputFile to have space'); + }); + + it('should throw if passed a version 19 KMX file that has no reservation for KMX+ header', function() { + // This file has a v19 header but is not a full file, just for test purposes + // taken from k_000__null_keyboard.kmx and tweaked to v.19. Has space for + // KMX+ header but store and group offsets are not accounting for the space + const invalid19KmxFileNoReservation = new Uint8Array([ + // COMP_KEYBOARD + 0x4B, 0x58, 0x54, 0x53, // dwIdentifier = 'KXTS' + 0x00, 0x13, 0x00, 0x00, // dwFileVersion = 19.0 + 0x00, 0x00, 0x00, 0x00, // dwCheckSum + 0x00, 0x00, 0x00, 0x00, // KeyboardID + 0x01, 0x00, 0x00, 0x00, // IsRegistered + 0x00, 0x00, 0x00, 0x00, // version (unused) + 0x06, 0x00, 0x00, 0x00, // cxStoreArray + 0x01, 0x00, 0x00, 0x00, // cxGroupArray + 0x40, 0x00, 0x00, 0x00, // dpStoreArray -- note 0x40 offset + 0x1A, 0x01, 0x00, 0x00, // dpGroupArray + 0xFF, 0xFF, 0xFF, 0xFF, // StartGroup[ansi] + 0x00, 0x00, 0x00, 0x00, // StartGroup[unicode] + 0x00, 0x00, 0x00, 0x00, // dwFlags + 0x00, 0x00, 0x00, 0x00, // dwHotKey + 0x3C, 0x01, 0x00, 0x00, // dpBitmapOffset + 0x00, 0x00, 0x00, 0x00, // dwBitmapSize + // COMP_KEYBOARD_KMXPLUSINFO + 0x00, 0x00, 0x00, 0x00, // dpKMXPlus + 0x00, 0x00, 0x00, 0x00, // dwKMXPlusSize + ]); + assert.throws(function() { embedder.unitTestEndpoints.injectKmxPlusIntoKmxFile(invalid19KmxFileNoReservation, sentinelKmxPlusBlob) }, 'Expected reservation for COMP_KEYBOARD_KMXPLUSINFO'); + }); + + it('should successfully inject a KMX+ blob into a v19 KMX file', function() { + // This file has a v19 header but is not a full file, just for test purposes + // taken from k_000__null_keyboard.kmx and tweaked to v.19. Has space for + // KMX+ header but store and group offsets are not accounting for the space + const semiValid19KmxFile = new Uint8Array([ + // COMP_KEYBOARD + 0x4B, 0x58, 0x54, 0x53, // dwIdentifier = 'KXTS' + 0x00, 0x13, 0x00, 0x00, // dwFileVersion = 19.0 + 0x00, 0x00, 0x00, 0x00, // dwCheckSum + 0x00, 0x00, 0x00, 0x00, // KeyboardID + 0x01, 0x00, 0x00, 0x00, // IsRegistered + 0x00, 0x00, 0x00, 0x00, // version (unused) + 0x01, 0x00, 0x00, 0x00, // cxStoreArray + 0x01, 0x00, 0x00, 0x00, // cxGroupArray + 0x48, 0x00, 0x00, 0x00, // dpStoreArray -- note 0x48 offset + 0x4C, 0x00, 0x00, 0x00, // dpGroupArray -- note 0x4C offset + 0xFF, 0xFF, 0xFF, 0xFF, // StartGroup[ansi] + 0x00, 0x00, 0x00, 0x00, // StartGroup[unicode] + 0x00, 0x00, 0x00, 0x00, // dwFlags + 0x00, 0x00, 0x00, 0x00, // dwHotKey + 0x3C, 0x01, 0x00, 0x00, // dpBitmapOffset + 0x00, 0x00, 0x00, 0x00, // dwBitmapSize + // COMP_KEYBOARD_KMXPLUSINFO + 0x00, 0x00, 0x00, 0x00, // dpKMXPlus + 0x00, 0x00, 0x00, 0x00, // dwKMXPlusSize + // Fake additional data - just to verify that we don't clobber it + 0x01, 0x02, 0x03, 0x04, // "dpStoreArray" + 0x05, 0x06, 0x07, 0x08, // "dpGroupArray" + ]); - const expectedHeaderSize = KMX.KMXFile.COMP_KEYBOARD_SIZE + KMX.KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE; + // good path - // We'll check that the stores and groups start after the KMX+ struct + const data = embedder.unitTestEndpoints.injectKmxPlusIntoKmxFile(semiValid19KmxFile, sentinelKmxPlusBlob); + assert.isNotNull(data); + assert.equal(data.byteLength, semiValid19KmxFile.byteLength + sentinelKmxPlusBlob.byteLength); + assert.deepEqual( + data.slice(0x00, 0x30), semiValid19KmxFile.slice(0x00, 0x30), + 'Expected start of header to be unmodified' + ); - assert.isAtLeast(result.artifacts.kmx.data.byteLength, expectedHeaderSize); + assert.deepEqual( + data.slice(0x34, 0x40), semiValid19KmxFile.slice(0x34, 0x40), + 'Expected end of header to be unmodified' + ); - const kmx = new KMX.KMXFile(); - const binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(result.artifacts.kmx.data); - assert.isAtLeast(binaryKeyboard.dpStoreArray, expectedHeaderSize); - assert.isAtLeast(binaryKeyboard.dpGroupArray, expectedHeaderSize); + assert.deepEqual( + data.slice(0x30, 0x34), new Uint8Array([0x40, 0, 0, 0]), + 'Expected KF_KMXPLUSOSK flag to have been set in COMP_KEYBOARD.dwFlags' + ); - // Verify that the KMX+ flags are not set + assert.deepEqual( + data.slice(0x40, 0x44), new Uint8Array([semiValid19KmxFile.byteLength, 0, 0, 0]), + 'Expected COMP_KEYBOARD_KMXPLUSINFO.dpKMXPlus to have been updated to length of original file' + ); - assert.equal(binaryKeyboard.dwFlags & KMX.KMXFile.KF_KMXPLUS, 0); - assert.equal(binaryKeyboard.dwFlags & KMX.KMXFile.KF_KMXPLUSOSK, 0); + assert.deepEqual( + data.slice(0x44, 0x48), new Uint8Array([sentinelKmxPlusBlob.byteLength, 0, 0, 0]), + 'Expected COMP_KEYBOARD_KMXPLUSINFO.dwKMXPlusSize to have been updated' + ); - // the KMX+ struct itself should be empty at this point + assert.deepEqual( + data.slice(0x48, 0x50), new Uint8Array([1,2,3,4,5,6,7,8]), + 'Expected KMX data to have not been modified' + ); - const bufferKmxPlus = new Uint8Array(result.artifacts.kmx.data.buffer, KMX.KMXFile.COMP_KEYBOARD_SIZE); - const binaryKmxPlus = kmx.COMP_KEYBOARD_KMXPLUSINFO.fromBuffer(bufferKmxPlus); - assert.equal(binaryKmxPlus.dpKMXPlus, 0); - assert.equal(binaryKmxPlus.dwKMXPlusSize, 0); + assert.deepEqual( + data.slice(0x50, 0x58), sentinelKmxPlusBlob, + 'Expected KMX+ data to have been appended' + ); + }); + }); }); }); From 12b77a5a782de8df57637b72c8c7501d37a5d59f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 25 Nov 2025 11:59:34 +0100 Subject: [PATCH 32/37] feat(developer): convert .kvk into KMX+ and embed into .kmx Implement the conversion from .kvk into KMX+ format, and embed the result into the .kmx file. This change does not yet support &displaymap. Move some virtual key modifier state helpers out of visual-keyboard-compiler.ts and into shared module modifier-key-constants.ts. This change also flags the need to update the package compiler to rewrite the font metadata into the KMX+ tables. Test-bot: skip --- .../src/consts/modifier-key-constants.ts | 104 +++++- .../types/src/consts/virtual-key-constants.ts | 24 ++ common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 23 +- common/web/types/src/main.ts | 8 +- core/include/ldml/keyman_core_ldml.ts | 22 ++ developer/src/common/web/utils/src/index.ts | 2 + .../web/utils/src/kmx-plus-osk-token.ts | 17 + .../src/kmc-kmn/src/compiler/compiler.ts | 25 ++ .../src/compiler/embed-osk/embed-osk.ts | 194 ++++++++++- .../src/compiler/embed-osk/osk-layout.ts | 32 ++ .../src/compiler/kmn-compiler-messages.ts | 37 ++- .../kmw-compiler/visual-keyboard-compiler.ts | 58 +--- developer/src/kmc-kmn/test/embed-osk.tests.ts | 306 +++++++++++++++++- .../test/fixtures/embed-osk/khmer_angkor.kvks | 206 ++++++++++++ developer/src/kmc-ldml/src/compiler/keys.ts | 6 +- developer/src/kmc-ldml/test/keys.tests.ts | 10 +- .../kmc-package/src/compiler/kmp-compiler.ts | 6 + 17 files changed, 995 insertions(+), 85 deletions(-) create mode 100644 developer/src/common/web/utils/src/kmx-plus-osk-token.ts create mode 100644 developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts create mode 100644 developer/src/kmc-kmn/test/fixtures/embed-osk/khmer_angkor.kvks diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts index 6475a3444cd..8344aaf66cd 100644 --- a/common/web/types/src/consts/modifier-key-constants.ts +++ b/common/web/types/src/consts/modifier-key-constants.ts @@ -6,9 +6,14 @@ import { VisualKeyboardShiftState } from "../kvk/visual-keyboard.js"; +/** + * This type is declared as a `const` rather than as an `enum` for + * historical reasons. Use instead `ModifierKeyConstant` where possible. + */ export const ModifierKeyConstants = { - // Define Keyman Developer modifier bit-flags (exposed for use by other modules) - // Compare against /common/include/kmx_file.h. CTRL+F "#define LCTRLFLAG" to find the secton. + // Define Keyman Developer modifier bit-flags (exposed for use by other + // modules) Compare against /common/include/kmx_file.h. CTRL+F "#define + // LCTRLFLAG" to find the section. LCTRLFLAG: 0x0001, // Left Control flag RCTRLFLAG: 0x0002, // Right Control flag LALTFLAG: 0x0004, // Left Alt flag @@ -34,7 +39,31 @@ export const ModifierKeyConstants = { // See keys_mod_other in keyman_core_ldml.ts }; -export const LDML_MODIFIER_TO_KVK_MODIFIER = new Map(); +/** + * Defines the standard modifier key flags used by Keyman. Note that some keys + * are chiral, and toggle key state is ignored if niether the __FLAG nor the + * corresponding NOT__FLAG are set. + */ +export enum ModifierKeyConstant { + LCTRLFLAG = ModifierKeyConstants.LCTRLFLAG, + RCTRLFLAG = ModifierKeyConstants.RCTRLFLAG, + LALTFLAG = ModifierKeyConstants.LALTFLAG, + RALTFLAG = ModifierKeyConstants.RALTFLAG, + K_SHIFTFLAG = ModifierKeyConstants.K_SHIFTFLAG, + K_CTRLFLAG = ModifierKeyConstants.K_CTRLFLAG, + K_ALTFLAG = ModifierKeyConstants.K_ALTFLAG, + K_METAFLAG = ModifierKeyConstants.K_METAFLAG, + CAPITALFLAG = ModifierKeyConstants.CAPITALFLAG, + NOTCAPITALFLAG = ModifierKeyConstants.NOTCAPITALFLAG, + NUMLOCKFLAG = ModifierKeyConstants.NUMLOCKFLAG, + NOTNUMLOCKFLAG = ModifierKeyConstants.NOTNUMLOCKFLAG, + SCROLLFLAG = ModifierKeyConstants.SCROLLFLAG, + NOTSCROLLFLAG = ModifierKeyConstants.NOTSCROLLFLAG, + ISVIRTUALKEY = ModifierKeyConstants.ISVIRTUALKEY, + VIRTUALCHARKEY = ModifierKeyConstants.VIRTUALCHARKEY, +}; + +export const LDML_MODIFIER_TO_KVK_MODIFIER = new Map(); LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboardShiftState.KVKS_LCTRL); LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboardShiftState.KVKS_RCTRL); LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LALTFLAG, VisualKeyboardShiftState.KVKS_LALT); @@ -43,7 +72,7 @@ LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKey LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboardShiftState.KVKS_CTRL); LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboardShiftState.KVKS_ALT); -export const KVK_MODIFIER_TO_LDML_MODIFIER = new Map(); +export const KVK_MODIFIER_TO_LDML_MODIFIER = new Map(); KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_LCTRL, ModifierKeyConstants.LCTRLFLAG); KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_RCTRL, ModifierKeyConstants.RCTRLFLAG); KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_LALT, ModifierKeyConstants.LALTFLAG); @@ -52,13 +81,13 @@ KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_SHIFT, ModifierK KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_CTRL, ModifierKeyConstants.K_CTRLFLAG); KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_ALT, ModifierKeyConstants.K_ALTFLAG); -export function translateLdmlModifiersToVisualKeyboardShift(modifiers: number): VisualKeyboardShiftState { +export function translateLdmlModifiersToVisualKeyboardShift(modifiers: ModifierKeyConstant): VisualKeyboardShiftState { if(modifiers == 0) { return VisualKeyboardShiftState.KVKS_NORMAL; } if(modifiers & - (ModifierKeyConstants.CAPITALFLAG | ModifierKeyConstants.NUMLOCKFLAG | ModifierKeyConstants.SCROLLFLAG) + (ModifierKeyConstant.CAPITALFLAG | ModifierKeyConstant.NUMLOCKFLAG | ModifierKeyConstant.SCROLLFLAG) ) { // Caps/Num/Scroll are not supported in .kvk, in combination or alone return null; @@ -75,7 +104,7 @@ export function translateLdmlModifiersToVisualKeyboardShift(modifiers: number): return shift; } -export function translateVisualKeyboardShiftToLdmlModifiers(shift: VisualKeyboardShiftState): number { +export function translateVisualKeyboardShiftToLdmlModifiers(shift: VisualKeyboardShiftState): ModifierKeyConstant { if(shift == VisualKeyboardShiftState.KVKS_NORMAL) { return 0; } @@ -89,3 +118,64 @@ export function translateVisualKeyboardShiftToLdmlModifiers(shift: VisualKeyboar return mod; } + + + +function VkShiftStateToKmxShiftState(ShiftState: number): number { + + interface TVKToKMX { + VK: VisualKeyboardShiftState; KMX: ModifierKeyConstant; + } + + const Map: TVKToKMX[] = [ + {VK: VisualKeyboardShiftState.KVKS_SHIFT, KMX: ModifierKeyConstant.K_SHIFTFLAG}, + {VK: VisualKeyboardShiftState.KVKS_CTRL, KMX: ModifierKeyConstant.K_CTRLFLAG}, + {VK: VisualKeyboardShiftState.KVKS_ALT, KMX: ModifierKeyConstant.K_ALTFLAG}, + {VK: VisualKeyboardShiftState.KVKS_LCTRL, KMX: ModifierKeyConstant.LCTRLFLAG}, + {VK: VisualKeyboardShiftState.KVKS_RCTRL, KMX: ModifierKeyConstant.RCTRLFLAG}, + {VK: VisualKeyboardShiftState.KVKS_LALT, KMX: ModifierKeyConstant.LALTFLAG}, + {VK: VisualKeyboardShiftState.KVKS_RALT, KMX: ModifierKeyConstant.RALTFLAG} + ]; + + let result = 0; + for(let i = 0; i < Map.length; i++) { + if (ShiftState & Map[i].VK) { + result |= Map[i].KMX; + } + } + + return result; +} + +/** + * Convert a VK modifier combination bitmask to a hyphen-separated string of + * modifier names, e.g. for use in layer names and key identifiers. The name + * order matches the bit flag order from ModifierKeyConstant, not the bit flag + * order from VisualKeyboardShiftState, for historical reasons. + */ +export function visualKeyboardShiftToLayerName(shift: VisualKeyboardShiftState): string { + + // index is ModifierKeyConstant, not VisualKeyboardShiftState + const modifierNames: string[] = [ + 'leftctrl', + 'rightctrl', + 'leftalt', + 'rightalt', + 'shift', + 'ctrl', + 'alt' + ]; + + const mod = VkShiftStateToKmxShiftState(shift); + if(mod == 0) { + return 'default'; + } + + let result = ''; + for(let i = 0; i < modifierNames.length; i++) { + if(mod & (1 << i)) { + result += modifierNames[i] + '-'; + } + } + return result.substring(0, result.length - 1); +} diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 7b083241bdc..300db547a53 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -230,3 +230,27 @@ export function CLDRScanToVkey(scan: number, badScans?: Set): number { } } +const USVirtualKeyCodeNames = new Map(); + +function fillVirtualKeyNames() { + Object.keys(USVirtualKeyCodes).forEach(name => { + USVirtualKeyCodeNames.set((USVirtualKeyCodes)[name], name); + }); + + // These three keys have multiple definitions + USVirtualKeyCodeNames.set(USVirtualKeyCodes.K_oE2, 'K_oE2'); + USVirtualKeyCodeNames.set(USVirtualKeyCodes.K_oC1, 'K_oC1'); + USVirtualKeyCodeNames.set(USVirtualKeyCodes.K_oDF, 'K_oDF'); +} + +/** + * Get the defined name of a virtual key + * @param vk A defined Keyman virtual key + * @returns the name of the virtual key, or undefined if not found + */ +export function usVirtualKeyName(vk: number): string { + if(USVirtualKeyCodeNames.size == 0) { + fillVirtualKeyNames(); + } + return USVirtualKeyCodeNames.get(vk); +} \ No newline at end of file diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 7864dd59de5..2aa6a06a840 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -584,6 +584,15 @@ export enum LayrFormFlags { chiralSeparate = constants.layr_form_flags_chiral_separate, }; +export enum LayrFormHardware { + touch = 'touch', // layr_form_hardware_touch + abnt2 = 'abnt2', // layr_form_hardware_abnt2 + iso = 'iso', // layr_form_hardware_iso + jis = 'jis', // layr_form_hardware_jis + ks = 'ks', // layr_form_hardware_ks + us = 'us', // layr_form_hardware_us +}; + /** * In-memory `` */ @@ -617,8 +626,20 @@ export class Layr extends Section { forms: LayrForm[] = []; }; +export enum KeysKeysFlags { + /** + * 0 if to is a char, 1 if it is a string + */ + extend = constants.keys_key_flags_extend, + + /** + * 1 if the key is a gap + */ + gap = constants.keys_key_flags_gap, +}; + export class KeysKeys { - flags: number; + flags: KeysKeysFlags; flicks: string; // for in-memory only id: StrsItem; longPress: ListItem; diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 5c017273585..924f1095626 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -8,9 +8,13 @@ export { default as KvkFileWriter } from './kvk/kvk-file-writer.js'; export * as KvkFile from './kvk/kvk-file.js'; -export { USVirtualKeyCodes } from './consts/virtual-key-constants.js'; +export { USVirtualKeyCodes, usVirtualKeyName } from './consts/virtual-key-constants.js'; export * as Constants from './consts/virtual-key-constants.js'; -export { ModifierKeyConstants, LDML_MODIFIER_TO_KVK_MODIFIER, KVK_MODIFIER_TO_LDML_MODIFIER, translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers } from './consts/modifier-key-constants.js'; +export { + ModifierKeyConstant, ModifierKeyConstants, LDML_MODIFIER_TO_KVK_MODIFIER, KVK_MODIFIER_TO_LDML_MODIFIER, + translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers, + visualKeyboardShiftToLayerName, +} from './consts/modifier-key-constants.js'; export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js'; diff --git a/core/include/ldml/keyman_core_ldml.ts b/core/include/ldml/keyman_core_ldml.ts index c9c14935dee..83a3f12683b 100644 --- a/core/include/ldml/keyman_core_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -513,10 +513,32 @@ class Constants { * Length of each layer form in the 'layr' section variable part (v19+) */ readonly length_layr_form_v19 = 32; + /** * for the 'hardware' field indicating a touch keyboard, non-hardware */ readonly layr_form_hardware_touch = 'touch'; + /** + * for the 'hardware' field indicating a Brazilian 103 key ABNT2 layout (iso + extra key near right shift) + */ + readonly layr_form_hardware_abnt2 = 'abnt2'; + /** + * for the 'hardware' field indicating a European 102 key layout (extra key near left shift) + */ + readonly layr_form_hardware_iso = 'iso'; + /** + * for the 'hardware' field indicating a Japanese 109 key layout + */ + readonly layr_form_hardware_jis = 'jis'; + /** + * for the 'hardware' field indicating a Korean KS layout + */ + readonly layr_form_hardware_ks = 'ks'; + /** + * for the 'hardware' field indicating a US ANSI 101 key keyboard + */ + readonly layr_form_hardware_us = 'us'; + /** * Length of each layer entry in the 'layr' section variable part */ diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index 32f55e1294b..8d95a9de948 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -78,3 +78,5 @@ export { getFontFamily, getFontFamilySync } from './font-family.js'; export * as ValidIds from './valid-ids.js'; export * as ProjectLoader from './project-loader.js'; + +export { oskFontMagicToken } from './kmx-plus-osk-token.js'; \ No newline at end of file diff --git a/developer/src/common/web/utils/src/kmx-plus-osk-token.ts b/developer/src/common/web/utils/src/kmx-plus-osk-token.ts new file mode 100644 index 00000000000..a900372e904 --- /dev/null +++ b/developer/src/common/web/utils/src/kmx-plus-osk-token.ts @@ -0,0 +1,17 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ + +/** + * In a KMX+ file, when we first build it, we reserve space for the font + * facename to be rewritten, with a magic token that the package compiler will + * search for; see kmp-compiler.ts and embed-osk.ts. This is the token that + * marks the rewritten space; it is 32 characters long, to match the maximum + * font facename length (in Windows). + * + * Background: this comes from a legacy design decision, where responsibility for + * font selection is made in the package, referencing the relevant font .ttf + * files. This forces a rewrite of font metadata in .kvk and .kmx files when they + * are compiled into a .kmp file. + */ +export const oskFontMagicToken = "*OSK-FONT-MAGIC-TOKEN-OSK-FONT*"; diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 26e51ea36b2..635c4470487 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -17,6 +17,7 @@ import loadWasmHost from '../import/kmcmplib/wasm-host.js'; import { loadKvkFile } from './osk.js'; import { KmnCompilerMessages } from './kmn-compiler-messages.js'; import { WriteCompiledKeyboard } from '../kmw-compiler/kmw-compiler.js'; +import { EmbedOskInKmx } from './embed-osk/embed-osk.js'; // // Matches kmcmplibapi.h definitions @@ -356,7 +357,14 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet } } + if(result.extra.targetVersion >= KMX.KMXFile.VERSION_190) { + if(!this.embedOskInKmx(infile, result)) { + return null; + } + } + if(result.extra.kvksFilename) { + // TODO-EMBED-OSK-IN-KMX: skip this once we support embedded OSK in all desktop targets result.artifacts.kvk = this.runKvkCompiler(result.extra.kvksFilename, infile, outfile, result.displayMap); if(!result.artifacts.kvk) { return null; @@ -427,6 +435,23 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet return this.callbacks.path.basename(kmnFilename, KeymanFileTypes.Source.KeymanKeyboard); } + private embedOskInKmx(kmnFilename: string, result: KmnCompilerResult): boolean { + const embedder = new EmbedOskInKmx(this.callbacks, this.options); + const newData = embedder.embed( + result.artifacts.kmx.data, + result.extra.kvksFilename ? this.callbacks.resolveFilename(kmnFilename, result.extra.kvksFilename) : null, + result.extra.touchLayoutFilename ? this.callbacks.resolveFilename(kmnFilename, result.extra.touchLayoutFilename) : null, + result.displayMap + ); + if(!newData) { + // messages will have been raised in .embed + return false; + } + + result.artifacts.kmx.data = newData; + return true; + } + private runKvkCompiler(kvksFilename: string, kmnFilename: string, kmxFilename: string, displayMap?: Osk.PuaMap) { // The compiler detected a .kvks file, which needs to be captured const vk = loadKvkFile(this.callbacks.resolveFilename(kmnFilename, kvksFilename), this.callbacks); diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts index e3881778893..279ab1ea10d 100644 --- a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts @@ -6,9 +6,16 @@ * Convert Keyman .kvks and .keyman-touch-layout files to KMX+ format and embed * in .kmx. */ -import { CompilerCallbacks } from "@keymanapp/developer-utils"; +import { KMX, KMXPlus, ModifierKeyConstant, translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers, usVirtualKeyName, VisualKeyboard, visualKeyboardShiftToLayerName } from "@keymanapp/common-types"; +import { CompilerCallbacks, KMXPlusBuilder, oskFontMagicToken } from "@keymanapp/developer-utils"; +import { KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { KmnCompilerOptions } from "../compiler.js"; -import { KMX } from "@keymanapp/common-types"; +import { PuaMap, loadKvkFile } from "../osk.js"; +import { oskLayouts } from "./osk-layout.js"; +import { KmnCompilerMessages } from "../kmn-compiler-messages.js"; + +type VirtualKey = number; +type LayerBag = Map; export class EmbedOskInKmx { constructor( @@ -17,6 +24,186 @@ export class EmbedOskInKmx { ) { } + /** + * Take .kvks and .keyman-touch-layout files, merge them and build them as + * KMX+ data, and embed into the provided KMX + * @param kmx .kmx file, v19 or later + * @param kmnFilename source filename for .kmn, only used for path resolution + * @param kvksFilename + * @param touchLayoutFilename + * @param displayMap + * @returns + */ + public embed(kmx: Uint8Array, kvksFilename: string, touchLayoutFilename: string, displayMap: PuaMap) { + const vk = loadKvkFile(kvksFilename, this.callbacks); + + const kmxPlus = this.transformVisualKeyboardToKmxPlus(vk); + if(!kmxPlus) { + return null; + } + + // TODO-EMBED-OSK-IN-KMX: touch layout to ldml + // TODO-EMBED-OSK-IN-KMX: display map remapping + + const builder = new KMXPlusBuilder(kmxPlus); + const data = builder.compile(); + + return this.injectKmxPlusIntoKmxFile(kmx, data); + } + + /** + * Transform a .kvk file to the KMX+ format + */ + private transformVisualKeyboardToKmxPlus(vk: VisualKeyboard.VisualKeyboard): KMXPlus.KMXPlusFile { + + // TODO-EMBED-OSK-IN-KMX: if(displayMap) { + // // Remap using the osk-char-use-rewriter + // Osk.remapVisualKeyboard(vk, displayMap); + // } + + // TODO-EMBED-OSK-IN-KMX: merge this default construction with LDML compiler + // start to write the ldml format + const kmx = new KMXPlus.KMXPlusFile(KMXPlusVersion.Version19); + const strs = kmx.kmxplus.strs = new KMXPlus.Strs(); + const layr = kmx.kmxplus.layr = new KMXPlus.Layr(); + kmx.kmxplus.elem = new KMXPlus.Elem(kmx.kmxplus); + const disp = kmx.kmxplus.disp = new KMXPlus.Disp(); + const keys = kmx.kmxplus.keys = new KMXPlus.Keys(kmx.kmxplus.strs); + // list? + kmx.kmxplus.loca = new KMXPlus.Loca(); + kmx.kmxplus.meta = new KMXPlus.Meta(); + kmx.kmxplus.meta.author = strs.allocString(); + kmx.kmxplus.meta.conform = strs.allocString(); + kmx.kmxplus.meta.indicator = strs.allocString(); + kmx.kmxplus.meta.layout = strs.allocString(); + kmx.kmxplus.meta.name = strs.allocString(); + kmx.kmxplus.meta.settings = 0; + kmx.kmxplus.meta.version = strs.allocString(); + + const layerBags = this.buildLayerBags(vk, strs, keys); + const form = this.buildForm(vk, layerBags, strs); + layr.forms.push(form); + + // For now, we only support dotted circle (U+25CC) as our base character + disp.baseCharacter = strs.allocString('\u25cc'); + + return kmx; + } + + /** + * Build the layout of keys, with gaps for missing keys, for the given form -- + * either ANSI (US) or ISO (EU), which are the only two supported layouts in + * .kvk + */ + private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map, strs: KMXPlus.Strs) { + const baseLayoutName = 'en-us'; // This is the only value we support for 19.0 + const formName: KMXPlus.LayrFormHardware = + vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 + ? KMXPlus.LayrFormHardware.iso + : KMXPlus.LayrFormHardware.us; + + const form = new KMXPlus.LayrForm(); + + form.baseLayout = strs.allocString(baseLayoutName); + form.flags = 0; + + if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) { + form.flags |= KMXPlus.LayrFormFlags.showBaseLayout; + } + + if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhAltGr) { + form.flags |= KMXPlus.LayrFormFlags.chiralSeparate; + } + + // We will reserve space for the font facename to be rewritten, with a magic + // token that the package compiler will search for; see kmp-compiler.ts. + form.fontFaceName = strs.allocString(oskFontMagicToken); + + // We only currently support 100% font size + form.fontSizePct = 100; + form.hardware = strs.allocString(formName); + + // For hardware-style keyboards, device width is not relevant + form.minDeviceWidth = 0; + + layerBags.forEach((keys, modifier) => { + const layr = new KMXPlus.LayrEntry(); + + // layr.id is not relevant for hardware keyboards, but we include it to + // make it easier to debug. We can use the existing KVK shift string + // generation, because we can only have KVK modifiers here, even though + // the LDML modifiers spec supports other modifiers + const vkShift = translateLdmlModifiersToVisualKeyboardShift(modifier); + layr.id = strs.allocString(visualKeyboardShiftToLayerName(vkShift)); + layr.mod = modifier; + + // fill the rows + + for(const row of oskLayouts[formName]) { + const layrRow = new KMXPlus.LayrRow(); + layr.rows.push(layrRow); + for(const vk of row) { + const key = keys.get(vk); + layrRow.keys.push(strs.allocString(key?.id?.value ?? 'gap')); + } + } + + form.layers.push(layr); + }); + + return form; + } + + /** + * Collect all the relevant keys from the visual keyboard, add them to the key + * bag, and build a set of key bags, one for each layer in the visual + * keyboard. + */ + private buildLayerBags(vk: VisualKeyboard.VisualKeyboard, strs: KMXPlus.Strs, keys: KMXPlus.Keys) { + const layerBags = new Map(); + + let hasHintedAboutNonUnicode = false; + + for (const key of vk.keys) { + const keyId = visualKeyboardShiftToLayerName(key.shift) + '-' + (usVirtualKeyName(key.vkey) ?? ('Unknown_'+key.vkey.toString())); + + if(!(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode)) { + if(!hasHintedAboutNonUnicode) { + this.callbacks.reportMessage(KmnCompilerMessages.Hint_EmbeddedOskDoesNotSupportNonUnicode({keyId})); + hasHintedAboutNonUnicode = true; + } + continue; + } + + if(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap) { + this.callbacks.reportMessage(KmnCompilerMessages.Warn_EmbeddedOskDoesNotSupportBitmaps({keyId})); + continue; + } + + const keykey: KMXPlus.KeysKeys = { + id: strs.allocString(keyId), + to: strs.allocString(key.text), + flags: 0, // available flags are: gap, extend; neither needed + flicks: "", + longPress: null, + longPressDefault: strs.allocString(), + multiTap: null, + switch: strs.allocString(), + width: 100, + }; + + const mod = translateVisualKeyboardShiftToLdmlModifiers(key.shift); + if (!layerBags.has(mod)) { + const bag = new Map(); + layerBags.set(mod, bag); + } + + layerBags.get(mod).set(key.vkey, keykey); + keys.keys.push(keykey); + } + return layerBags; + } + /** * Takes an existing KMX file, which must have a version >= 19.0, and must * have space pre-allocated for the KMX+ header, and injects the prebuilt KMX+ @@ -76,6 +263,9 @@ export class EmbedOskInKmx { } public readonly unitTestEndpoints = { + transformVisualKeyboardToKmxPlus: this.transformVisualKeyboardToKmxPlus.bind(this), + buildForm: this.buildForm.bind(this), + buildLayerBags: this.buildLayerBags.bind(this), injectKmxPlusIntoKmxFile: this.injectKmxPlusIntoKmxFile.bind(this), }; diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts new file mode 100644 index 00000000000..46f5f5bd9a9 --- /dev/null +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts @@ -0,0 +1,32 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-11-24 + */ +import { USVirtualKeyCodes as V } from "@keymanapp/common-types"; + +/** + * On Screen Keyboard virtual key codes and positions for the Windows "kbdus" + * hardware layout which Keyman for Windows uses as its basic positional layout, + * mapped against the LDML supported base layouts. Only the character-producing + * keys are included; frame keys are excluded. + */ +export const oskLayouts = { + us: [ + [V.K_BKQUOTE, V.K_1, V.K_2, V.K_3, V.K_4, V.K_5, V.K_6, V.K_7, V.K_8, V.K_9, V.K_0, V.K_HYPHEN, V.K_EQUAL], + [V.K_Q, V.K_W, V.K_E, V.K_R, V.K_T, V.K_Y, V.K_U, V.K_I, V.K_O, V.K_P, V.K_LBRKT, V.K_RBRKT, V.K_BKSLASH], + [V.K_A, V.K_S, V.K_D, V.K_F, V.K_G, V.K_H, V.K_J, V.K_K, V.K_L, V.K_COLON, V.K_QUOTE], + [V.K_Z, V.K_X, V.K_C, V.K_V, V.K_B, V.K_N, V.K_M, V.K_COMMA, V.K_PERIOD, V.K_SLASH], + [V.K_SPACE], + ], + + iso: [ + [V.K_BKQUOTE, V.K_1, V.K_2, V.K_3, V.K_4, V.K_5, V.K_6, V.K_7, V.K_8, V.K_9, V.K_0, V.K_HYPHEN, V.K_EQUAL], + [V.K_Q, V.K_W, V.K_E, V.K_R, V.K_T, V.K_Y, V.K_U, V.K_I, V.K_O, V.K_P, V.K_LBRKT, V.K_RBRKT], // differences from us: K_BKSLASH removed here + [V.K_A, V.K_S, V.K_D, V.K_F, V.K_G, V.K_H, V.K_J, V.K_K, V.K_L, V.K_COLON, V.K_QUOTE, V.K_BKSLASH], // differences from us: K_BKSLASH add here + [V.K_OE2, V.K_Z, V.K_X, V.K_C, V.K_V, V.K_B, V.K_N, V.K_M, V.K_COMMA, V.K_PERIOD, V.K_SLASH], // differences from us: adds K_OE2, "102nd key" + [V.K_SPACE], + ], + + // abnt2, jis, ks may be needed in the future +}; \ No newline at end of file diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts index b85a7086e6f..9eb6b9928b7 100644 --- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts +++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts @@ -68,9 +68,10 @@ type KmcmpLibMessageParameters = {p:string[]}; * ``` */ export class KmnCompilerMessages { - // TODO: v18.0 we should consider moving error message generation in kmcmplib to - // kmc-kmn, which would avoid a number of legacy issues. Questions about - // parameterisation. + + //------------------------------------------------------------------------------| + // max length of detail message lines (checked by verifyCompilerMessagesObject) | + //------------------------------------------------------------------------------| static FATAL_UnexpectedException = SevFatal | 0x900; static Fatal_UnexpectedException = (o:{e: any}) => CompilerMessageSpecWithException( @@ -197,10 +198,32 @@ export class KmnCompilerMessages { static ERROR_FileNotFound = SevError | 0x90C; static Error_FileNotFound = (o:{filename: string}) => m( this.ERROR_FileNotFound, - `File ${def(o.filename)} was not found`, - `The file was not found on the disk. Verify that you have the correct path - to the file.` - ); + `File ${def(o.filename)} was not found`, ` + The file was not found on the disk. Verify that you have the correct path + to the file. + `); + + static WARN_EmbeddedOskDoesNotSupportBitmaps = SevWarn | 0x90D; + static Warn_EmbeddedOskDoesNotSupportBitmaps = (o:{keyId: string}) => m( + this.WARN_EmbeddedOskDoesNotSupportBitmaps, + `The On Screen Keyboard key '${def(o.keyId)}' uses a bitmap, which is not supported in v19+ embedded On Screen Keyboards`, ` + The v19+ On Screen Keyboard is embedded into the .kmx file. However, bitmap + images are not supported for key caps. To support arbitrary images, use a + custom font for the On Screen Keyboard. + `); + + static HINT_EmbeddedOskDoesNotSupportNonUnicode = SevHint | 0x90E; + static Hint_EmbeddedOskDoesNotSupportNonUnicode = (o:{keyId: string}) => m( + this.HINT_EmbeddedOskDoesNotSupportNonUnicode, + `The On Screen Keyboard key '${def(o.keyId)}' is not Unicode, and will be ignored`, ` + The v19+ On Screen Keyboard is embedded into the .kmx file. However, + non-Unicode key caps are not supported in the .kmx embedded On Screen + Keyboard. Only the first non-Unicode key cap found will be reported. + `); + + //------------------------------------------------------------------------------| + // Messages below this point come from kmcmplib | + //------------------------------------------------------------------------------| // static STATUS_None = 0x000; // This is not a real error // static STATUS_EndOfFile = 0x001; // This is not a real error diff --git a/developer/src/kmc-kmn/src/kmw-compiler/visual-keyboard-compiler.ts b/developer/src/kmc-kmn/src/kmw-compiler/visual-keyboard-compiler.ts index 117d0f2affe..d5895e4d126 100644 --- a/developer/src/kmc-kmn/src/kmw-compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-kmn/src/kmw-compiler/visual-keyboard-compiler.ts @@ -1,4 +1,4 @@ -import { KMX, KvkFile, VisualKeyboard } from "@keymanapp/common-types"; +import { KvkFile, VisualKeyboard, visualKeyboardShiftToLayerName } from "@keymanapp/common-types"; import { FTabStop, nl } from "./compiler-globals.js"; import { CKeymanWebKeyCodes } from "./keymanweb-key-codes.js"; import { RequotedString } from "./kmw-compiler.js"; @@ -22,60 +22,6 @@ function WideQuote(s: string): string { return result; } -function VkShiftStateToKmxShiftState(ShiftState: number): number { - - interface TVKToKMX { - VK: number; KMX: number; - } - - const Map: TVKToKMX[] = [ - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_SHIFT, KMX: KMX.KMXFile.K_SHIFTFLAG}, - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_CTRL, KMX: KMX.KMXFile.K_CTRLFLAG}, - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_ALT, KMX: KMX.KMXFile.K_ALTFLAG}, - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_LCTRL, KMX: KMX.KMXFile.LCTRLFLAG}, - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_RCTRL, KMX: KMX.KMXFile.RCTRLFLAG}, - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_LALT, KMX: KMX.KMXFile.LALTFLAG}, - {VK: KvkFile.BUILDER_KVK_SHIFT_STATE.KVKS_RALT, KMX: KMX.KMXFile.RALTFLAG} - ]; - - let result = 0; - for(let i = 0; i < Map.length; i++) { - if (ShiftState & Map[i].VK) { - result |= Map[i].KMX; - } - } - - return result; -} - - -function VKShiftToLayerName(shift: number): string { - - const masks: string[] = [ - 'leftctrl', - 'rightctrl', - 'leftalt', - 'rightalt', - 'shift', - 'ctrl', - 'alt' - ]; - - shift = VkShiftStateToKmxShiftState(shift); - if(shift == 0) { - return 'default'; - } - - let result = ''; - for(let i = 0; i < masks.length; i++) { - if(shift & (1 << i)) { - result += masks[i] + '-'; - } - } - return result.substring(0, result.length - 1); -} - - function VisualKeyboardToKLS(FVK: VisualKeyboard.VisualKeyboard): string { interface TLayer { @@ -111,7 +57,7 @@ function VisualKeyboardToKLS(FVK: VisualKeyboard.VisualKeyboard): string { for(let i = 0; i < layers.length; i++) { const layer = layers[i]; - result += `${FTabStop}${FTabStop}"${VKShiftToLayerName(layer.shift)}": [`; + result += `${FTabStop}${FTabStop}"${visualKeyboardShiftToLayerName(layer.shift)}": [`; for(let j = 0; j < layer.keys.length - 1; j++) { result += '"'+WideQuote(layer.keys[j] ?? '')+'",'; } diff --git a/developer/src/kmc-kmn/test/embed-osk.tests.ts b/developer/src/kmc-kmn/test/embed-osk.tests.ts index c0c9de77b25..fdf234cecfd 100644 --- a/developer/src/kmc-kmn/test/embed-osk.tests.ts +++ b/developer/src/kmc-kmn/test/embed-osk.tests.ts @@ -6,13 +6,25 @@ import { fileURLToPath } from 'node:url'; import 'mocha'; import { assert } from 'chai'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; -import { KMX } from '@keymanapp/common-types'; -import { KmnCompiler } from '../src/main.js'; +import { KMX, KMXPlus, ModifierKeyConstant, USVirtualKeyCodes, VisualKeyboard } from '@keymanapp/common-types'; +import { KMXPlusBuilder, oskFontMagicToken } from '@keymanapp/developer-utils'; +import { makePathToFixture } from './helpers/index.js'; +import { KmnCompiler, KmnCompilerMessages } from '../src/main.js'; import { EmbedOskInKmx } from '../src/compiler/embed-osk/embed-osk.js'; +import { loadKvkFile } from '../src/compiler/osk.js'; const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/'); const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/'; + +// VK header is not used in all functions, e.g. buildLayerBags, so this is a +// default header for those tests +const NullVisualKeyboardHeader: VisualKeyboard.VisualKeyboardHeader = { + flags: VisualKeyboard.VisualKeyboardHeaderFlags.kvkhNone, + ansiFont: null, + unicodeFont: null, +}; + describe('Compiler OSK Embedding', function() { const callbacks = new TestCompilerCallbacks(); @@ -248,5 +260,295 @@ describe('Compiler OSK Embedding', function() { ); }); }); + + describe('EmbedOskInKmx.buildLayerBags', function() { + const embedder = new EmbedOskInKmx(callbacks, {}); + + it('should build a bag of layers from an in-memory .kvks structure', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { + vkey: USVirtualKeyCodes.K_A, + text: 'a', + shift: 0, + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + }, + { + vkey: USVirtualKeyCodes.K_B, + text: 'B', + shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT, + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + }, + { + vkey: USVirtualKeyCodes.K_C, + text: 'Ctrl+Shift+C', + shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT, + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + + assert.lengthOf(strs.strings, 7); + assert.equal(strs.strings[0].value, ''); + assert.equal(strs.strings[1].value, 'default-K_A'); + assert.equal(strs.strings[2].value, 'a'); + assert.equal(strs.strings[3].value, 'shift-K_B'); + assert.equal(strs.strings[4].value, 'B'); + assert.equal(strs.strings[5].value, 'shift-ctrl-K_C'); + assert.equal(strs.strings[6].value, 'Ctrl+Shift+C'); + + assert.lengthOf(keys.flicks, 1); + assert.lengthOf(keys.flicks[0].flicks, 0); + assert.equal(keys.flicks[0].id, strs.strings[0]); + + assert.deepEqual(keys.keys, [ + { + flags: 0, + flicks: "", + id: strs.strings[1], + longPress: null, + longPressDefault: strs.strings[0], + multiTap: null, + switch: strs.strings[0], + to: strs.strings[2], + width: 100 + }, + { + flags: 0, + flicks: "", + id: strs.strings[3], + longPress: null, + longPressDefault: strs.strings[0], + multiTap: null, + switch: strs.strings[0], + to: strs.strings[4], + width: 100 + }, + { + flags: 0, + flicks: "", + id: strs.strings[5], + longPress: null, + longPressDefault: strs.strings[0], + multiTap: null, + switch: strs.strings[0], + to: strs.strings[6], + width: 100 + }, + ]); + + assert.isArray(keys.kmap); + assert.isEmpty(keys.kmap); + + // bag will be a map of maps; this test has three layers with an unmodified base layer K_A, a shift+K_B, and Ctrl+Shift+C + assert.isNotNull(bag); + assert.equal(bag.size, 3); + assert.isTrue(bag.has(0)); + assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG)); + assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG)); + + const defaultLayer = bag.get(0); + assert.equal(defaultLayer.size, 1); + + assert.isTrue(defaultLayer.has(USVirtualKeyCodes.K_A)); + const k_a = defaultLayer.get(USVirtualKeyCodes.K_A); + assert.equal(k_a.flags, 0); + assert.equal(k_a.flicks, ""); + assert.equal(k_a.id, strs.strings[1]); + assert.equal(k_a.longPress, null); + assert.equal(k_a.longPressDefault, strs.strings[0]); + assert.equal(k_a.multiTap, null); + assert.equal(k_a.switch, strs.strings[0]); + assert.equal(k_a.to, strs.strings[2]); + assert.equal(k_a.width, 100); + + const shiftLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG); + assert.equal(shiftLayer.size, 1); + + assert.isTrue(shiftLayer.has(USVirtualKeyCodes.K_B)); + const k_b = shiftLayer.get(USVirtualKeyCodes.K_B); + assert.equal(k_b.flags, 0); + assert.equal(k_b.flicks, ""); + assert.equal(k_b.id, strs.strings[3]); + assert.equal(k_b.longPress, null); + assert.equal(k_b.longPressDefault, strs.strings[0]); + assert.equal(k_b.multiTap, null); + assert.equal(k_b.switch, strs.strings[0]); + assert.equal(k_b.to, strs.strings[4]); + assert.equal(k_b.width, 100); + + const shiftCtrlLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG); + assert.equal(shiftCtrlLayer.size, 1); + + assert.isTrue(shiftCtrlLayer.has(USVirtualKeyCodes.K_C)); + const k_c = shiftCtrlLayer.get(USVirtualKeyCodes.K_C); + assert.equal(k_c.flags, 0); + assert.equal(k_c.flicks, ""); + assert.equal(k_c.id, strs.strings[5]); + assert.equal(k_c.longPress, null); + assert.equal(k_c.longPressDefault, strs.strings[0]); + assert.equal(k_c.multiTap, null); + assert.equal(k_c.switch, strs.strings[0]); + assert.equal(k_c.to, strs.strings[6]); + assert.equal(k_c.width, 100); + + }); + + it('should emit WARN_EmbeddedOskDoesNotSupportBitmaps if a key with kvkkBitmap flag is found', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { + vkey: USVirtualKeyCodes.K_A, + shift: 0, + // kvkkUnicode required because otherwise the key is ignored as 'ansi' + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap | VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + bitmap: new Uint8Array() + }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + assert.isNotNull(bag); + assert.equal(bag.size, 0); + assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.WARN_EmbeddedOskDoesNotSupportBitmaps)); + }); + + it('should emit HINT_EmbeddedOskDoesNotSupportNonUnicode if a key without kvkkUnicode flag is found', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { + vkey: USVirtualKeyCodes.K_A, + shift: 0, + // !kvkkUnicode + flags: 0 as VisualKeyboard.VisualKeyboardKeyFlags, + }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + assert.isNotNull(bag); + assert.equal(bag.size, 0); + assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.HINT_EmbeddedOskDoesNotSupportNonUnicode)); + }); + }); + + describe('EmbedOskInKmx.buildForm', function() { + const embedder = new EmbedOskInKmx(callbacks, {}); + + it('should a layout of keys from a layer bag, from an in-memory .kvks structure', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { vkey: USVirtualKeyCodes.K_A, text: 'a', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, + { vkey: USVirtualKeyCodes.K_B, text: 'b', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, + { vkey: USVirtualKeyCodes.K_C, text: 'c', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const layerBags = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + assert.isNotNull(layerBags); + const form = embedder.unitTestEndpoints.buildForm(vk, layerBags, strs); + assert.isNotNull(form); + assert.equal(form.baseLayout.value, 'en-us'); // For v19 + assert.equal(form.flags, 0); + assert.equal(form.fontFaceName.value, oskFontMagicToken); + assert.equal(form.fontSizePct, 100); + assert.equal(form.hardware.value, 'us'); + assert.equal(form.minDeviceWidth, 0); + + assert.lengthOf(form.layers, 1); + assert.equal(form.layers[0].id.value, 'default'); + assert.equal(form.layers[0].mod, 0); // no modifiers + assert.equal(form.layers[0].rows.length, 5); + + assert.equal(form.layers[0].rows[0].keys.length, 13); + assert.equal(form.layers[0].rows[1].keys.length, 13); + assert.equal(form.layers[0].rows[2].keys.length, 11); + assert.equal(form.layers[0].rows[3].keys.length, 10); + assert.equal(form.layers[0].rows[4].keys.length, 1); + + assert.deepEqual(form.layers[0].rows[0].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[1].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[2].keys.map(key => key.value), ['default-K_A','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[3].keys.map(key => key.value), ['gap','gap','default-K_C','gap','default-K_B','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[4].keys.map(key => key.value), ['gap']); + }); + }); + + describe('EmbedOskInKmx.transformVisualKeyboardToKmxPlus', function() { + const embedder = new EmbedOskInKmx(callbacks, {}); + + it('should transform a .kvks file into an KMX+ structure', async function() { + const vk = loadKvkFile(makePathToFixture('embed-osk', 'khmer_angkor.kvks'), callbacks); + assert.isNotNull(vk); + + const kmxPlus = embedder.unitTestEndpoints.transformVisualKeyboardToKmxPlus(vk); + assert.isNotNull(kmxPlus); + + // Verify various aspects of the kmxPlus based on the source .kvks + assert.equal(kmxPlus.kmxplus.keys.flicks.length, 1); + + // number of s in the .kvks = 186, vscode search + assert.equal(kmxPlus.kmxplus.keys.keys.length, 186); + + // first key in the file is RA K_B ឞ + assert.equal(kmxPlus.kmxplus.keys.keys[0].id.value, 'rightalt-K_B'); + assert.equal(kmxPlus.kmxplus.keys.keys[0].to.value, 'ឞ'); + + // last key in the file is Shift K_BKQUOTE » + assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].id.value, 'shift-K_BKQUOTE'); + assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].to.value, '»'); + + // first layer is ralt + // first key on the first row of the RALT layer should be RALT+BKQUOTE + assert.equal(kmxPlus.kmxplus.layr.forms.length, 1); + assert.equal(kmxPlus.kmxplus.layr.forms[0].baseLayout.value, 'en-us'); + assert.equal(kmxPlus.kmxplus.layr.forms[0].flags, KMXPlus.LayrFormFlags.chiralSeparate); + // TODO-EMBED-OSK-IN-KMX: need to test showBaseLayout at some point + assert.equal(kmxPlus.kmxplus.layr.forms[0].fontFaceName.value, oskFontMagicToken); + assert.equal(kmxPlus.kmxplus.layr.forms[0].fontSizePct, 100); + assert.equal(kmxPlus.kmxplus.layr.forms[0].hardware.value, 'us'); + assert.equal(kmxPlus.kmxplus.layr.forms[0].minDeviceWidth, 0); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers.length, 4); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].id.value, 'rightalt'); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].mod, KMX.KMXFile.RALTFLAG); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows.length, 5); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys.length, 13); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys[0].value, 'rightalt-K_BKQUOTE'); + + // Finally, pass the through KMXPlusBuilder, there should be no errors, + // hints, or warnings for this file + + const builder = new KMXPlusBuilder(kmxPlus); + const data = builder.compile(); + + assert.isNotNull(data); + assert.lengthOf(callbacks.messages, 0); + }); + }); + + describe('EmbedOskInKmx.embed', function() { + // const embedder = new EmbedOskInKmx(callbacks, {}); + + it('should embed a .kvks file correctly into a .kmx file', function() { + // + // TODO-EMBED-OSK-IN-KMX: implement + // const result = embedder.embed(kmx, kvksFilename, '', null); + this.skip(); + }); + }); }); }); diff --git a/developer/src/kmc-kmn/test/fixtures/embed-osk/khmer_angkor.kvks b/developer/src/kmc-kmn/test/fixtures/embed-osk/khmer_angkor.kvks new file mode 100644 index 00000000000..f0f9bc775d2 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/embed-osk/khmer_angkor.kvks @@ -0,0 +1,206 @@ + + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ុំ + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + ោះ + ុះ + + ? + + ាំ + + + + + + + + + + + + + + + + + + + + + េះ + + + + + + + + + + » + + +
diff --git a/developer/src/kmc-ldml/src/compiler/keys.ts b/developer/src/kmc-ldml/src/compiler/keys.ts index 8656e7fbf2f..fa52bc7b705 100644 --- a/developer/src/kmc-ldml/src/compiler/keys.ts +++ b/developer/src/kmc-ldml/src/compiler/keys.ts @@ -276,7 +276,7 @@ export class KeysCompiler extends SectionCompiler { // now add the reserved key(s). r.set(reserved_gap, KeysCompiler.asReserved({ - flags: constants.keys_key_flags_gap | constants.keys_key_flags_extend, + flags: KMXPlus.KeysKeysFlags.gap | KMXPlus.KeysKeysFlags.extend, id: sections.strs.allocString(reserved_gap), flicks: '', longPress: no_list, @@ -365,10 +365,10 @@ export class KeysCompiler extends SectionCompiler { const key = keyBag.get(keyId); if (!key) continue; // missing key - let flags = 0; + let flags: KMXPlus.KeysKeysFlags = 0; const { flickId, gap, longPressDefaultKeyId, longPressKeyIds, multiTapKeyIds, layerId, output } = key; if (!!gap) { - flags |= constants.keys_key_flags_gap; + flags |= KMXPlus.KeysKeysFlags.gap; } const id = sections.strs.allocString(key.id, { compileContext: key }); const longPress: ListItem = sections.list.allocListFromSpaces( diff --git a/developer/src/kmc-ldml/test/keys.tests.ts b/developer/src/kmc-ldml/test/keys.tests.ts index 57edb7f257c..a0e76085550 100644 --- a/developer/src/kmc-ldml/test/keys.tests.ts +++ b/developer/src/kmc-ldml/test/keys.tests.ts @@ -49,7 +49,7 @@ describe('keys', function () { const [q] = keys.keys.filter(({ id }) => id.value === 'q'); assert.ok(q); - assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.isFalse(!!(q.flags & KMXPlus.KeysKeysFlags.gap)); assert.equal(q.width, 32, 'q\'s width'); // ceil(3.14159 * 10.0) assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem assert.equal(q.longPress.toString(), 'a-acute e-acute i-acute'); @@ -92,7 +92,7 @@ describe('keys', function () { const [q] = keys.keys.filter(({ id }) => id.value === 'q'); assert.ok(q); - assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.isFalse(!!(q.flags & KMXPlus.KeysKeysFlags.gap)); assert.equal(q.width, 32, 'q\'s width'); // ceil(3.14159 * 10.0) assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem assert.equal(q.longPress.toString(), 'a-acute e-acute i-acute'); @@ -134,7 +134,7 @@ describe('keys', function () { const [q] = keys.keys.filter(({ id }) => id.value === 'q'); assert.ok(q); - assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.isFalse(!!(q.flags & KMXPlus.KeysKeysFlags.gap)); assert.equal(q.width, 32); // ceil(3.1 * 10) assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem assert.equal(q.longPress.toString(), 'a-acute e-acute i-acute'); @@ -163,11 +163,11 @@ describe('keys', function () { const [Qgap] = keys.keys.filter(({ id }) => id.value === 'Q'); assert.ok(Qgap); - assert.isTrue(!!(Qgap.flags & constants.keys_key_flags_gap), 'Q’s gap='); + assert.isTrue(!!(Qgap.flags & KMXPlus.KeysKeysFlags.gap), 'Q’s gap='); const [Wshift] = keys.keys.filter(({ id }) => id.value === 'W'); assert.isNotNull(Wshift); - assert.isFalse(!!(Wshift.flags & constants.keys_key_flags_gap)); + assert.isFalse(!!(Wshift.flags & KMXPlus.KeysKeysFlags.gap)); assert.equal(Wshift.switch.value, 'shift'); }, }, diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 4999336ceef..9ac3656cf3f 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -609,6 +609,12 @@ export class KmpCompiler implements KeymanCompiler { } } + // TODO-EMBED-OSK-IN-KMX: rewrite .kmx+ font data in the same way: + // meta: we need to rewrite the .KMX: + // 1. load the kmx and kmx+ headers, verify the file has KMX+ data and is v19+, otherwise skip + // 2. seek to the sect section, verify it is v19+, otherwise skip + // 3. seek to strs section, iterate over all strings, looking for magic token "*OSK-FONT-MAGIC-TOKEN-OSK-FONT*" (32 chars) + // 4. if found, rewrite the string to the font facename, and update the string length header; zero out remaining bytes private setKvkFontData(kpsFilename: string, data: KmpJsonFile.KmpJsonFile, filename: string, kvk: Uint8Array) { // find the appropriate font to set const kvkId = this.callbacks.path.basename(filename.toLowerCase(), '.kvk'); From 2751b531a3c16fae96a9d0e39a9077555e2e4d62 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 26 Nov 2025 06:51:57 +0100 Subject: [PATCH 33/37] Apply suggestions from code review Co-authored-by: Eberhard Beilharz --- developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts index e3881778893..5a7e8bf618e 100644 --- a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts @@ -22,9 +22,9 @@ export class EmbedOskInKmx { * have space pre-allocated for the KMX+ header, and injects the prebuilt KMX+ * kmxPlusBinary data blob, and updates the header flag to indicate that the * file now includes OSK data. - * @param inputFile - * @param kmxPlusBinary - * @returns + * @param inputFile existing KMX file + * @param kmxPlusBinary KMX+ data blob + * @returns new KMX file with embedded KMX+ data */ private injectKmxPlusIntoKmxFile(inputFile: Uint8Array, kmxPlusBinary: Uint8Array): Uint8Array { const kmx = new KMX.KMXFile(); From f50934bf692822ce6e63670758d393d42389505c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 27 Nov 2025 10:28:15 +0100 Subject: [PATCH 34/37] chore(core): update keyman_core_ldml.h --- core/include/ldml/keyman_core_ldml.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/include/ldml/keyman_core_ldml.h b/core/include/ldml/keyman_core_ldml.h index 9380dc84f62..ef08f8651b6 100644 --- a/core/include/ldml/keyman_core_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -129,7 +129,12 @@ #define LDML_KMXPLUS_VERSION_19 0x1300 #define LDML_LAYR_FORM_FLAGS_CHIRAL_SEPARATE 0x2 #define LDML_LAYR_FORM_FLAGS_SHOW_BASE_LAYOUT 0x1 +#define LDML_LAYR_FORM_HARDWARE_ABNT2 "abnt2" +#define LDML_LAYR_FORM_HARDWARE_ISO "iso" +#define LDML_LAYR_FORM_HARDWARE_JIS "jis" +#define LDML_LAYR_FORM_HARDWARE_KS "ks" #define LDML_LAYR_FORM_HARDWARE_TOUCH "touch" +#define LDML_LAYR_FORM_HARDWARE_US "us" #define LDML_LAYR_MAX_MINDEVICEWIDTH 0x3E7 #define LDML_LAYR_MIN_MINDEVICEWIDTH 0x1 #define LDML_LENGTH_BKSP 0xC From ce5a5bb2f1abb4e40f9ef142ce3a9683c131a676 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 27 Nov 2025 11:42:08 +0100 Subject: [PATCH 35/37] refactor(developer): move KVK embed into embed-osk-kvk.ts --- .../src/compiler/embed-osk/embed-osk-kvk.ts | 158 +++++++++ .../src/compiler/embed-osk/embed-osk.ts | 192 ++--------- .../src/kmc-kmn/test/embed-osk-kvk.tests.ts | 315 ++++++++++++++++++ developer/src/kmc-kmn/test/embed-osk.tests.ts | 295 +--------------- 4 files changed, 507 insertions(+), 453 deletions(-) create mode 100644 developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts create mode 100644 developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts new file mode 100644 index 00000000000..6b1d51d3bdc --- /dev/null +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts @@ -0,0 +1,158 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-11-27 + * + * Convert Keyman .kvks files to KMX+ format. + */ +import { KMXPlus, VisualKeyboard, translateLdmlModifiersToVisualKeyboardShift, visualKeyboardShiftToLayerName, ModifierKeyConstant, usVirtualKeyName, translateVisualKeyboardShiftToLdmlModifiers } from "@keymanapp/common-types"; +import { CompilerCallbacks, oskFontMagicToken } from "@keymanapp/developer-utils"; +import { KmnCompilerMessages } from "../kmn-compiler-messages.js"; +import { oskLayouts } from "./osk-layout.js"; + +type VirtualKey = number; +type LayerBag = Map; + +export class EmbedOskKvkInKmx { + + constructor(private callbacks: CompilerCallbacks) { + } + + /** + * Transform a .kvk file to the KMX+ format + */ + public transformVisualKeyboardToKmxPlus(kmx: KMXPlus.KMXPlusFile, vk: VisualKeyboard.VisualKeyboard): void { + + // TODO-EMBED-OSK-IN-KMX: if(displayMap) { + // // Remap using the osk-char-use-rewriter + // Osk.remapVisualKeyboard(vk, displayMap); + // } + + const layerBags = this.buildLayerBags(vk, kmx.kmxplus.strs, kmx.kmxplus.keys); + const form = this.buildForm(vk, layerBags, kmx.kmxplus.strs); + kmx.kmxplus.layr.forms.push(form); + + // For now, we only support dotted circle (U+25CC) as our base character + kmx.kmxplus.disp.baseCharacter = kmx.kmxplus.strs.allocString('\u25cc'); + } + + /** + * Build the layout of keys, with gaps for missing keys, for the given form -- + * either ANSI (US) or ISO (EU), which are the only two supported layouts in + * .kvk + */ + private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map, strs: KMXPlus.Strs) { + const baseLayoutName = 'en-us'; // This is the only value we support for 19.0 + const formName: KMXPlus.LayrFormHardware = + vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 + ? KMXPlus.LayrFormHardware.iso + : KMXPlus.LayrFormHardware.us; + + const form = new KMXPlus.LayrForm(); + + form.baseLayout = strs.allocString(baseLayoutName); + form.flags = 0; + + if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) { + form.flags |= KMXPlus.LayrFormFlags.showBaseLayout; + } + + if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhAltGr) { + form.flags |= KMXPlus.LayrFormFlags.chiralSeparate; + } + + // We will reserve space for the font facename to be rewritten, with a magic + // token that the package compiler will search for; see kmp-compiler.ts. + form.fontFaceName = strs.allocString(oskFontMagicToken); + + // We only currently support 100% font size + form.fontSizePct = 100; + form.hardware = strs.allocString(formName); + + // For hardware-style keyboards, device width is not relevant + form.minDeviceWidth = 0; + + layerBags.forEach((keys, modifier) => { + const layr = new KMXPlus.LayrEntry(); + + // layr.id is not relevant for hardware keyboards, but we include it to + // make it easier to debug. We can use the existing KVK shift string + // generation, because we can only have KVK modifiers here, even though + // the LDML modifiers spec supports other modifiers + const vkShift = translateLdmlModifiersToVisualKeyboardShift(modifier); + layr.id = strs.allocString(visualKeyboardShiftToLayerName(vkShift)); + layr.mod = modifier; + + // fill the rows + + for(const row of oskLayouts[formName]) { + const layrRow = new KMXPlus.LayrRow(); + layr.rows.push(layrRow); + for(const vk of row) { + const key = keys.get(vk); + layrRow.keys.push(strs.allocString(key?.id?.value ?? 'gap')); + } + } + + form.layers.push(layr); + }); + + return form; + } + + /** + * Collect all the relevant keys from the visual keyboard, add them to the key + * bag, and build a set of key bags, one for each layer in the visual + * keyboard. + */ + private buildLayerBags(vk: VisualKeyboard.VisualKeyboard, strs: KMXPlus.Strs, keys: KMXPlus.Keys) { + const layerBags = new Map(); + + let hasHintedAboutNonUnicode = false; + + for (const key of vk.keys) { + const keyId = visualKeyboardShiftToLayerName(key.shift) + '-' + (usVirtualKeyName(key.vkey) ?? ('Unknown_'+key.vkey.toString())); + + if(!(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode)) { + if(!hasHintedAboutNonUnicode) { + this.callbacks.reportMessage(KmnCompilerMessages.Hint_EmbeddedOskDoesNotSupportNonUnicode({keyId})); + hasHintedAboutNonUnicode = true; + } + continue; + } + + if(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap) { + this.callbacks.reportMessage(KmnCompilerMessages.Warn_EmbeddedOskDoesNotSupportBitmaps({keyId})); + continue; + } + + const keykey: KMXPlus.KeysKeys = { + id: strs.allocString(keyId), + to: strs.allocString(key.text), + flags: 0, // available flags are: gap, extend; neither needed + flicks: "", + longPress: null, + longPressDefault: strs.allocString(), + multiTap: null, + switch: strs.allocString(), + width: 100, + }; + + const mod = translateVisualKeyboardShiftToLdmlModifiers(key.shift); + if (!layerBags.has(mod)) { + const bag = new Map(); + layerBags.set(mod, bag); + } + + layerBags.get(mod).set(key.vkey, keykey); + keys.keys.push(keykey); + } + return layerBags; + } + + public readonly unitTestEndpoints = { + transformVisualKeyboardToKmxPlus: this.transformVisualKeyboardToKmxPlus.bind(this), + buildForm: this.buildForm.bind(this), + buildLayerBags: this.buildLayerBags.bind(this), + }; +} \ No newline at end of file diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts index 279ab1ea10d..03933547c49 100644 --- a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts @@ -6,16 +6,14 @@ * Convert Keyman .kvks and .keyman-touch-layout files to KMX+ format and embed * in .kmx. */ -import { KMX, KMXPlus, ModifierKeyConstant, translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers, usVirtualKeyName, VisualKeyboard, visualKeyboardShiftToLayerName } from "@keymanapp/common-types"; -import { CompilerCallbacks, KMXPlusBuilder, oskFontMagicToken } from "@keymanapp/developer-utils"; +import { KMX, KMXPlus } from "@keymanapp/common-types"; +import { CompilerCallbacks, KMXPlusBuilder } from "@keymanapp/developer-utils"; import { KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants"; import { KmnCompilerOptions } from "../compiler.js"; import { PuaMap, loadKvkFile } from "../osk.js"; -import { oskLayouts } from "./osk-layout.js"; -import { KmnCompilerMessages } from "../kmn-compiler-messages.js"; +import { EmbedOskKvkInKmx } from "./embed-osk-kvk.js"; -type VirtualKey = number; -type LayerBag = Map; +// import { EmbedOskTouchLayoutInKmx } from "./embed-osk-touch-layout.js"; export class EmbedOskInKmx { constructor( @@ -24,51 +22,15 @@ export class EmbedOskInKmx { ) { } - /** - * Take .kvks and .keyman-touch-layout files, merge them and build them as - * KMX+ data, and embed into the provided KMX - * @param kmx .kmx file, v19 or later - * @param kmnFilename source filename for .kmn, only used for path resolution - * @param kvksFilename - * @param touchLayoutFilename - * @param displayMap - * @returns - */ - public embed(kmx: Uint8Array, kvksFilename: string, touchLayoutFilename: string, displayMap: PuaMap) { - const vk = loadKvkFile(kvksFilename, this.callbacks); - - const kmxPlus = this.transformVisualKeyboardToKmxPlus(vk); - if(!kmxPlus) { - return null; - } - - // TODO-EMBED-OSK-IN-KMX: touch layout to ldml - // TODO-EMBED-OSK-IN-KMX: display map remapping - - const builder = new KMXPlusBuilder(kmxPlus); - const data = builder.compile(); - - return this.injectKmxPlusIntoKmxFile(kmx, data); - } - - /** - * Transform a .kvk file to the KMX+ format - */ - private transformVisualKeyboardToKmxPlus(vk: VisualKeyboard.VisualKeyboard): KMXPlus.KMXPlusFile { - - // TODO-EMBED-OSK-IN-KMX: if(displayMap) { - // // Remap using the osk-char-use-rewriter - // Osk.remapVisualKeyboard(vk, displayMap); - // } - + private createEmptyKmxPlusFile() { // TODO-EMBED-OSK-IN-KMX: merge this default construction with LDML compiler // start to write the ldml format const kmx = new KMXPlus.KMXPlusFile(KMXPlusVersion.Version19); const strs = kmx.kmxplus.strs = new KMXPlus.Strs(); - const layr = kmx.kmxplus.layr = new KMXPlus.Layr(); + kmx.kmxplus.layr = new KMXPlus.Layr(); kmx.kmxplus.elem = new KMXPlus.Elem(kmx.kmxplus); - const disp = kmx.kmxplus.disp = new KMXPlus.Disp(); - const keys = kmx.kmxplus.keys = new KMXPlus.Keys(kmx.kmxplus.strs); + kmx.kmxplus.disp = new KMXPlus.Disp(); + kmx.kmxplus.keys = new KMXPlus.Keys(strs); // list? kmx.kmxplus.loca = new KMXPlus.Loca(); kmx.kmxplus.meta = new KMXPlus.Meta(); @@ -80,128 +42,40 @@ export class EmbedOskInKmx { kmx.kmxplus.meta.settings = 0; kmx.kmxplus.meta.version = strs.allocString(); - const layerBags = this.buildLayerBags(vk, strs, keys); - const form = this.buildForm(vk, layerBags, strs); - layr.forms.push(form); - - // For now, we only support dotted circle (U+25CC) as our base character - disp.baseCharacter = strs.allocString('\u25cc'); - return kmx; } /** - * Build the layout of keys, with gaps for missing keys, for the given form -- - * either ANSI (US) or ISO (EU), which are the only two supported layouts in - * .kvk - */ - private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map, strs: KMXPlus.Strs) { - const baseLayoutName = 'en-us'; // This is the only value we support for 19.0 - const formName: KMXPlus.LayrFormHardware = - vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 - ? KMXPlus.LayrFormHardware.iso - : KMXPlus.LayrFormHardware.us; - - const form = new KMXPlus.LayrForm(); - - form.baseLayout = strs.allocString(baseLayoutName); - form.flags = 0; - - if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) { - form.flags |= KMXPlus.LayrFormFlags.showBaseLayout; - } - - if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhAltGr) { - form.flags |= KMXPlus.LayrFormFlags.chiralSeparate; - } - - // We will reserve space for the font facename to be rewritten, with a magic - // token that the package compiler will search for; see kmp-compiler.ts. - form.fontFaceName = strs.allocString(oskFontMagicToken); - - // We only currently support 100% font size - form.fontSizePct = 100; - form.hardware = strs.allocString(formName); - - // For hardware-style keyboards, device width is not relevant - form.minDeviceWidth = 0; - - layerBags.forEach((keys, modifier) => { - const layr = new KMXPlus.LayrEntry(); - - // layr.id is not relevant for hardware keyboards, but we include it to - // make it easier to debug. We can use the existing KVK shift string - // generation, because we can only have KVK modifiers here, even though - // the LDML modifiers spec supports other modifiers - const vkShift = translateLdmlModifiersToVisualKeyboardShift(modifier); - layr.id = strs.allocString(visualKeyboardShiftToLayerName(vkShift)); - layr.mod = modifier; - - // fill the rows - - for(const row of oskLayouts[formName]) { - const layrRow = new KMXPlus.LayrRow(); - layr.rows.push(layrRow); - for(const vk of row) { - const key = keys.get(vk); - layrRow.keys.push(strs.allocString(key?.id?.value ?? 'gap')); - } - } - - form.layers.push(layr); - }); - - return form; - } - - /** - * Collect all the relevant keys from the visual keyboard, add them to the key - * bag, and build a set of key bags, one for each layer in the visual - * keyboard. + * Take .kvks and .keyman-touch-layout files, merge them and build them as + * KMX+ data, and embed into the provided KMX + * @param kmx .kmx file, v19 or later + * @param kmnFilename source filename for .kmn, only used for path resolution + * @param kvksFilename + * @param touchLayoutFilename + * @param displayMap + * @returns */ - private buildLayerBags(vk: VisualKeyboard.VisualKeyboard, strs: KMXPlus.Strs, keys: KMXPlus.Keys) { - const layerBags = new Map(); - - let hasHintedAboutNonUnicode = false; - - for (const key of vk.keys) { - const keyId = visualKeyboardShiftToLayerName(key.shift) + '-' + (usVirtualKeyName(key.vkey) ?? ('Unknown_'+key.vkey.toString())); + public embed(kmx: Uint8Array, kvksFilename: string, touchLayoutFilename: string, displayMap: PuaMap) { + const kmxPlus = this.createEmptyKmxPlusFile(); - if(!(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode)) { - if(!hasHintedAboutNonUnicode) { - this.callbacks.reportMessage(KmnCompilerMessages.Hint_EmbeddedOskDoesNotSupportNonUnicode({keyId})); - hasHintedAboutNonUnicode = true; - } - continue; + if(kvksFilename) { + const embedKvk = new EmbedOskKvkInKmx(this.callbacks); + const vk = loadKvkFile(kvksFilename, this.callbacks); + if(!vk) { + // error will have been reported by loadKvkFile + return null; } + embedKvk.transformVisualKeyboardToKmxPlus(kmxPlus, vk); + } - if(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap) { - this.callbacks.reportMessage(KmnCompilerMessages.Warn_EmbeddedOskDoesNotSupportBitmaps({keyId})); - continue; - } - const keykey: KMXPlus.KeysKeys = { - id: strs.allocString(keyId), - to: strs.allocString(key.text), - flags: 0, // available flags are: gap, extend; neither needed - flicks: "", - longPress: null, - longPressDefault: strs.allocString(), - multiTap: null, - switch: strs.allocString(), - width: 100, - }; + // TODO-EMBED-OSK-IN-KMX: touch layout to ldml + // TODO-EMBED-OSK-IN-KMX: display map remapping - const mod = translateVisualKeyboardShiftToLdmlModifiers(key.shift); - if (!layerBags.has(mod)) { - const bag = new Map(); - layerBags.set(mod, bag); - } + const builder = new KMXPlusBuilder(kmxPlus); + const data = builder.compile(); - layerBags.get(mod).set(key.vkey, keykey); - keys.keys.push(keykey); - } - return layerBags; + return this.injectKmxPlusIntoKmxFile(kmx, data); } /** @@ -263,10 +137,8 @@ export class EmbedOskInKmx { } public readonly unitTestEndpoints = { - transformVisualKeyboardToKmxPlus: this.transformVisualKeyboardToKmxPlus.bind(this), - buildForm: this.buildForm.bind(this), - buildLayerBags: this.buildLayerBags.bind(this), injectKmxPlusIntoKmxFile: this.injectKmxPlusIntoKmxFile.bind(this), + createEmptyKmxPlusFile: this.createEmptyKmxPlusFile.bind(this), }; }; diff --git a/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts b/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts new file mode 100644 index 00000000000..b748c9f4dd4 --- /dev/null +++ b/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts @@ -0,0 +1,315 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import 'mocha'; +import { assert } from 'chai'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { KMX, KMXPlus, ModifierKeyConstant, USVirtualKeyCodes, VisualKeyboard } from '@keymanapp/common-types'; +import { KMXPlusBuilder, oskFontMagicToken } from '@keymanapp/developer-utils'; +import { makePathToFixture } from './helpers/index.js'; +import { KmnCompilerMessages } from '../src/main.js'; +import { EmbedOskInKmx } from '../src/compiler/embed-osk/embed-osk.js'; +import { loadKvkFile } from '../src/compiler/osk.js'; +import { EmbedOskKvkInKmx } from '../src/compiler/embed-osk/embed-osk-kvk.js'; + +// VK header is not used in all functions, e.g. buildLayerBags, so this is a +// default header for those tests +const NullVisualKeyboardHeader: VisualKeyboard.VisualKeyboardHeader = { + flags: VisualKeyboard.VisualKeyboardHeaderFlags.kvkhNone, + ansiFont: null, + unicodeFont: null, +}; + +describe('Compiler OSK Embedding', function() { + + const callbacks = new TestCompilerCallbacks(); + + this.beforeEach(function() { + callbacks.clear(); + }); + + this.afterEach(function() { + if(this.currentTest.isFailed()) { + callbacks.printMessages(); + } + }); + + describe('EmbedOskKvkInKmx', function() { + const embedder = new EmbedOskKvkInKmx(callbacks); + + describe('EmbedOskKvkInKmx.buildLayerBags', function() { + it('should build a bag of layers from an in-memory .kvks structure', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { + vkey: USVirtualKeyCodes.K_A, + text: 'a', + shift: 0, + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + }, + { + vkey: USVirtualKeyCodes.K_B, + text: 'B', + shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT, + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + }, + { + vkey: USVirtualKeyCodes.K_C, + text: 'Ctrl+Shift+C', + shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT, + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + + assert.lengthOf(strs.strings, 7); + assert.equal(strs.strings[0].value, ''); + assert.equal(strs.strings[1].value, 'default-K_A'); + assert.equal(strs.strings[2].value, 'a'); + assert.equal(strs.strings[3].value, 'shift-K_B'); + assert.equal(strs.strings[4].value, 'B'); + assert.equal(strs.strings[5].value, 'shift-ctrl-K_C'); + assert.equal(strs.strings[6].value, 'Ctrl+Shift+C'); + + assert.lengthOf(keys.flicks, 1); + assert.lengthOf(keys.flicks[0].flicks, 0); + assert.equal(keys.flicks[0].id, strs.strings[0]); + + assert.deepEqual(keys.keys, [ + { + flags: 0, + flicks: "", + id: strs.strings[1], + longPress: null, + longPressDefault: strs.strings[0], + multiTap: null, + switch: strs.strings[0], + to: strs.strings[2], + width: 100 + }, + { + flags: 0, + flicks: "", + id: strs.strings[3], + longPress: null, + longPressDefault: strs.strings[0], + multiTap: null, + switch: strs.strings[0], + to: strs.strings[4], + width: 100 + }, + { + flags: 0, + flicks: "", + id: strs.strings[5], + longPress: null, + longPressDefault: strs.strings[0], + multiTap: null, + switch: strs.strings[0], + to: strs.strings[6], + width: 100 + }, + ]); + + assert.isArray(keys.kmap); + assert.isEmpty(keys.kmap); + + // bag will be a map of maps; this test has three layers with an unmodified base layer K_A, a shift+K_B, and Ctrl+Shift+C + assert.isNotNull(bag); + assert.equal(bag.size, 3); + assert.isTrue(bag.has(0)); + assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG)); + assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG)); + + const defaultLayer = bag.get(0); + assert.equal(defaultLayer.size, 1); + + assert.isTrue(defaultLayer.has(USVirtualKeyCodes.K_A)); + const k_a = defaultLayer.get(USVirtualKeyCodes.K_A); + assert.equal(k_a.flags, 0); + assert.equal(k_a.flicks, ""); + assert.equal(k_a.id, strs.strings[1]); + assert.equal(k_a.longPress, null); + assert.equal(k_a.longPressDefault, strs.strings[0]); + assert.equal(k_a.multiTap, null); + assert.equal(k_a.switch, strs.strings[0]); + assert.equal(k_a.to, strs.strings[2]); + assert.equal(k_a.width, 100); + + const shiftLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG); + assert.equal(shiftLayer.size, 1); + + assert.isTrue(shiftLayer.has(USVirtualKeyCodes.K_B)); + const k_b = shiftLayer.get(USVirtualKeyCodes.K_B); + assert.equal(k_b.flags, 0); + assert.equal(k_b.flicks, ""); + assert.equal(k_b.id, strs.strings[3]); + assert.equal(k_b.longPress, null); + assert.equal(k_b.longPressDefault, strs.strings[0]); + assert.equal(k_b.multiTap, null); + assert.equal(k_b.switch, strs.strings[0]); + assert.equal(k_b.to, strs.strings[4]); + assert.equal(k_b.width, 100); + + const shiftCtrlLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG); + assert.equal(shiftCtrlLayer.size, 1); + + assert.isTrue(shiftCtrlLayer.has(USVirtualKeyCodes.K_C)); + const k_c = shiftCtrlLayer.get(USVirtualKeyCodes.K_C); + assert.equal(k_c.flags, 0); + assert.equal(k_c.flicks, ""); + assert.equal(k_c.id, strs.strings[5]); + assert.equal(k_c.longPress, null); + assert.equal(k_c.longPressDefault, strs.strings[0]); + assert.equal(k_c.multiTap, null); + assert.equal(k_c.switch, strs.strings[0]); + assert.equal(k_c.to, strs.strings[6]); + assert.equal(k_c.width, 100); + + }); + + it('should emit WARN_EmbeddedOskDoesNotSupportBitmaps if a key with kvkkBitmap flag is found', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { + vkey: USVirtualKeyCodes.K_A, + shift: 0, + // kvkkUnicode required because otherwise the key is ignored as 'ansi' + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap | VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + bitmap: new Uint8Array() + }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + assert.isNotNull(bag); + assert.equal(bag.size, 0); + assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.WARN_EmbeddedOskDoesNotSupportBitmaps)); + }); + + it('should emit HINT_EmbeddedOskDoesNotSupportNonUnicode if a key without kvkkUnicode flag is found', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { + vkey: USVirtualKeyCodes.K_A, + shift: 0, + // !kvkkUnicode + flags: 0 as VisualKeyboard.VisualKeyboardKeyFlags, + }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + assert.isNotNull(bag); + assert.equal(bag.size, 0); + assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.HINT_EmbeddedOskDoesNotSupportNonUnicode)); + }); + }); + + describe('EmbedOskKvkInKmx.buildForm', function() { + it('should a layout of keys from a layer bag, from an in-memory .kvks structure', async function() { + const vk: VisualKeyboard.VisualKeyboard = { + header: NullVisualKeyboardHeader, + keys: [ + { vkey: USVirtualKeyCodes.K_A, text: 'a', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, + { vkey: USVirtualKeyCodes.K_B, text: 'b', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, + { vkey: USVirtualKeyCodes.K_C, text: 'c', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, + ] + }; + + const strs = new KMXPlus.Strs(); + const keys = new KMXPlus.Keys(strs); + const layerBags = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); + assert.isNotNull(layerBags); + const form = embedder.unitTestEndpoints.buildForm(vk, layerBags, strs); + assert.isNotNull(form); + assert.equal(form.baseLayout.value, 'en-us'); // For v19 + assert.equal(form.flags, 0); + assert.equal(form.fontFaceName.value, oskFontMagicToken); + assert.equal(form.fontSizePct, 100); + assert.equal(form.hardware.value, 'us'); + assert.equal(form.minDeviceWidth, 0); + + assert.lengthOf(form.layers, 1); + assert.equal(form.layers[0].id.value, 'default'); + assert.equal(form.layers[0].mod, 0); // no modifiers + assert.equal(form.layers[0].rows.length, 5); + + assert.equal(form.layers[0].rows[0].keys.length, 13); + assert.equal(form.layers[0].rows[1].keys.length, 13); + assert.equal(form.layers[0].rows[2].keys.length, 11); + assert.equal(form.layers[0].rows[3].keys.length, 10); + assert.equal(form.layers[0].rows[4].keys.length, 1); + + assert.deepEqual(form.layers[0].rows[0].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[1].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[2].keys.map(key => key.value), ['default-K_A','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[3].keys.map(key => key.value), ['gap','gap','default-K_C','gap','default-K_B','gap','gap','gap','gap','gap']); + assert.deepEqual(form.layers[0].rows[4].keys.map(key => key.value), ['gap']); + }); + }); + + describe('EmbedOskKvkInKmx.transformVisualKeyboardToKmxPlus', function() { + it('should transform a .kvks file into an KMX+ structure', async function() { + const vk = loadKvkFile(makePathToFixture('embed-osk', 'khmer_angkor.kvks'), callbacks); + assert.isNotNull(vk); + + const kmxPlus = new EmbedOskInKmx(callbacks,{}).unitTestEndpoints.createEmptyKmxPlusFile(); + assert.isNotNull(kmxPlus); + + embedder.unitTestEndpoints.transformVisualKeyboardToKmxPlus(kmxPlus, vk); + + // Verify various aspects of the kmxPlus based on the source .kvks + assert.equal(kmxPlus.kmxplus.keys.flicks.length, 1); + + // number of s in the .kvks = 186, vscode search + assert.equal(kmxPlus.kmxplus.keys.keys.length, 186); + + // first key in the file is RA K_B ឞ + assert.equal(kmxPlus.kmxplus.keys.keys[0].id.value, 'rightalt-K_B'); + assert.equal(kmxPlus.kmxplus.keys.keys[0].to.value, 'ឞ'); + + // last key in the file is Shift K_BKQUOTE » + assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].id.value, 'shift-K_BKQUOTE'); + assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].to.value, '»'); + + // first layer is ralt + // first key on the first row of the RALT layer should be RALT+BKQUOTE + assert.equal(kmxPlus.kmxplus.layr.forms.length, 1); + assert.equal(kmxPlus.kmxplus.layr.forms[0].baseLayout.value, 'en-us'); + assert.equal(kmxPlus.kmxplus.layr.forms[0].flags, KMXPlus.LayrFormFlags.chiralSeparate); + // TODO-EMBED-OSK-IN-KMX: need to test showBaseLayout at some point + assert.equal(kmxPlus.kmxplus.layr.forms[0].fontFaceName.value, oskFontMagicToken); + assert.equal(kmxPlus.kmxplus.layr.forms[0].fontSizePct, 100); + assert.equal(kmxPlus.kmxplus.layr.forms[0].hardware.value, 'us'); + assert.equal(kmxPlus.kmxplus.layr.forms[0].minDeviceWidth, 0); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers.length, 4); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].id.value, 'rightalt'); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].mod, KMX.KMXFile.RALTFLAG); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows.length, 5); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys.length, 13); + assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys[0].value, 'rightalt-K_BKQUOTE'); + + // Finally, pass the through KMXPlusBuilder, there should be no errors, + // hints, or warnings for this file + + const builder = new KMXPlusBuilder(kmxPlus); + const data = builder.compile(); + + assert.isNotNull(data); + assert.lengthOf(callbacks.messages, 0); + }); + }); + }); +}); diff --git a/developer/src/kmc-kmn/test/embed-osk.tests.ts b/developer/src/kmc-kmn/test/embed-osk.tests.ts index fdf234cecfd..c9383f5d4d5 100644 --- a/developer/src/kmc-kmn/test/embed-osk.tests.ts +++ b/developer/src/kmc-kmn/test/embed-osk.tests.ts @@ -6,25 +6,13 @@ import { fileURLToPath } from 'node:url'; import 'mocha'; import { assert } from 'chai'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; -import { KMX, KMXPlus, ModifierKeyConstant, USVirtualKeyCodes, VisualKeyboard } from '@keymanapp/common-types'; -import { KMXPlusBuilder, oskFontMagicToken } from '@keymanapp/developer-utils'; -import { makePathToFixture } from './helpers/index.js'; -import { KmnCompiler, KmnCompilerMessages } from '../src/main.js'; +import { KMX } from '@keymanapp/common-types'; +import { KmnCompiler } from '../src/main.js'; import { EmbedOskInKmx } from '../src/compiler/embed-osk/embed-osk.js'; -import { loadKvkFile } from '../src/compiler/osk.js'; const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/'); const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/'; - -// VK header is not used in all functions, e.g. buildLayerBags, so this is a -// default header for those tests -const NullVisualKeyboardHeader: VisualKeyboard.VisualKeyboardHeader = { - flags: VisualKeyboard.VisualKeyboardHeaderFlags.kvkhNone, - ansiFont: null, - unicodeFont: null, -}; - describe('Compiler OSK Embedding', function() { const callbacks = new TestCompilerCallbacks(); @@ -261,285 +249,6 @@ describe('Compiler OSK Embedding', function() { }); }); - describe('EmbedOskInKmx.buildLayerBags', function() { - const embedder = new EmbedOskInKmx(callbacks, {}); - - it('should build a bag of layers from an in-memory .kvks structure', async function() { - const vk: VisualKeyboard.VisualKeyboard = { - header: NullVisualKeyboardHeader, - keys: [ - { - vkey: USVirtualKeyCodes.K_A, - text: 'a', - shift: 0, - flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, - }, - { - vkey: USVirtualKeyCodes.K_B, - text: 'B', - shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT, - flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, - }, - { - vkey: USVirtualKeyCodes.K_C, - text: 'Ctrl+Shift+C', - shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT, - flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, - }, - ] - }; - - const strs = new KMXPlus.Strs(); - const keys = new KMXPlus.Keys(strs); - const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); - - assert.lengthOf(strs.strings, 7); - assert.equal(strs.strings[0].value, ''); - assert.equal(strs.strings[1].value, 'default-K_A'); - assert.equal(strs.strings[2].value, 'a'); - assert.equal(strs.strings[3].value, 'shift-K_B'); - assert.equal(strs.strings[4].value, 'B'); - assert.equal(strs.strings[5].value, 'shift-ctrl-K_C'); - assert.equal(strs.strings[6].value, 'Ctrl+Shift+C'); - - assert.lengthOf(keys.flicks, 1); - assert.lengthOf(keys.flicks[0].flicks, 0); - assert.equal(keys.flicks[0].id, strs.strings[0]); - - assert.deepEqual(keys.keys, [ - { - flags: 0, - flicks: "", - id: strs.strings[1], - longPress: null, - longPressDefault: strs.strings[0], - multiTap: null, - switch: strs.strings[0], - to: strs.strings[2], - width: 100 - }, - { - flags: 0, - flicks: "", - id: strs.strings[3], - longPress: null, - longPressDefault: strs.strings[0], - multiTap: null, - switch: strs.strings[0], - to: strs.strings[4], - width: 100 - }, - { - flags: 0, - flicks: "", - id: strs.strings[5], - longPress: null, - longPressDefault: strs.strings[0], - multiTap: null, - switch: strs.strings[0], - to: strs.strings[6], - width: 100 - }, - ]); - - assert.isArray(keys.kmap); - assert.isEmpty(keys.kmap); - - // bag will be a map of maps; this test has three layers with an unmodified base layer K_A, a shift+K_B, and Ctrl+Shift+C - assert.isNotNull(bag); - assert.equal(bag.size, 3); - assert.isTrue(bag.has(0)); - assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG)); - assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG)); - - const defaultLayer = bag.get(0); - assert.equal(defaultLayer.size, 1); - - assert.isTrue(defaultLayer.has(USVirtualKeyCodes.K_A)); - const k_a = defaultLayer.get(USVirtualKeyCodes.K_A); - assert.equal(k_a.flags, 0); - assert.equal(k_a.flicks, ""); - assert.equal(k_a.id, strs.strings[1]); - assert.equal(k_a.longPress, null); - assert.equal(k_a.longPressDefault, strs.strings[0]); - assert.equal(k_a.multiTap, null); - assert.equal(k_a.switch, strs.strings[0]); - assert.equal(k_a.to, strs.strings[2]); - assert.equal(k_a.width, 100); - - const shiftLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG); - assert.equal(shiftLayer.size, 1); - - assert.isTrue(shiftLayer.has(USVirtualKeyCodes.K_B)); - const k_b = shiftLayer.get(USVirtualKeyCodes.K_B); - assert.equal(k_b.flags, 0); - assert.equal(k_b.flicks, ""); - assert.equal(k_b.id, strs.strings[3]); - assert.equal(k_b.longPress, null); - assert.equal(k_b.longPressDefault, strs.strings[0]); - assert.equal(k_b.multiTap, null); - assert.equal(k_b.switch, strs.strings[0]); - assert.equal(k_b.to, strs.strings[4]); - assert.equal(k_b.width, 100); - - const shiftCtrlLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG); - assert.equal(shiftCtrlLayer.size, 1); - - assert.isTrue(shiftCtrlLayer.has(USVirtualKeyCodes.K_C)); - const k_c = shiftCtrlLayer.get(USVirtualKeyCodes.K_C); - assert.equal(k_c.flags, 0); - assert.equal(k_c.flicks, ""); - assert.equal(k_c.id, strs.strings[5]); - assert.equal(k_c.longPress, null); - assert.equal(k_c.longPressDefault, strs.strings[0]); - assert.equal(k_c.multiTap, null); - assert.equal(k_c.switch, strs.strings[0]); - assert.equal(k_c.to, strs.strings[6]); - assert.equal(k_c.width, 100); - - }); - - it('should emit WARN_EmbeddedOskDoesNotSupportBitmaps if a key with kvkkBitmap flag is found', async function() { - const vk: VisualKeyboard.VisualKeyboard = { - header: NullVisualKeyboardHeader, - keys: [ - { - vkey: USVirtualKeyCodes.K_A, - shift: 0, - // kvkkUnicode required because otherwise the key is ignored as 'ansi' - flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap | VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, - bitmap: new Uint8Array() - }, - ] - }; - - const strs = new KMXPlus.Strs(); - const keys = new KMXPlus.Keys(strs); - const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); - assert.isNotNull(bag); - assert.equal(bag.size, 0); - assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.WARN_EmbeddedOskDoesNotSupportBitmaps)); - }); - - it('should emit HINT_EmbeddedOskDoesNotSupportNonUnicode if a key without kvkkUnicode flag is found', async function() { - const vk: VisualKeyboard.VisualKeyboard = { - header: NullVisualKeyboardHeader, - keys: [ - { - vkey: USVirtualKeyCodes.K_A, - shift: 0, - // !kvkkUnicode - flags: 0 as VisualKeyboard.VisualKeyboardKeyFlags, - }, - ] - }; - - const strs = new KMXPlus.Strs(); - const keys = new KMXPlus.Keys(strs); - const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); - assert.isNotNull(bag); - assert.equal(bag.size, 0); - assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.HINT_EmbeddedOskDoesNotSupportNonUnicode)); - }); - }); - - describe('EmbedOskInKmx.buildForm', function() { - const embedder = new EmbedOskInKmx(callbacks, {}); - - it('should a layout of keys from a layer bag, from an in-memory .kvks structure', async function() { - const vk: VisualKeyboard.VisualKeyboard = { - header: NullVisualKeyboardHeader, - keys: [ - { vkey: USVirtualKeyCodes.K_A, text: 'a', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, - { vkey: USVirtualKeyCodes.K_B, text: 'b', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, - { vkey: USVirtualKeyCodes.K_C, text: 'c', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, }, - ] - }; - - const strs = new KMXPlus.Strs(); - const keys = new KMXPlus.Keys(strs); - const layerBags = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys); - assert.isNotNull(layerBags); - const form = embedder.unitTestEndpoints.buildForm(vk, layerBags, strs); - assert.isNotNull(form); - assert.equal(form.baseLayout.value, 'en-us'); // For v19 - assert.equal(form.flags, 0); - assert.equal(form.fontFaceName.value, oskFontMagicToken); - assert.equal(form.fontSizePct, 100); - assert.equal(form.hardware.value, 'us'); - assert.equal(form.minDeviceWidth, 0); - - assert.lengthOf(form.layers, 1); - assert.equal(form.layers[0].id.value, 'default'); - assert.equal(form.layers[0].mod, 0); // no modifiers - assert.equal(form.layers[0].rows.length, 5); - - assert.equal(form.layers[0].rows[0].keys.length, 13); - assert.equal(form.layers[0].rows[1].keys.length, 13); - assert.equal(form.layers[0].rows[2].keys.length, 11); - assert.equal(form.layers[0].rows[3].keys.length, 10); - assert.equal(form.layers[0].rows[4].keys.length, 1); - - assert.deepEqual(form.layers[0].rows[0].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); - assert.deepEqual(form.layers[0].rows[1].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); - assert.deepEqual(form.layers[0].rows[2].keys.map(key => key.value), ['default-K_A','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']); - assert.deepEqual(form.layers[0].rows[3].keys.map(key => key.value), ['gap','gap','default-K_C','gap','default-K_B','gap','gap','gap','gap','gap']); - assert.deepEqual(form.layers[0].rows[4].keys.map(key => key.value), ['gap']); - }); - }); - - describe('EmbedOskInKmx.transformVisualKeyboardToKmxPlus', function() { - const embedder = new EmbedOskInKmx(callbacks, {}); - - it('should transform a .kvks file into an KMX+ structure', async function() { - const vk = loadKvkFile(makePathToFixture('embed-osk', 'khmer_angkor.kvks'), callbacks); - assert.isNotNull(vk); - - const kmxPlus = embedder.unitTestEndpoints.transformVisualKeyboardToKmxPlus(vk); - assert.isNotNull(kmxPlus); - - // Verify various aspects of the kmxPlus based on the source .kvks - assert.equal(kmxPlus.kmxplus.keys.flicks.length, 1); - - // number of s in the .kvks = 186, vscode search - assert.equal(kmxPlus.kmxplus.keys.keys.length, 186); - - // first key in the file is RA K_B ឞ - assert.equal(kmxPlus.kmxplus.keys.keys[0].id.value, 'rightalt-K_B'); - assert.equal(kmxPlus.kmxplus.keys.keys[0].to.value, 'ឞ'); - - // last key in the file is Shift K_BKQUOTE » - assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].id.value, 'shift-K_BKQUOTE'); - assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].to.value, '»'); - - // first layer is ralt - // first key on the first row of the RALT layer should be RALT+BKQUOTE - assert.equal(kmxPlus.kmxplus.layr.forms.length, 1); - assert.equal(kmxPlus.kmxplus.layr.forms[0].baseLayout.value, 'en-us'); - assert.equal(kmxPlus.kmxplus.layr.forms[0].flags, KMXPlus.LayrFormFlags.chiralSeparate); - // TODO-EMBED-OSK-IN-KMX: need to test showBaseLayout at some point - assert.equal(kmxPlus.kmxplus.layr.forms[0].fontFaceName.value, oskFontMagicToken); - assert.equal(kmxPlus.kmxplus.layr.forms[0].fontSizePct, 100); - assert.equal(kmxPlus.kmxplus.layr.forms[0].hardware.value, 'us'); - assert.equal(kmxPlus.kmxplus.layr.forms[0].minDeviceWidth, 0); - assert.equal(kmxPlus.kmxplus.layr.forms[0].layers.length, 4); - assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].id.value, 'rightalt'); - assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].mod, KMX.KMXFile.RALTFLAG); - assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows.length, 5); - assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys.length, 13); - assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys[0].value, 'rightalt-K_BKQUOTE'); - - // Finally, pass the through KMXPlusBuilder, there should be no errors, - // hints, or warnings for this file - - const builder = new KMXPlusBuilder(kmxPlus); - const data = builder.compile(); - - assert.isNotNull(data); - assert.lengthOf(callbacks.messages, 0); - }); - }); - describe('EmbedOskInKmx.embed', function() { // const embedder = new EmbedOskInKmx(callbacks, {}); From 56cdf428eadd577ca0d2337f55a81fd545014b2d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 28 Nov 2025 10:45:34 +0100 Subject: [PATCH 36/37] feat(developer): make shared constants tree-shakable In order to avoid embedding unused tables of constants into KeymanWeb, we need to make their declarations pure. https://esbuild.github.io/api/#tree-shaking-and-side-effects --- .../src/consts/modifier-key-constants.ts | 78 +++++++++++-------- .../types/src/consts/virtual-key-constants.ts | 6 +- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts index 8344aaf66cd..b6eade57cad 100644 --- a/common/web/types/src/consts/modifier-key-constants.ts +++ b/common/web/types/src/consts/modifier-key-constants.ts @@ -45,44 +45,54 @@ export const ModifierKeyConstants = { * corresponding NOT__FLAG are set. */ export enum ModifierKeyConstant { - LCTRLFLAG = ModifierKeyConstants.LCTRLFLAG, - RCTRLFLAG = ModifierKeyConstants.RCTRLFLAG, - LALTFLAG = ModifierKeyConstants.LALTFLAG, - RALTFLAG = ModifierKeyConstants.RALTFLAG, - K_SHIFTFLAG = ModifierKeyConstants.K_SHIFTFLAG, - K_CTRLFLAG = ModifierKeyConstants.K_CTRLFLAG, - K_ALTFLAG = ModifierKeyConstants.K_ALTFLAG, - K_METAFLAG = ModifierKeyConstants.K_METAFLAG, - CAPITALFLAG = ModifierKeyConstants.CAPITALFLAG, - NOTCAPITALFLAG = ModifierKeyConstants.NOTCAPITALFLAG, - NUMLOCKFLAG = ModifierKeyConstants.NUMLOCKFLAG, - NOTNUMLOCKFLAG = ModifierKeyConstants.NOTNUMLOCKFLAG, - SCROLLFLAG = ModifierKeyConstants.SCROLLFLAG, - NOTSCROLLFLAG = ModifierKeyConstants.NOTSCROLLFLAG, - ISVIRTUALKEY = ModifierKeyConstants.ISVIRTUALKEY, - VIRTUALCHARKEY = ModifierKeyConstants.VIRTUALCHARKEY, + NO_MODIFIER = 0, + LCTRLFLAG = 0x0001, // Left Control flag + RCTRLFLAG = 0x0002, // Right Control flag + LALTFLAG = 0x0004, // Left Alt flag + RALTFLAG = 0x0008, // Right Alt flag + K_SHIFTFLAG = 0x0010, // Either shift flag + K_CTRLFLAG = 0x0020, // Either ctrl flag + K_ALTFLAG = 0x0040, // Either alt flag + K_METAFLAG = 0x0080, // Either Meta-key flag (tentative). Not usable in keyboard rules; + // Used internally (currently, only by KMW) to ensure Meta-key + // shortcuts safely bypass rules + // Meta key = Command key on macOS, Windows key on Windows/Linux + CAPITALFLAG = 0x0100, // Caps lock on + NOTCAPITALFLAG = 0x0200, // Caps lock NOT on + NUMLOCKFLAG = 0x0400, // Num lock on + NOTNUMLOCKFLAG = 0x0800, // Num lock NOT on + SCROLLFLAG = 0x1000, // Scroll lock on + NOTSCROLLFLAG = 0x2000, // Scroll lock NOT on + ISVIRTUALKEY = 0x4000, // It is a Virtual Key Sequence + VIRTUALCHARKEY = 0x8000, // Keyman 6.0: Virtual Key Cap Sequence NOT YET }; -export const LDML_MODIFIER_TO_KVK_MODIFIER = new Map(); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboardShiftState.KVKS_LCTRL); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboardShiftState.KVKS_RCTRL); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LALTFLAG, VisualKeyboardShiftState.KVKS_LALT); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RALTFLAG, VisualKeyboardShiftState.KVKS_RALT); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKeyboardShiftState.KVKS_SHIFT); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboardShiftState.KVKS_CTRL); -LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboardShiftState.KVKS_ALT); - -export const KVK_MODIFIER_TO_LDML_MODIFIER = new Map(); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_LCTRL, ModifierKeyConstants.LCTRLFLAG); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_RCTRL, ModifierKeyConstants.RCTRLFLAG); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_LALT, ModifierKeyConstants.LALTFLAG); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_RALT, ModifierKeyConstants.RALTFLAG); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_SHIFT, ModifierKeyConstants.K_SHIFTFLAG); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_CTRL, ModifierKeyConstants.K_CTRLFLAG); -KVK_MODIFIER_TO_LDML_MODIFIER.set(VisualKeyboardShiftState.KVKS_ALT, ModifierKeyConstants.K_ALTFLAG); +export const LDML_MODIFIER_TO_KVK_MODIFIER = /* @__PURE__ */ (() => { + const m = new Map(); + m.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboardShiftState.KVKS_LCTRL); + m.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboardShiftState.KVKS_RCTRL); + m.set(ModifierKeyConstants.LALTFLAG, VisualKeyboardShiftState.KVKS_LALT); + m.set(ModifierKeyConstants.RALTFLAG, VisualKeyboardShiftState.KVKS_RALT); + m.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKeyboardShiftState.KVKS_SHIFT); + m.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboardShiftState.KVKS_CTRL); + m.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboardShiftState.KVKS_ALT); + return m; +})(); + +export const KVK_MODIFIER_TO_LDML_MODIFIER = /* @__PURE__ */ (() => { + const m = new Map(); + m.set(VisualKeyboardShiftState.KVKS_LCTRL, ModifierKeyConstants.LCTRLFLAG); + m.set(VisualKeyboardShiftState.KVKS_RCTRL, ModifierKeyConstants.RCTRLFLAG); + m.set(VisualKeyboardShiftState.KVKS_LALT, ModifierKeyConstants.LALTFLAG); + m.set(VisualKeyboardShiftState.KVKS_RALT, ModifierKeyConstants.RALTFLAG); + m.set(VisualKeyboardShiftState.KVKS_SHIFT, ModifierKeyConstants.K_SHIFTFLAG); + m.set(VisualKeyboardShiftState.KVKS_CTRL, ModifierKeyConstants.K_CTRLFLAG); + m.set(VisualKeyboardShiftState.KVKS_ALT, ModifierKeyConstants.K_ALTFLAG); + return m; +})(); export function translateLdmlModifiersToVisualKeyboardShift(modifiers: ModifierKeyConstant): VisualKeyboardShiftState { - if(modifiers == 0) { + if(modifiers == ModifierKeyConstant.NO_MODIFIER) { return VisualKeyboardShiftState.KVKS_NORMAL; } diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 300db547a53..3e3fca2426b 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -141,10 +141,8 @@ export const USVirtualKeyCodes = { K_TABFWD:50012 }; -const k = USVirtualKeyCodes; - /** Map a CLDR scancode to a US VKey ala USVirtualKeyCodes */ -export const CLDRScanToUSVirtualKeyCodes = { +export const CLDRScanToUSVirtualKeyCodes = /* @__PURE__ */ ((k) => ({ 0x02: k.K_1, 0x03: k.K_2, 0x04: k.K_3, @@ -202,7 +200,7 @@ export const CLDRScanToUSVirtualKeyCodes = { 0x73: k.K_oC1, 0x7D: k.K_oE2, // << Same as 0x56; found on jis -}; +}))(USVirtualKeyCodes); export type KeyMap = number[][]; From f3bab43e96858d55f19efca6a5428ab10023fe93 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 1 Dec 2025 11:15:58 +0100 Subject: [PATCH 37/37] chore(developer): Apply suggestions from code review Co-authored-by: Eberhard Beilharz --- .../web/types/src/consts/modifier-key-constants.ts | 4 ++-- .../kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts | 7 +++++-- .../src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts | 4 ++-- .../src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts | 2 +- .../kmc-kmn/src/compiler/kmn-compiler-messages.ts | 12 ++++++------ developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts | 5 +++-- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts index b6eade57cad..1f09c183f14 100644 --- a/common/web/types/src/consts/modifier-key-constants.ts +++ b/common/web/types/src/consts/modifier-key-constants.ts @@ -8,7 +8,7 @@ import { VisualKeyboardShiftState } from "../kvk/visual-keyboard.js"; /** * This type is declared as a `const` rather than as an `enum` for - * historical reasons. Use instead `ModifierKeyConstant` where possible. + * historical reasons. Use `ModifierKeyConstant` instead where possible. */ export const ModifierKeyConstants = { // Define Keyman Developer modifier bit-flags (exposed for use by other @@ -41,7 +41,7 @@ export const ModifierKeyConstants = { /** * Defines the standard modifier key flags used by Keyman. Note that some keys - * are chiral, and toggle key state is ignored if niether the __FLAG nor the + * are chiral, and toggle key state is ignored if neither the __FLAG nor the * corresponding NOT__FLAG are set. */ export enum ModifierKeyConstant { diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts index 6b1d51d3bdc..434dbdca8e7 100644 --- a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts @@ -19,7 +19,10 @@ export class EmbedOskKvkInKmx { } /** - * Transform a .kvk file to the KMX+ format + * Transform a visual keyboard from a .kvk file to the KMX+ format by setting the appropriate fields in the KMX+ data structure + * + * @param vk content of a .kvk file + * @param kmx */ public transformVisualKeyboardToKmxPlus(kmx: KMXPlus.KMXPlusFile, vk: VisualKeyboard.VisualKeyboard): void { @@ -41,7 +44,7 @@ export class EmbedOskKvkInKmx { * either ANSI (US) or ISO (EU), which are the only two supported layouts in * .kvk */ - private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map, strs: KMXPlus.Strs) { + private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map, strs: KMXPlus.Strs): KMXPlus.LayrForm { const baseLayoutName = 'en-us'; // This is the only value we support for 19.0 const formName: KMXPlus.LayrFormHardware = vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts index 03933547c49..b1ede278473 100644 --- a/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk.ts @@ -55,12 +55,12 @@ export class EmbedOskInKmx { * @param displayMap * @returns */ - public embed(kmx: Uint8Array, kvksFilename: string, touchLayoutFilename: string, displayMap: PuaMap) { + public embed(kmx: Uint8Array, kvksFilename: string, touchLayoutFilename: string, displayMap: PuaMap): Uint8Array { const kmxPlus = this.createEmptyKmxPlusFile(); if(kvksFilename) { const embedKvk = new EmbedOskKvkInKmx(this.callbacks); - const vk = loadKvkFile(kvksFilename, this.callbacks); + const vk = loadKvkFile(kvksFilename, this.callbacks); if(!vk) { // error will have been reported by loadKvkFile return null; diff --git a/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts b/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts index 46f5f5bd9a9..d2e31318b5f 100644 --- a/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts +++ b/developer/src/kmc-kmn/src/compiler/embed-osk/osk-layout.ts @@ -6,7 +6,7 @@ import { USVirtualKeyCodes as V } from "@keymanapp/common-types"; /** - * On Screen Keyboard virtual key codes and positions for the Windows "kbdus" + * On-Screen Keyboard virtual key codes and positions for the Windows "kbdus" * hardware layout which Keyman for Windows uses as its basic positional layout, * mapped against the LDML supported base layouts. Only the character-producing * keys are included; frame keys are excluded. diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts index 9eb6b9928b7..b7bfd87a637 100644 --- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts +++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts @@ -206,18 +206,18 @@ export class KmnCompilerMessages { static WARN_EmbeddedOskDoesNotSupportBitmaps = SevWarn | 0x90D; static Warn_EmbeddedOskDoesNotSupportBitmaps = (o:{keyId: string}) => m( this.WARN_EmbeddedOskDoesNotSupportBitmaps, - `The On Screen Keyboard key '${def(o.keyId)}' uses a bitmap, which is not supported in v19+ embedded On Screen Keyboards`, ` - The v19+ On Screen Keyboard is embedded into the .kmx file. However, bitmap + `The On-Screen Keyboard key '${def(o.keyId)}' uses a bitmap, which is not supported in v19+ embedded On-Screen Keyboards`, ` + The v19+ On-Screen Keyboard is embedded into the .kmx file. However, bitmap images are not supported for key caps. To support arbitrary images, use a - custom font for the On Screen Keyboard. + custom font for the On-Screen Keyboard. `); static HINT_EmbeddedOskDoesNotSupportNonUnicode = SevHint | 0x90E; static Hint_EmbeddedOskDoesNotSupportNonUnicode = (o:{keyId: string}) => m( this.HINT_EmbeddedOskDoesNotSupportNonUnicode, - `The On Screen Keyboard key '${def(o.keyId)}' is not Unicode, and will be ignored`, ` - The v19+ On Screen Keyboard is embedded into the .kmx file. However, - non-Unicode key caps are not supported in the .kmx embedded On Screen + `The On-Screen Keyboard key '${def(o.keyId)}' is not Unicode, and will be ignored`, ` + The v19+ On-Screen Keyboard is embedded into the .kmx file. However, + non-Unicode key caps are not supported in the .kmx embedded On-Screen Keyboard. Only the first non-Unicode key cap found will be reported. `); diff --git a/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts b/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts index b748c9f4dd4..0b88e2de868 100644 --- a/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts +++ b/developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts @@ -218,7 +218,8 @@ describe('Compiler OSK Embedding', function() { }); describe('EmbedOskKvkInKmx.buildForm', function() { - it('should a layout of keys from a layer bag, from an in-memory .kvks structure', async function() { + it('should transform a layout of keys from a layer bag, from an in-memory .kvks structure', async function() { + const vk: VisualKeyboard.VisualKeyboard = { header: NullVisualKeyboardHeader, keys: [ @@ -301,7 +302,7 @@ describe('Compiler OSK Embedding', function() { assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys.length, 13); assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys[0].value, 'rightalt-K_BKQUOTE'); - // Finally, pass the through KMXPlusBuilder, there should be no errors, + // Finally, pass the kmxPlus data through KMXPlusBuilder, there should be no errors, // hints, or warnings for this file const builder = new KMXPlusBuilder(kmxPlus);