@keymanapp/kmc-ldml 18.0.16-alpha → 18.0.18-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/build/src/compiler/compiler.js +309 -308
  2. package/build/src/compiler/compiler.js.map +1 -1
  3. package/build/src/compiler/disp.js +84 -83
  4. package/build/src/compiler/disp.js.map +1 -1
  5. package/build/src/compiler/empty-compiler.js +116 -115
  6. package/build/src/compiler/empty-compiler.js.map +1 -1
  7. package/build/src/compiler/keymanweb-compiler.js +97 -96
  8. package/build/src/compiler/keymanweb-compiler.js.map +1 -1
  9. package/build/src/compiler/keys.d.ts.map +1 -1
  10. package/build/src/compiler/keys.js +419 -415
  11. package/build/src/compiler/keys.js.map +1 -1
  12. package/build/src/compiler/layr.d.ts.map +1 -1
  13. package/build/src/compiler/layr.js +83 -76
  14. package/build/src/compiler/layr.js.map +1 -1
  15. package/build/src/compiler/ldml-compiler-options.js +5 -4
  16. package/build/src/compiler/ldml-compiler-options.js.map +1 -1
  17. package/build/src/compiler/loca.js +61 -60
  18. package/build/src/compiler/loca.js.map +1 -1
  19. package/build/src/compiler/messages.d.ts +11 -3
  20. package/build/src/compiler/messages.d.ts.map +1 -1
  21. package/build/src/compiler/messages.js +120 -109
  22. package/build/src/compiler/messages.js.map +1 -1
  23. package/build/src/compiler/meta.js +57 -56
  24. package/build/src/compiler/meta.js.map +1 -1
  25. package/build/src/compiler/metadata-compiler.js +49 -48
  26. package/build/src/compiler/metadata-compiler.js.map +1 -1
  27. package/build/src/compiler/section-compiler.js +42 -41
  28. package/build/src/compiler/section-compiler.js.map +1 -1
  29. package/build/src/compiler/substitution-tracker.js +105 -104
  30. package/build/src/compiler/substitution-tracker.js.map +1 -1
  31. package/build/src/compiler/touch-layout-compiler.js +93 -92
  32. package/build/src/compiler/touch-layout-compiler.js.map +1 -1
  33. package/build/src/compiler/tran.d.ts +7 -0
  34. package/build/src/compiler/tran.d.ts.map +1 -1
  35. package/build/src/compiler/tran.js +382 -353
  36. package/build/src/compiler/tran.js.map +1 -1
  37. package/build/src/compiler/vars.d.ts.map +1 -1
  38. package/build/src/compiler/vars.js +236 -227
  39. package/build/src/compiler/vars.js.map +1 -1
  40. package/build/src/compiler/visual-keyboard-compiler.js +70 -69
  41. package/build/src/compiler/visual-keyboard-compiler.js.map +1 -1
  42. package/build/src/main.js +5 -4
  43. package/build/src/main.js.map +1 -1
  44. package/build/src/util/util.d.ts +2 -2
  45. package/build/src/util/util.d.ts.map +1 -1
  46. package/build/src/util/util.js +185 -179
  47. package/build/src/util/util.js.map +1 -1
  48. package/package.json +6 -6
@@ -1,416 +1,420 @@
1
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bf7b9fde-52d2-5bfd-92db-514587d6b939")}catch(e){}}();
2
- import { constants } from '@keymanapp/ldml-keyboard-constants';
3
- import { LDMLKeyboard, KMXPlus, Constants } from '@keymanapp/common-types';
4
- import { CompilerMessages } from './messages.js';
5
- import { SectionCompiler } from "./section-compiler.js";
6
- var Keys = KMXPlus.Keys;
7
- var KeysFlicks = KMXPlus.KeysFlicks;
8
- import { allUsedKeyIdsInFlick, allUsedKeyIdsInKey, allUsedKeyIdsInLayers, calculateUniqueKeys, hashFlicks, hashKeys, translateLayerAttrToModifier, validModifier } from '../util/util.js';
9
- import { SubstitutionUse } from './substitution-tracker.js';
10
- /** reserved name for the special gap key. space is not allowed in key ids. */
11
- const reserved_gap = "gap (reserved)";
12
- export class KeysCompiler extends SectionCompiler {
13
- static validateSubstitutions(keyboard, st) {
14
- const uniqueKeys = calculateUniqueKeys([...keyboard.keys?.key]);
15
- const keyBag = hashKeys(uniqueKeys); // for easier lookup
16
- // will be the set of ALL keys used in this keyboard
17
- const usedKeys = allUsedKeyIdsInLayers(keyboard?.layers);
18
- // save off the layer key IDs before we mutate the set
19
- const layerKeyIds = Array.from(usedKeys.values());
20
- const flickHash = hashFlicks(keyboard?.flicks?.flick); // for easier lookup
21
- const usedFlicks = KeysCompiler.getUsedFlicks(layerKeyIds, keyBag);
22
- KeysCompiler.addKeysFromFlicks(usedFlicks, flickHash, usedKeys);
23
- KeysCompiler.addUsedGestureKeys(layerKeyIds, keyBag, usedKeys);
24
- // process each used key. unused keys don't get checked.
25
- for (let keyId of usedKeys.values()) {
26
- const key = keyBag.get(keyId);
27
- if (!key)
28
- continue; // key not found is handled elsewhere.
29
- st.addStringAndMarkerSubstitution(SubstitutionUse.emit, key.output);
30
- }
31
- return true;
32
- }
33
- get id() {
34
- return constants.section.keys;
35
- }
36
- get dependencies() {
37
- const defaults = new Set([
38
- constants.section.elem,
39
- constants.section.list,
40
- constants.section.meta,
41
- constants.section.strs,
42
- constants.section.vars,
43
- ]);
44
- return defaults;
45
- }
46
- /**
47
- *
48
- * @returns just the non-touch layers.
49
- */
50
- hardwareLayers() {
51
- return this.keyboard3.layers?.filter(({ formId }) => formId !== "touch");
52
- }
53
- validate() {
54
- let valid = true;
55
- // There's no 'form' compiler.
56
- // We validate this here so that someone checks it.
57
- this.keyboard3.forms?.form?.forEach((form) => {
58
- if (!LDMLKeyboard.ImportStatus.isImpliedImport(form)) {
59
- // If it's not an implied import, give a warning.
60
- this.callbacks.reportMessage(CompilerMessages.Warn_CustomForm({ id: form.id }));
61
- }
62
- });
63
- const keyBag = this.getKeyBag();
64
- // will be the set of ALL keys used in this keyboard
65
- const usedKeys = this.getLayerKeyIds();
66
- // save off the layer key IDs before we mutate the set
67
- const layerKeyIds = Array.from(usedKeys.values());
68
- const flickHash = this.getFlicks(); // for easier lookup
69
- const usedFlicks = KeysCompiler.getUsedFlicks(layerKeyIds, keyBag);
70
- // go through each layer key and collect flicks and gestures
71
- for (const keyId of layerKeyIds) {
72
- const key = keyBag.get(keyId);
73
- if (!key) {
74
- // Note: validateHardwareLayerForKmap(), below, will raise an error for hardware keys that are missing, with additional
75
- // context. For this section, we just skip missing keys.
76
- continue;
77
- }
78
- const { flickId } = key;
79
- if (flickId) {
80
- if (!flickHash.has(flickId)) {
81
- valid = false;
82
- this.callbacks.reportMessage(CompilerMessages.Error_MissingFlicks({ flickId, id: keyId }));
83
- }
84
- }
85
- const gestureKeys = allUsedKeyIdsInKey(key);
86
- for (const [gestureKeyId, attrs] of gestureKeys.entries()) {
87
- const gestureKey = keyBag.get(gestureKeyId);
88
- if (gestureKey == null) {
89
- // TODO-LDML: could keep track of already missing keys so we don't warn multiple times on gesture keys
90
- valid = false;
91
- this.callbacks.reportMessage(CompilerMessages.Error_GestureKeyNotFoundInKeyBag({ keyId: gestureKeyId, parentKeyId: keyId, attribute: attrs.join(',') }));
92
- }
93
- else {
94
- usedKeys.add(gestureKeyId);
95
- }
96
- }
97
- }
98
- // now, check the flicks
99
- KeysCompiler.addKeysFromFlicks(usedFlicks, flickHash, usedKeys);
100
- // TODO-LDML: hint on unused flicks (that aren't imported)
101
- // Note: the layr compiler does more extensive validation of the layer attributes.
102
- // Kmap validation
103
- const hardwareLayers = this.hardwareLayers();
104
- if (hardwareLayers.length >= 1) {
105
- // validate all errors
106
- for (let layers of hardwareLayers) {
107
- for (let layer of layers.layer) {
108
- valid =
109
- this.validateHardwareLayerForKmap(layers.formId, layer, keyBag) && valid; // note: always validate even if previously invalid results found
110
- }
111
- }
112
- // TODO-LDML: } else { touch?
113
- }
114
- return valid;
115
- }
116
- static addKeysFromFlicks(usedFlicks, flickHash, usedKeys) {
117
- for (let flickId of usedFlicks.values()) {
118
- const flick = flickHash.get(flickId);
119
- if (!flick)
120
- continue;
121
- const flickKeys = allUsedKeyIdsInFlick(flick);
122
- flickKeys.forEach(keyId => usedKeys.add(keyId));
123
- }
124
- }
125
- getFlicks() {
126
- return hashFlicks(this.keyboard3?.flicks?.flick);
127
- }
128
- /** a set with all key ids used in all layers */
129
- getLayerKeyIds() {
130
- return allUsedKeyIdsInLayers(this.keyboard3?.layers);
131
- }
132
- /** the entire keybag (used or unused) as a hash */
133
- getKeyBag() {
134
- const uniqueKeys = calculateUniqueKeys([...this.keyboard3.keys?.key]);
135
- return hashKeys(uniqueKeys); // for easier lookup
136
- }
137
- compile(sections) {
138
- /* c8 ignore next 4 */
139
- if (!this.keyboard3?.keys?.key && !this.keyboard3?.keys?.flicks) {
140
- // short-circuit if no keys or flicks. Doesn't happen in practice due to implied import.
141
- return null;
142
- }
143
- let sect = new Keys(sections.strs);
144
- // TODO-LDML: some duplication with validate()
145
- const keyBag = this.getKeyBag();
146
- // We only want to include used keys in .kmx
147
- const usedKeys = this.getLayerKeyIds();
148
- // save off the layer key IDs before we mutate the set
149
- const layerKeyIds = Array.from(usedKeys.values());
150
- // Load the flicks first
151
- this.loadFlicks(sections, sect, keyBag, layerKeyIds, usedKeys);
152
- // add in the gesture keys
153
- KeysCompiler.addUsedGestureKeys(layerKeyIds, keyBag, usedKeys);
154
- // Now, load the keys into memory
155
- this.loadKeys(sections, sect, keyBag, layerKeyIds, usedKeys);
156
- // Finally, kmap
157
- // Use LayerMap + keys to generate compiled keys for hardware
158
- const hardwareLayers = this.hardwareLayers();
159
- /* c8 ignore next 3 */
160
- if (hardwareLayers.length > 1) {
161
- // validation should have already caught this
162
- throw Error(`Internal error: Expected 0 or 1 hardware layer, not ${hardwareLayers.length}`);
163
- }
164
- else if (hardwareLayers.length === 1) {
165
- const theLayers = hardwareLayers[0];
166
- const { formId } = theLayers;
167
- for (let layer of theLayers.layer) {
168
- this.compileHardwareLayerToKmap(sections, layer, sect, formId);
169
- }
170
- } // else: TODO-LDML do nothing if only touch layers
171
- // Now load the reserved keys and slip them in here
172
- const reservedKeys = this.getReservedKeys(sections);
173
- for (const key of reservedKeys.values()) {
174
- sect.keys.push(key);
175
- }
176
- return sect;
177
- }
178
- /** list of reserved keys, for tests */
179
- static reserved_keys = [reserved_gap];
180
- /** count of reserved keys, for tests */
181
- static reserved_count = KeysCompiler.reserved_keys.length;
182
- /** load up all reserved keys */
183
- getReservedKeys(sections) {
184
- const r = new Map();
185
- // set up some constants..
186
- const no_string = sections.strs.allocString('');
187
- const no_list = sections.list.allocList([], {}, sections);
188
- // now add the reserved key(s).
189
- r.set(reserved_gap, {
190
- flags: constants.keys_key_flags_gap | constants.keys_key_flags_extend,
191
- id: sections.strs.allocString(reserved_gap),
192
- flicks: '',
193
- longPress: no_list,
194
- longPressDefault: no_string,
195
- multiTap: no_list,
196
- switch: no_string,
197
- to: no_string,
198
- width: 10.0, // 10 * .1
199
- });
200
- if (r.size !== KeysCompiler.reserved_count) {
201
- throw Error(`Internal Error: KeysCompiler.reserved_count=${KeysCompiler.reserved_count} != ${r.size} actual reserved keys.`);
202
- }
203
- return r;
204
- }
205
- static addUsedGestureKeys(layerKeyIds, keyBag, usedKeys) {
206
- for (let keyId of layerKeyIds) {
207
- const key = keyBag.get(keyId);
208
- if (!key)
209
- continue;
210
- for (let gestureKeyId of allUsedKeyIdsInKey(key).keys()) {
211
- usedKeys.add(gestureKeyId);
212
- }
213
- }
214
- }
215
- /**
216
- *
217
- * @param keyBag the keybag as a hash
218
- * @param layerKeyIds list of keys from the layer, to extract used flicks
219
- * @param usedKeys will be populated with keys used in the flick
220
- */
221
- loadFlicks(sections, sect, keyBag, layerKeyIds, usedKeys) {
222
- const flickHash = this.getFlicks(); // for easier lookup
223
- const usedFlicks = KeysCompiler.getUsedFlicks(layerKeyIds, keyBag);
224
- // only include used flicks in the table
225
- // this way, extra unused imported flicks are ignored
226
- // in id order, for now
227
- for (let flickId of Array.from(usedFlicks.values()).sort()) {
228
- const flick = flickHash.get(flickId);
229
- if (!flick)
230
- continue; // already reported by validate()
231
- // allocate the in-memory <flick id=…>
232
- let flicks = new KeysFlicks(sections.strs.allocString(flickId));
233
- // add data from each segment
234
- for (let { keyId, directions } of flick.flickSegment) {
235
- const keyIdStr = sections.strs.allocString(keyId);
236
- let directionsList = sections.list.allocListFromSpaces(directions, {}, sections);
237
- flicks.flicks.push({
238
- directions: directionsList,
239
- keyId: keyIdStr,
240
- });
241
- usedKeys.add(keyId);
242
- }
243
- sect.flicks.push(flicks);
244
- }
245
- }
246
- static getUsedFlicks(layerKeyIds, keyBag) {
247
- const usedFlicks = new Set();
248
- for (let keyId of layerKeyIds) {
249
- const key = keyBag.get(keyId);
250
- if (!key?.flickId)
251
- continue;
252
- usedFlicks.add(key.flickId);
253
- }
254
- return usedFlicks;
255
- }
256
- loadKeys(sections, sect, keyBag, layerKeyIds, usedKeys) {
257
- // for each used key (whether from layer, gesture, etc.)
258
- // push these in id order, for tidiness
259
- for (let keyId of Array.from(usedKeys.values()).sort()) {
260
- const key = keyBag.get(keyId);
261
- if (!key)
262
- continue; // missing key
263
- let flags = 0;
264
- const { flickId, gap, longPressDefaultKeyId, longPressKeyIds, multiTapKeyIds, layerId, output } = key;
265
- if (!!gap) {
266
- flags |= constants.keys_key_flags_gap;
267
- }
268
- const id = sections.strs.allocString(key.id);
269
- const longPress = sections.list.allocListFromSpaces(longPressKeyIds, {}, sections);
270
- const longPressDefault = sections.strs.allocString(longPressDefaultKeyId, {}, sections);
271
- const multiTap = sections.list.allocListFromSpaces(multiTapKeyIds, {}, sections);
272
- const keySwitch = sections.strs.allocString(layerId); // 'switch' is a reserved word
273
- const toRaw = output;
274
- let toCooked = sections.vars.substituteStrings(toRaw, sections);
275
- toCooked = sections.vars.substituteMarkerString(toCooked);
276
- const to = sections.strs.allocString(toCooked, {
277
- stringVariables: true,
278
- markers: true,
279
- unescape: true,
280
- singleOk: true,
281
- nfd: true,
282
- }, sections);
283
- if (!to.isOneChar) {
284
- flags |= constants.keys_key_flags_extend;
285
- }
286
- const width = Math.ceil((key.width || 1) * 10.0); // default, width=1
287
- sect.keys.push({
288
- flags,
289
- flicks: flickId,
290
- id,
291
- longPress,
292
- longPressDefault,
293
- multiTap,
294
- switch: keySwitch,
295
- to,
296
- width,
297
- });
298
- }
299
- }
300
- getKeymapFromForm(hardware, badScans) {
301
- return KeysCompiler.getKeymapFromForms(this.keyboard3?.forms.form, hardware, badScans);
302
- }
303
- static getKeymapFromForms(forms, hardware, badScans) {
304
- // seach in reverse form because of overrides
305
- const ldmlForm = [...forms].reverse().find((f) => f.id === hardware);
306
- if (!ldmlForm) {
307
- return undefined;
308
- }
309
- return KeysCompiler.getKeymapFromScancodes(ldmlForm, badScans);
310
- }
311
- static getKeymapFromScancodes(ldmlForm, badScans) {
312
- const { scanCodes } = ldmlForm;
313
- const ldmlScan = scanCodes.map(o => o.codes.split(" ").map(n => Number.parseInt(n, 16)));
314
- const ldmlVkey = Constants.CLDRScanToKeyMap(ldmlScan, badScans);
315
- return ldmlVkey;
316
- }
317
- /**
318
- * Validate for purpose of kmap
319
- * @param hardware the 'form' parameter
320
- * @param layer
321
- * @param keyHash the keybag's hash
322
- * @returns true if valid
323
- */
324
- validateHardwareLayerForKmap(hardware, layer, keyHash) {
325
- let valid = true;
326
- const { modifiers } = layer;
327
- if (!validModifier(modifiers)) {
328
- this.callbacks.reportMessage(CompilerMessages.Error_InvalidModifier({ modifiers, layer: layer.id }));
329
- valid = false;
330
- }
331
- const badScans = new Set();
332
- const keymap = this.getKeymapFromForm(hardware, badScans);
333
- if (!keymap) {
334
- this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({ formId: hardware }));
335
- valid = false;
336
- return valid;
337
- }
338
- else if (badScans.size !== 0) {
339
- const codes = Array.from(badScans.values()).map(n => Number(n).toString(16)).sort();
340
- this.callbacks.reportMessage(CompilerMessages.Error_InvalidScanCode({ form: hardware, codes }));
341
- valid = false;
342
- return valid;
343
- }
344
- if (layer.row.length > keymap.length) {
345
- this.callbacks.reportMessage(CompilerMessages.Error_HardwareLayerHasTooManyRows());
346
- valid = false;
347
- }
348
- for (let y = 0; y < layer.row.length && y < keymap.length; y++) {
349
- const keys = layer.row[y].keys.split(" ");
350
- if (keys.length > keymap[y].length) {
351
- this.callbacks.reportMessage(CompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({
352
- row: y + 1,
353
- hardware,
354
- modifiers,
355
- }));
356
- valid = false;
357
- }
358
- let x = -1;
359
- for (let key of keys) {
360
- x++;
361
- let keydef = keyHash.get(key);
362
- if (!keydef) {
363
- this.callbacks.reportMessage(CompilerMessages.Error_KeyNotFoundInKeyBag({
364
- keyId: key,
365
- col: x + 1,
366
- row: y + 1,
367
- layer: layer.id,
368
- form: "hardware",
369
- }));
370
- valid = false;
371
- continue;
372
- }
373
- if (!keydef.output && !keydef.gap && !keydef.layerId) {
374
- this.callbacks.reportMessage(CompilerMessages.Error_KeyMissingToGapOrSwitch({ keyId: key }));
375
- valid = false;
376
- continue;
377
- }
378
- }
379
- }
380
- return valid;
381
- }
382
- compileHardwareLayerToKmap(sections, layer, sect, hardware) {
383
- const mod = translateLayerAttrToModifier(layer);
384
- const keymap = this.getKeymapFromForm(hardware);
385
- // Iterate over rows (y) and cols (x) of the scancodes table.
386
- // Any assigned keys will be used until we run out of keys in each row,
387
- // and run out of rows. The rest will be reserved_gap.
388
- for (let y = 0; y < keymap.length; y++) {
389
- let keys;
390
- // if there are keys, use them.
391
- if (y < layer.row.length) {
392
- const row = layer.row[y];
393
- keys = row.keys.split(" ");
394
- }
395
- else {
396
- keys = [];
397
- }
398
- // all columns in this row
399
- for (let x = 0; x < keymap[y].length; x++) {
400
- const vkey = keymap[y][x]; // from the scan table
401
- let key = reserved_gap; // unless there's a key in this row
402
- if (x < keys.length) {
403
- key = keys[x];
404
- }
405
- sect.kmap.push({
406
- vkey,
407
- mod,
408
- key, // key id, to be changed into key index at finalization
409
- });
410
- }
411
- }
412
- return sect;
413
- }
414
- }
415
- //# debugId=bf7b9fde-52d2-5bfd-92db-514587d6b939
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ce44ab4e-cf0b-5e01-b83e-b8818380b183")}catch(e){}}();
3
+ import { constants } from '@keymanapp/ldml-keyboard-constants';
4
+ import { LDMLKeyboard, KMXPlus, Constants } from '@keymanapp/common-types';
5
+ import { CompilerMessages } from './messages.js';
6
+ import { SectionCompiler } from "./section-compiler.js";
7
+ var Keys = KMXPlus.Keys;
8
+ var KeysFlicks = KMXPlus.KeysFlicks;
9
+ import { allUsedKeyIdsInFlick, allUsedKeyIdsInKey, allUsedKeyIdsInLayers, calculateUniqueKeys, hashFlicks, hashKeys, translateLayerAttrToModifier, validModifier } from '../util/util.js';
10
+ import { SubstitutionUse } from './substitution-tracker.js';
11
+ /** reserved name for the special gap key. space is not allowed in key ids. */
12
+ const reserved_gap = "gap (reserved)";
13
+ export class KeysCompiler extends SectionCompiler {
14
+ static validateSubstitutions(keyboard, st) {
15
+ const uniqueKeys = calculateUniqueKeys([...keyboard.keys?.key]);
16
+ const keyBag = hashKeys(uniqueKeys); // for easier lookup
17
+ // will be the set of ALL keys used in this keyboard
18
+ const usedKeys = allUsedKeyIdsInLayers(keyboard?.layers);
19
+ // save off the layer key IDs before we mutate the set
20
+ const layerKeyIds = Array.from(usedKeys.values());
21
+ const flickHash = hashFlicks(keyboard?.flicks?.flick); // for easier lookup
22
+ const usedFlicks = KeysCompiler.getUsedFlicks(layerKeyIds, keyBag);
23
+ KeysCompiler.addKeysFromFlicks(usedFlicks, flickHash, usedKeys);
24
+ KeysCompiler.addUsedGestureKeys(layerKeyIds, keyBag, usedKeys);
25
+ // process each used key. unused keys don't get checked.
26
+ for (let keyId of usedKeys.values()) {
27
+ const key = keyBag.get(keyId);
28
+ if (!key)
29
+ continue; // key not found is handled elsewhere.
30
+ st.addStringAndMarkerSubstitution(SubstitutionUse.emit, key.output);
31
+ }
32
+ return true;
33
+ }
34
+ get id() {
35
+ return constants.section.keys;
36
+ }
37
+ get dependencies() {
38
+ const defaults = new Set([
39
+ constants.section.elem,
40
+ constants.section.list,
41
+ constants.section.meta,
42
+ constants.section.strs,
43
+ constants.section.vars,
44
+ ]);
45
+ return defaults;
46
+ }
47
+ /**
48
+ *
49
+ * @returns just the non-touch layers.
50
+ */
51
+ hardwareLayers() {
52
+ return this.keyboard3.layers?.filter(({ formId }) => formId !== "touch");
53
+ }
54
+ validate() {
55
+ let valid = true;
56
+ // There's no 'form' compiler.
57
+ // We validate this here so that someone checks it.
58
+ this.keyboard3.forms?.form?.forEach((form) => {
59
+ if (!LDMLKeyboard.ImportStatus.isImpliedImport(form)) {
60
+ // If it's not an implied import, give a warning.
61
+ this.callbacks.reportMessage(CompilerMessages.Warn_CustomForm({ id: form.id }));
62
+ }
63
+ });
64
+ const keyBag = this.getKeyBag();
65
+ // will be the set of ALL keys used in this keyboard
66
+ const usedKeys = this.getLayerKeyIds();
67
+ // save off the layer key IDs before we mutate the set
68
+ const layerKeyIds = Array.from(usedKeys.values());
69
+ const flickHash = this.getFlicks(); // for easier lookup
70
+ const usedFlicks = KeysCompiler.getUsedFlicks(layerKeyIds, keyBag);
71
+ // go through each layer key and collect flicks and gestures
72
+ for (const keyId of layerKeyIds) {
73
+ const key = keyBag.get(keyId);
74
+ if (!key) {
75
+ // Note: validateHardwareLayerForKmap(), below, will raise an error for hardware keys that are missing, with additional
76
+ // context. For this section, we just skip missing keys.
77
+ continue;
78
+ }
79
+ const { flickId } = key;
80
+ if (flickId) {
81
+ if (!flickHash.has(flickId)) {
82
+ valid = false;
83
+ this.callbacks.reportMessage(CompilerMessages.Error_MissingFlicks({ flickId, id: keyId }));
84
+ }
85
+ }
86
+ const gestureKeys = allUsedKeyIdsInKey(key);
87
+ for (const [gestureKeyId, attrs] of gestureKeys.entries()) {
88
+ const gestureKey = keyBag.get(gestureKeyId);
89
+ if (gestureKey == null) {
90
+ // TODO-LDML: could keep track of already missing keys so we don't warn multiple times on gesture keys
91
+ valid = false;
92
+ this.callbacks.reportMessage(CompilerMessages.Error_GestureKeyNotFoundInKeyBag({ keyId: gestureKeyId, parentKeyId: keyId, attribute: attrs.join(',') }));
93
+ }
94
+ else {
95
+ usedKeys.add(gestureKeyId);
96
+ }
97
+ }
98
+ }
99
+ // now, check the flicks
100
+ KeysCompiler.addKeysFromFlicks(usedFlicks, flickHash, usedKeys);
101
+ // TODO-LDML: hint on unused flicks (that aren't imported)
102
+ // Note: the layr compiler does more extensive validation of the layer attributes.
103
+ // Kmap validation
104
+ const hardwareLayers = this.hardwareLayers();
105
+ if (hardwareLayers.length >= 1) {
106
+ // validate all errors
107
+ for (let layers of hardwareLayers) {
108
+ for (let layer of layers.layer) {
109
+ valid =
110
+ this.validateHardwareLayerForKmap(layers.formId, layer, keyBag) && valid; // note: always validate even if previously invalid results found
111
+ }
112
+ }
113
+ // TODO-LDML: } else { touch?
114
+ }
115
+ return valid;
116
+ }
117
+ static addKeysFromFlicks(usedFlicks, flickHash, usedKeys) {
118
+ for (let flickId of usedFlicks.values()) {
119
+ const flick = flickHash.get(flickId);
120
+ if (!flick)
121
+ continue;
122
+ const flickKeys = allUsedKeyIdsInFlick(flick);
123
+ flickKeys.forEach(keyId => usedKeys.add(keyId));
124
+ }
125
+ }
126
+ getFlicks() {
127
+ return hashFlicks(this.keyboard3?.flicks?.flick);
128
+ }
129
+ /** a set with all key ids used in all layers */
130
+ getLayerKeyIds() {
131
+ return allUsedKeyIdsInLayers(this.keyboard3?.layers);
132
+ }
133
+ /** the entire keybag (used or unused) as a hash */
134
+ getKeyBag() {
135
+ const uniqueKeys = calculateUniqueKeys([...this.keyboard3.keys?.key]);
136
+ return hashKeys(uniqueKeys); // for easier lookup
137
+ }
138
+ compile(sections) {
139
+ /* c8 ignore next 4 */
140
+ if (!this.keyboard3?.keys?.key && !this.keyboard3?.keys?.flicks) {
141
+ // short-circuit if no keys or flicks. Doesn't happen in practice due to implied import.
142
+ return null;
143
+ }
144
+ let sect = new Keys(sections.strs);
145
+ // TODO-LDML: some duplication with validate()
146
+ const keyBag = this.getKeyBag();
147
+ // We only want to include used keys in .kmx
148
+ const usedKeys = this.getLayerKeyIds();
149
+ // save off the layer key IDs before we mutate the set
150
+ const layerKeyIds = Array.from(usedKeys.values());
151
+ // Load the flicks first
152
+ this.loadFlicks(sections, sect, keyBag, layerKeyIds, usedKeys);
153
+ // add in the gesture keys
154
+ KeysCompiler.addUsedGestureKeys(layerKeyIds, keyBag, usedKeys);
155
+ // Now, load the keys into memory
156
+ this.loadKeys(sections, sect, keyBag, layerKeyIds, usedKeys);
157
+ // Finally, kmap
158
+ // Use LayerMap + keys to generate compiled keys for hardware
159
+ const hardwareLayers = this.hardwareLayers();
160
+ /* c8 ignore next 3 */
161
+ if (hardwareLayers.length > 1) {
162
+ // validation should have already caught this
163
+ throw Error(`Internal error: Expected 0 or 1 hardware layer, not ${hardwareLayers.length}`);
164
+ }
165
+ else if (hardwareLayers.length === 1) {
166
+ const theLayers = hardwareLayers[0];
167
+ const { formId } = theLayers;
168
+ for (let layer of theLayers.layer) {
169
+ this.compileHardwareLayerToKmap(sections, layer, sect, formId);
170
+ }
171
+ } // else: TODO-LDML do nothing if only touch layers
172
+ // Now load the reserved keys and slip them in here
173
+ const reservedKeys = this.getReservedKeys(sections);
174
+ for (const key of reservedKeys.values()) {
175
+ sect.keys.push(key);
176
+ }
177
+ return sect;
178
+ }
179
+ /** list of reserved keys, for tests */
180
+ static reserved_keys = [reserved_gap];
181
+ /** count of reserved keys, for tests */
182
+ static reserved_count = KeysCompiler.reserved_keys.length;
183
+ /** load up all reserved keys */
184
+ getReservedKeys(sections) {
185
+ const r = new Map();
186
+ // set up some constants..
187
+ const no_string = sections.strs.allocString('');
188
+ const no_list = sections.list.allocList([], {}, sections);
189
+ // now add the reserved key(s).
190
+ r.set(reserved_gap, {
191
+ flags: constants.keys_key_flags_gap | constants.keys_key_flags_extend,
192
+ id: sections.strs.allocString(reserved_gap),
193
+ flicks: '',
194
+ longPress: no_list,
195
+ longPressDefault: no_string,
196
+ multiTap: no_list,
197
+ switch: no_string,
198
+ to: no_string,
199
+ width: 10.0, // 10 * .1
200
+ });
201
+ if (r.size !== KeysCompiler.reserved_count) {
202
+ throw Error(`Internal Error: KeysCompiler.reserved_count=${KeysCompiler.reserved_count} != ${r.size} actual reserved keys.`);
203
+ }
204
+ return r;
205
+ }
206
+ static addUsedGestureKeys(layerKeyIds, keyBag, usedKeys) {
207
+ for (let keyId of layerKeyIds) {
208
+ const key = keyBag.get(keyId);
209
+ if (!key)
210
+ continue;
211
+ for (let gestureKeyId of allUsedKeyIdsInKey(key).keys()) {
212
+ usedKeys.add(gestureKeyId);
213
+ }
214
+ }
215
+ }
216
+ /**
217
+ *
218
+ * @param keyBag the keybag as a hash
219
+ * @param layerKeyIds list of keys from the layer, to extract used flicks
220
+ * @param usedKeys will be populated with keys used in the flick
221
+ */
222
+ loadFlicks(sections, sect, keyBag, layerKeyIds, usedKeys) {
223
+ const flickHash = this.getFlicks(); // for easier lookup
224
+ const usedFlicks = KeysCompiler.getUsedFlicks(layerKeyIds, keyBag);
225
+ // only include used flicks in the table
226
+ // this way, extra unused imported flicks are ignored
227
+ // in id order, for now
228
+ for (let flickId of Array.from(usedFlicks.values()).sort()) {
229
+ const flick = flickHash.get(flickId);
230
+ if (!flick)
231
+ continue; // already reported by validate()
232
+ // allocate the in-memory <flick id=…>
233
+ let flicks = new KeysFlicks(sections.strs.allocString(flickId));
234
+ // add data from each segment
235
+ for (let { keyId, directions } of flick.flickSegment) {
236
+ const keyIdStr = sections.strs.allocString(keyId);
237
+ let directionsList = sections.list.allocListFromSpaces(directions, {}, sections);
238
+ flicks.flicks.push({
239
+ directions: directionsList,
240
+ keyId: keyIdStr,
241
+ });
242
+ usedKeys.add(keyId);
243
+ }
244
+ sect.flicks.push(flicks);
245
+ }
246
+ }
247
+ static getUsedFlicks(layerKeyIds, keyBag) {
248
+ const usedFlicks = new Set();
249
+ for (let keyId of layerKeyIds) {
250
+ const key = keyBag.get(keyId);
251
+ if (!key?.flickId)
252
+ continue;
253
+ usedFlicks.add(key.flickId);
254
+ }
255
+ return usedFlicks;
256
+ }
257
+ loadKeys(sections, sect, keyBag, layerKeyIds, usedKeys) {
258
+ // for each used key (whether from layer, gesture, etc.)
259
+ // push these in id order, for tidiness
260
+ for (let keyId of Array.from(usedKeys.values()).sort()) {
261
+ const key = keyBag.get(keyId);
262
+ if (!key)
263
+ continue; // missing key
264
+ let flags = 0;
265
+ const { flickId, gap, longPressDefaultKeyId, longPressKeyIds, multiTapKeyIds, layerId, output } = key;
266
+ if (!!gap) {
267
+ flags |= constants.keys_key_flags_gap;
268
+ }
269
+ const id = sections.strs.allocString(key.id);
270
+ const longPress = sections.list.allocListFromSpaces(longPressKeyIds, {}, sections);
271
+ const longPressDefault = sections.strs.allocString(longPressDefaultKeyId, {}, sections);
272
+ const multiTap = sections.list.allocListFromSpaces(multiTapKeyIds, {}, sections);
273
+ const keySwitch = sections.strs.allocString(layerId); // 'switch' is a reserved word
274
+ const toRaw = output;
275
+ let toCooked = sections.vars.substituteStrings(toRaw, sections);
276
+ toCooked = sections.vars.substituteMarkerString(toCooked);
277
+ const to = sections.strs.allocString(toCooked, {
278
+ stringVariables: true,
279
+ markers: true,
280
+ unescape: true,
281
+ singleOk: true,
282
+ nfd: true,
283
+ }, sections);
284
+ if (!to.isOneChar) {
285
+ flags |= constants.keys_key_flags_extend;
286
+ }
287
+ const width = Math.ceil((key.width || 1) * 10.0); // default, width=1
288
+ sect.keys.push({
289
+ flags,
290
+ flicks: flickId,
291
+ id,
292
+ longPress,
293
+ longPressDefault,
294
+ multiTap,
295
+ switch: keySwitch,
296
+ to,
297
+ width,
298
+ });
299
+ }
300
+ }
301
+ getKeymapFromForm(hardware, badScans) {
302
+ return KeysCompiler.getKeymapFromForms(this.keyboard3?.forms.form, hardware, badScans);
303
+ }
304
+ static getKeymapFromForms(forms, hardware, badScans) {
305
+ // seach in reverse form because of overrides
306
+ const ldmlForm = [...forms].reverse().find((f) => f.id === hardware);
307
+ if (!ldmlForm) {
308
+ return undefined;
309
+ }
310
+ return KeysCompiler.getKeymapFromScancodes(ldmlForm, badScans);
311
+ }
312
+ static getKeymapFromScancodes(ldmlForm, badScans) {
313
+ const { scanCodes } = ldmlForm;
314
+ const ldmlScan = scanCodes.map(o => o.codes.split(" ").map(n => Number.parseInt(n, 16)));
315
+ const ldmlVkey = Constants.CLDRScanToKeyMap(ldmlScan, badScans);
316
+ return ldmlVkey;
317
+ }
318
+ /**
319
+ * Validate for purpose of kmap
320
+ * @param hardware the 'form' parameter
321
+ * @param layer
322
+ * @param keyHash the keybag's hash
323
+ * @returns true if valid
324
+ */
325
+ validateHardwareLayerForKmap(hardware, layer, keyHash) {
326
+ let valid = true;
327
+ const { modifiers } = layer;
328
+ if (!validModifier(modifiers)) {
329
+ this.callbacks.reportMessage(CompilerMessages.Error_InvalidModifier({ modifiers, layer: layer.id }));
330
+ valid = false;
331
+ }
332
+ const badScans = new Set();
333
+ const keymap = this.getKeymapFromForm(hardware, badScans);
334
+ if (!keymap) {
335
+ this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({ formId: hardware }));
336
+ valid = false;
337
+ return valid;
338
+ }
339
+ else if (badScans.size !== 0) {
340
+ const codes = Array.from(badScans.values()).map(n => Number(n).toString(16)).sort();
341
+ this.callbacks.reportMessage(CompilerMessages.Error_InvalidScanCode({ form: hardware, codes }));
342
+ valid = false;
343
+ return valid;
344
+ }
345
+ if (layer.row.length > keymap.length) {
346
+ this.callbacks.reportMessage(CompilerMessages.Error_HardwareLayerHasTooManyRows());
347
+ valid = false;
348
+ }
349
+ for (let y = 0; y < layer.row.length && y < keymap.length; y++) {
350
+ const keys = layer.row[y].keys.split(" ");
351
+ if (keys.length > keymap[y].length) {
352
+ this.callbacks.reportMessage(CompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({
353
+ row: y + 1,
354
+ hardware,
355
+ modifiers,
356
+ }));
357
+ valid = false;
358
+ }
359
+ let x = -1;
360
+ for (let key of keys) {
361
+ x++;
362
+ let keydef = keyHash.get(key);
363
+ if (!keydef) {
364
+ this.callbacks.reportMessage(CompilerMessages.Error_KeyNotFoundInKeyBag({
365
+ keyId: key,
366
+ col: x + 1,
367
+ row: y + 1,
368
+ layer: layer.id,
369
+ form: "hardware",
370
+ }));
371
+ valid = false;
372
+ continue;
373
+ }
374
+ if (!keydef.output && !keydef.gap && !keydef.layerId) {
375
+ this.callbacks.reportMessage(CompilerMessages.Error_KeyMissingToGapOrSwitch({ keyId: key }));
376
+ valid = false;
377
+ continue;
378
+ }
379
+ }
380
+ }
381
+ return valid;
382
+ }
383
+ compileHardwareLayerToKmap(sections, layer, sect, hardware) {
384
+ const mods = translateLayerAttrToModifier(layer);
385
+ const keymap = this.getKeymapFromForm(hardware);
386
+ // Iterate over rows (y) and cols (x) of the scancodes table.
387
+ // Any assigned keys will be used until we run out of keys in each row,
388
+ // and run out of rows. The rest will be reserved_gap.
389
+ for (let y = 0; y < keymap.length; y++) {
390
+ let keys;
391
+ // if there are keys, use them.
392
+ if (y < layer.row.length) {
393
+ const row = layer.row[y];
394
+ keys = row.keys.split(" ");
395
+ }
396
+ else {
397
+ keys = [];
398
+ }
399
+ // all columns in this row
400
+ for (let x = 0; x < keymap[y].length; x++) {
401
+ const vkey = keymap[y][x]; // from the scan table
402
+ let key = reserved_gap; // unless there's a key in this row
403
+ if (x < keys.length) {
404
+ key = keys[x];
405
+ }
406
+ // push every combination
407
+ for (const mod of mods) {
408
+ sect.kmap.push({
409
+ vkey,
410
+ mod,
411
+ key, // key id, to be changed into key index at finalization
412
+ });
413
+ }
414
+ }
415
+ }
416
+ return sect;
417
+ }
418
+ }
416
419
  //# sourceMappingURL=keys.js.map
420
+ //# debugId=ce44ab4e-cf0b-5e01-b83e-b8818380b183