coreLdmlConstants)[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 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 = helpLines.findIndex(line => line.includes('start:special_key_caps'));
+ assert.notEqual(line0, -1);
+ const line1 = helpLines.findIndex(line => line.includes('end:special_key_caps'));
+ assert.notEqual(line1, -1);
+
+ const content = helpLines.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..82f24a7385c 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_layoutbuilder_constants.js",
+ "./kmw/_imported_web_osk_specialCharacters.ts",
"./helpers/index.ts",
"./kmw/util.ts"
],
diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts
index d66631e0b56..e0b616be4ea 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';
@@ -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;
}
@@ -165,7 +174,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
});
}
- const kmxBinary = kmxBuilder.compile();
+ const kmxBinary = kmxBuilder.compile(this.kmxPlusTargetVersion);
const kvkWriter = new KvkFileWriter();
const kvkBinary = vkData ? kvkWriter.write(vkData) : null;
@@ -232,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));
}
/**
@@ -357,10 +366,12 @@ 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
+
const sections = this.buildSections(source);
let passed = true;
- const kmx = new KMXPlusFile();
+ const kmx = new KMXPlusFile(this.kmxPlusTargetVersion);
for (const section of sections) {
if (!section.validate()) {
@@ -424,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/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/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/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts
index baaf9282b54..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.LayrList;
+import LayrForm = 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 = [];
@@ -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/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/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 480ac5add52..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,9 +5,10 @@
* 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";
// 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
@@ -26,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) {
}
@@ -60,13 +51,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
@@ -97,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;
@@ -136,33 +127,15 @@ 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, '');
}
- 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/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/compiler-e2e.tests.ts b/developer/src/kmc-ldml/test/compiler-e2e.tests.ts
index 7c05d39d227..24cdbb53885 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';
@@ -7,43 +8,121 @@ 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 { KMX, util } from '@keymanapp/common-types';
+import { KMXPlusBuilder, SectionBuilders } from '@keymanapp/developer-utils';
+import { constants, 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)
+ afterEach(function() {
+ if (this.currentTest.state !== 'passed') {
+ compilerTestCallbacks.printMessages();
+ }
+ });
- const inputFilename = makePathToFixture('basic.xml');
- const binaryFilename = makePathToFixture('basic.txt');
+ [
+ [17, KMX.KMX_Version.VERSION_170],
+ [19, KMX.KMX_Version.VERSION_190],
+ ].forEach(([vernum, version]) => {
+ 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)
+
+ 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, 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);
+ 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);
+ });
+ });
- // Compare output
- const expected = await hextobin(binaryFilename, undefined, {silent:true});
+ 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
+ );
+ });
- // 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 });
+ 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);
- 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);
+ assert.equal(kmxPlusBuilder.sect.sect?.header.ident, constants.hex_section_id(constants.section.sect));
- // TODO-LDML: compare the .kvk file to something else?
- assert.isNotNull(kvk?.data);
+ 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.txt b/developer/src/kmc-ldml/test/fixtures/basic-17.txt
similarity index 96%
rename from developer/src/kmc-ldml/test/fixtures/basic.txt
rename to developer/src/kmc-ldml/test/fixtures/basic-17.txt
index 81748b400e3..3d9891c9cb1 100644
--- a/developer/src/kmc-ldml/test/fixtures/basic.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
@@ -427,7 +414,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
new file mode 100644
index 00000000000..75806e72934
--- /dev/null
+++ b/developer/src/kmc-ldml/test/fixtures/basic-19.txt
@@ -0,0 +1,683 @@
+#
+# Keyman is copyright (C) SIL International. MIT License.
+#
+# 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.
+#
+# 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
+#
+# See README.md for more information.
+#
+
+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 '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
+ 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 (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 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 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,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
+
+
+
+# ----------------------------------------------------------------------------------------------------
+# 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 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
+ # form 0
+ index(strNull,strUs,2) # KMXPLUS_STR hardware = 'us'
+ 00 00 00 00 # KMX_DWORD layer;
+ 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
+ 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_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
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..a0e76085550 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;
@@ -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');
},
},
@@ -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 64e4a3d373a..42766c44830 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',
@@ -29,12 +31,12 @@ describe('layr', function () {
assert.ok(layr);
assert.equal(compilerTestCallbacks.messages.length, 0);
- assert.equal(layr.lists?.length, 1);
- const list0 = layr.lists[0];
- assert.ok(list0);
- assert.equal(list0.layers.length, 1);
- assert.equal(list0.hardware.value, 'us');
- const layer0 = list0.layers[0];
+ assert.equal(layr.forms?.length, 1);
+ 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];
@@ -50,13 +52,13 @@ 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');
- 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.lists.find(v => v.hardware.value === constants.layr_list_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_form_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,8 +125,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.forms.length');
+ const layers = layr.forms[0];
const bymod = layers.layers.map(({id,mod,rows})=>([
id.value, mod, rows[0].keys[0].value,
]));
@@ -149,12 +151,12 @@ describe('layr', function () {
assert.ok(layr);
assert.equal(compilerTestCallbacks.messages.length, 0);
- assert.equal(layr.lists?.length, 1);
- const list0 = layr.lists[0];
- assert.ok(list0);
- assert.equal(list0.layers.length, 1);
- assert.equal(list0.hardware.value, 'us');
- const layer0 = list0.layers[0];
+ assert.equal(layr.forms?.length, 1);
+ 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/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;
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/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');
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..a0087f3d4c7 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
};
//
@@ -71,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 155b1252589..1714149c403 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;
@@ -51,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;
@@ -195,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 099a5e69785..c278bbd2902 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);
@@ -1200,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)) {
@@ -1290,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
@@ -3355,6 +3365,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;
@@ -3396,21 +3412,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);
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;
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 3d02c2943c0..68269ac0ba7 100644
--- a/developer/src/kmdecomp/savekeyboard.cpp
+++ b/developer/src/kmdecomp/savekeyboard.cpp
@@ -30,8 +30,7 @@
#define _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
#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"
@@ -96,6 +95,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/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/docs/build/windows.md b/docs/build/windows.md
index edc756340ff..4f3b02c898b 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/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
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/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md
index 6c252de5247..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
@@ -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 |
@@ -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`
+- `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
@@ -336,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
@@ -400,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).
@@ -491,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.
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 */
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,
diff --git a/windows/src/engine/keyman32/keyman64.h b/windows/src/engine/keyman32/keyman64.h
index e46dad17829..7299abc31d2 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"
@@ -126,6 +126,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 2304bc5f382..91a5166b7ae 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/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..be9c39a8ce7 100644
--- a/windows/src/engine/mcompile/mcompile.h
+++ b/windows/src/engine/mcompile/mcompile.h
@@ -1,20 +1,19 @@
/*
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 +27,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 2de972b9f97..eb7a71f7296 100644
--- a/windows/src/engine/mcompile/pch.h
+++ b/windows/src/engine/mcompile/pch.h
@@ -8,10 +8,10 @@
#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/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"
|