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 f33ea20c34..9d5c20e526 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 9f9bbd6fc3..e689c0aa41 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 6a608d487b..76fb645213 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 7d94f880df..d1cc681663 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 1c41df145b..a678dc8726 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 0000000000..d9acdb7d35 --- /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 dee653dfac..ffdcf60ae7 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 117020e5d5..7680725d40 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 c13e63715e..4efac09233 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,