mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-11 11:25:34 +00:00
refactor(developer): move KVK embed into embed-osk-kvk.ts
This commit is contained in:
parent
f50934bf69
commit
ce5a5bb2f1
4 changed files with 513 additions and 459 deletions
158
developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts
Normal file
158
developer/src/kmc-kmn/src/compiler/embed-osk/embed-osk-kvk.ts
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by mcdurdin on 2025-11-27
|
||||
*
|
||||
* Convert Keyman .kvks files to KMX+ format.
|
||||
*/
|
||||
import { KMXPlus, VisualKeyboard, translateLdmlModifiersToVisualKeyboardShift, visualKeyboardShiftToLayerName, ModifierKeyConstant, usVirtualKeyName, translateVisualKeyboardShiftToLdmlModifiers } from "@keymanapp/common-types";
|
||||
import { CompilerCallbacks, oskFontMagicToken } from "@keymanapp/developer-utils";
|
||||
import { KmnCompilerMessages } from "../kmn-compiler-messages.js";
|
||||
import { oskLayouts } from "./osk-layout.js";
|
||||
|
||||
type VirtualKey = number;
|
||||
type LayerBag = Map<VirtualKey, KMXPlus.KeysKeys>;
|
||||
|
||||
export class EmbedOskKvkInKmx {
|
||||
|
||||
constructor(private callbacks: CompilerCallbacks) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a .kvk file to the KMX+ format
|
||||
*/
|
||||
public transformVisualKeyboardToKmxPlus(kmx: KMXPlus.KMXPlusFile, vk: VisualKeyboard.VisualKeyboard): void {
|
||||
|
||||
// TODO-EMBED-OSK-IN-KMX: if(displayMap) {
|
||||
// // Remap using the osk-char-use-rewriter
|
||||
// Osk.remapVisualKeyboard(vk, displayMap);
|
||||
// }
|
||||
|
||||
const layerBags = this.buildLayerBags(vk, kmx.kmxplus.strs, kmx.kmxplus.keys);
|
||||
const form = this.buildForm(vk, layerBags, kmx.kmxplus.strs);
|
||||
kmx.kmxplus.layr.forms.push(form);
|
||||
|
||||
// For now, we only support dotted circle (U+25CC) as our base character
|
||||
kmx.kmxplus.disp.baseCharacter = kmx.kmxplus.strs.allocString('\u25cc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the layout of keys, with gaps for missing keys, for the given form --
|
||||
* either ANSI (US) or ISO (EU), which are the only two supported layouts in
|
||||
* .kvk
|
||||
*/
|
||||
private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map<number, LayerBag>, strs: KMXPlus.Strs) {
|
||||
const baseLayoutName = 'en-us'; // This is the only value we support for 19.0
|
||||
const formName: KMXPlus.LayrFormHardware =
|
||||
vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102
|
||||
? KMXPlus.LayrFormHardware.iso
|
||||
: KMXPlus.LayrFormHardware.us;
|
||||
|
||||
const form = new KMXPlus.LayrForm();
|
||||
|
||||
form.baseLayout = strs.allocString(baseLayoutName);
|
||||
form.flags = 0;
|
||||
|
||||
if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) {
|
||||
form.flags |= KMXPlus.LayrFormFlags.showBaseLayout;
|
||||
}
|
||||
|
||||
if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhAltGr) {
|
||||
form.flags |= KMXPlus.LayrFormFlags.chiralSeparate;
|
||||
}
|
||||
|
||||
// We will reserve space for the font facename to be rewritten, with a magic
|
||||
// token that the package compiler will search for; see kmp-compiler.ts.
|
||||
form.fontFaceName = strs.allocString(oskFontMagicToken);
|
||||
|
||||
// We only currently support 100% font size
|
||||
form.fontSizePct = 100;
|
||||
form.hardware = strs.allocString(formName);
|
||||
|
||||
// For hardware-style keyboards, device width is not relevant
|
||||
form.minDeviceWidth = 0;
|
||||
|
||||
layerBags.forEach((keys, modifier) => {
|
||||
const layr = new KMXPlus.LayrEntry();
|
||||
|
||||
// layr.id is not relevant for hardware keyboards, but we include it to
|
||||
// make it easier to debug. We can use the existing KVK shift string
|
||||
// generation, because we can only have KVK modifiers here, even though
|
||||
// the LDML modifiers spec supports other modifiers
|
||||
const vkShift = translateLdmlModifiersToVisualKeyboardShift(modifier);
|
||||
layr.id = strs.allocString(visualKeyboardShiftToLayerName(vkShift));
|
||||
layr.mod = modifier;
|
||||
|
||||
// fill the rows
|
||||
|
||||
for(const row of oskLayouts[formName]) {
|
||||
const layrRow = new KMXPlus.LayrRow();
|
||||
layr.rows.push(layrRow);
|
||||
for(const vk of row) {
|
||||
const key = keys.get(vk);
|
||||
layrRow.keys.push(strs.allocString(key?.id?.value ?? 'gap'));
|
||||
}
|
||||
}
|
||||
|
||||
form.layers.push(layr);
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the relevant keys from the visual keyboard, add them to the key
|
||||
* bag, and build a set of key bags, one for each layer in the visual
|
||||
* keyboard.
|
||||
*/
|
||||
private buildLayerBags(vk: VisualKeyboard.VisualKeyboard, strs: KMXPlus.Strs, keys: KMXPlus.Keys) {
|
||||
const layerBags = new Map<ModifierKeyConstant, LayerBag>();
|
||||
|
||||
let hasHintedAboutNonUnicode = false;
|
||||
|
||||
for (const key of vk.keys) {
|
||||
const keyId = visualKeyboardShiftToLayerName(key.shift) + '-' + (usVirtualKeyName(key.vkey) ?? ('Unknown_'+key.vkey.toString()));
|
||||
|
||||
if(!(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode)) {
|
||||
if(!hasHintedAboutNonUnicode) {
|
||||
this.callbacks.reportMessage(KmnCompilerMessages.Hint_EmbeddedOskDoesNotSupportNonUnicode({keyId}));
|
||||
hasHintedAboutNonUnicode = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap) {
|
||||
this.callbacks.reportMessage(KmnCompilerMessages.Warn_EmbeddedOskDoesNotSupportBitmaps({keyId}));
|
||||
continue;
|
||||
}
|
||||
|
||||
const keykey: KMXPlus.KeysKeys = {
|
||||
id: strs.allocString(keyId),
|
||||
to: strs.allocString(key.text),
|
||||
flags: 0, // available flags are: gap, extend; neither needed
|
||||
flicks: "",
|
||||
longPress: null,
|
||||
longPressDefault: strs.allocString(),
|
||||
multiTap: null,
|
||||
switch: strs.allocString(),
|
||||
width: 100,
|
||||
};
|
||||
|
||||
const mod = translateVisualKeyboardShiftToLdmlModifiers(key.shift);
|
||||
if (!layerBags.has(mod)) {
|
||||
const bag = new Map<number, KMXPlus.KeysKeys>();
|
||||
layerBags.set(mod, bag);
|
||||
}
|
||||
|
||||
layerBags.get(mod).set(key.vkey, keykey);
|
||||
keys.keys.push(keykey);
|
||||
}
|
||||
return layerBags;
|
||||
}
|
||||
|
||||
public readonly unitTestEndpoints = {
|
||||
transformVisualKeyboardToKmxPlus: this.transformVisualKeyboardToKmxPlus.bind(this),
|
||||
buildForm: this.buildForm.bind(this),
|
||||
buildLayerBags: this.buildLayerBags.bind(this),
|
||||
};
|
||||
}
|
||||
|
|
@ -6,16 +6,14 @@
|
|||
* Convert Keyman .kvks and .keyman-touch-layout files to KMX+ format and embed
|
||||
* in .kmx.
|
||||
*/
|
||||
import { KMX, KMXPlus, ModifierKeyConstant, translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers, usVirtualKeyName, VisualKeyboard, visualKeyboardShiftToLayerName } from "@keymanapp/common-types";
|
||||
import { CompilerCallbacks, KMXPlusBuilder, oskFontMagicToken } from "@keymanapp/developer-utils";
|
||||
import { KMX, KMXPlus } from "@keymanapp/common-types";
|
||||
import { CompilerCallbacks, KMXPlusBuilder } from "@keymanapp/developer-utils";
|
||||
import { KMXPlusVersion } from "@keymanapp/ldml-keyboard-constants";
|
||||
import { KmnCompilerOptions } from "../compiler.js";
|
||||
import { PuaMap, loadKvkFile } from "../osk.js";
|
||||
import { oskLayouts } from "./osk-layout.js";
|
||||
import { KmnCompilerMessages } from "../kmn-compiler-messages.js";
|
||||
import { EmbedOskKvkInKmx } from "./embed-osk-kvk.js";
|
||||
|
||||
type VirtualKey = number;
|
||||
type LayerBag = Map<VirtualKey, KMXPlus.KeysKeys>;
|
||||
// import { EmbedOskTouchLayoutInKmx } from "./embed-osk-touch-layout.js";
|
||||
|
||||
export class EmbedOskInKmx {
|
||||
constructor(
|
||||
|
|
@ -24,6 +22,29 @@ export class EmbedOskInKmx {
|
|||
) {
|
||||
}
|
||||
|
||||
private createEmptyKmxPlusFile() {
|
||||
// TODO-EMBED-OSK-IN-KMX: merge this default construction with LDML compiler
|
||||
// start to write the ldml format
|
||||
const kmx = new KMXPlus.KMXPlusFile(KMXPlusVersion.Version19);
|
||||
const strs = kmx.kmxplus.strs = new KMXPlus.Strs();
|
||||
kmx.kmxplus.layr = new KMXPlus.Layr();
|
||||
kmx.kmxplus.elem = new KMXPlus.Elem(kmx.kmxplus);
|
||||
kmx.kmxplus.disp = new KMXPlus.Disp();
|
||||
kmx.kmxplus.keys = new KMXPlus.Keys(strs);
|
||||
// list?
|
||||
kmx.kmxplus.loca = new KMXPlus.Loca();
|
||||
kmx.kmxplus.meta = new KMXPlus.Meta();
|
||||
kmx.kmxplus.meta.author = strs.allocString();
|
||||
kmx.kmxplus.meta.conform = strs.allocString();
|
||||
kmx.kmxplus.meta.indicator = strs.allocString();
|
||||
kmx.kmxplus.meta.layout = strs.allocString();
|
||||
kmx.kmxplus.meta.name = strs.allocString();
|
||||
kmx.kmxplus.meta.settings = 0;
|
||||
kmx.kmxplus.meta.version = strs.allocString();
|
||||
|
||||
return kmx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take .kvks and .keyman-touch-layout files, merge them and build them as
|
||||
* KMX+ data, and embed into the provided KMX
|
||||
|
|
@ -35,13 +56,19 @@ export class EmbedOskInKmx {
|
|||
* @returns
|
||||
*/
|
||||
public embed(kmx: Uint8Array, kvksFilename: string, touchLayoutFilename: string, displayMap: PuaMap) {
|
||||
const vk = loadKvkFile(kvksFilename, this.callbacks);
|
||||
const kmxPlus = this.createEmptyKmxPlusFile();
|
||||
|
||||
const kmxPlus = this.transformVisualKeyboardToKmxPlus(vk);
|
||||
if(!kmxPlus) {
|
||||
return null;
|
||||
if(kvksFilename) {
|
||||
const embedKvk = new EmbedOskKvkInKmx(this.callbacks);
|
||||
const vk = loadKvkFile(kvksFilename, this.callbacks);
|
||||
if(!vk) {
|
||||
// error will have been reported by loadKvkFile
|
||||
return null;
|
||||
}
|
||||
embedKvk.transformVisualKeyboardToKmxPlus(kmxPlus, vk);
|
||||
}
|
||||
|
||||
|
||||
// TODO-EMBED-OSK-IN-KMX: touch layout to ldml
|
||||
// TODO-EMBED-OSK-IN-KMX: display map remapping
|
||||
|
||||
|
|
@ -51,159 +78,6 @@ export class EmbedOskInKmx {
|
|||
return this.injectKmxPlusIntoKmxFile(kmx, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a .kvk file to the KMX+ format
|
||||
*/
|
||||
private transformVisualKeyboardToKmxPlus(vk: VisualKeyboard.VisualKeyboard): KMXPlus.KMXPlusFile {
|
||||
|
||||
// TODO-EMBED-OSK-IN-KMX: if(displayMap) {
|
||||
// // Remap using the osk-char-use-rewriter
|
||||
// Osk.remapVisualKeyboard(vk, displayMap);
|
||||
// }
|
||||
|
||||
// TODO-EMBED-OSK-IN-KMX: merge this default construction with LDML compiler
|
||||
// start to write the ldml format
|
||||
const kmx = new KMXPlus.KMXPlusFile(KMXPlusVersion.Version19);
|
||||
const strs = kmx.kmxplus.strs = new KMXPlus.Strs();
|
||||
const layr = kmx.kmxplus.layr = new KMXPlus.Layr();
|
||||
kmx.kmxplus.elem = new KMXPlus.Elem(kmx.kmxplus);
|
||||
const disp = kmx.kmxplus.disp = new KMXPlus.Disp();
|
||||
const keys = kmx.kmxplus.keys = new KMXPlus.Keys(kmx.kmxplus.strs);
|
||||
// list?
|
||||
kmx.kmxplus.loca = new KMXPlus.Loca();
|
||||
kmx.kmxplus.meta = new KMXPlus.Meta();
|
||||
kmx.kmxplus.meta.author = strs.allocString();
|
||||
kmx.kmxplus.meta.conform = strs.allocString();
|
||||
kmx.kmxplus.meta.indicator = strs.allocString();
|
||||
kmx.kmxplus.meta.layout = strs.allocString();
|
||||
kmx.kmxplus.meta.name = strs.allocString();
|
||||
kmx.kmxplus.meta.settings = 0;
|
||||
kmx.kmxplus.meta.version = strs.allocString();
|
||||
|
||||
const layerBags = this.buildLayerBags(vk, strs, keys);
|
||||
const form = this.buildForm(vk, layerBags, strs);
|
||||
layr.forms.push(form);
|
||||
|
||||
// For now, we only support dotted circle (U+25CC) as our base character
|
||||
disp.baseCharacter = strs.allocString('\u25cc');
|
||||
|
||||
return kmx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the layout of keys, with gaps for missing keys, for the given form --
|
||||
* either ANSI (US) or ISO (EU), which are the only two supported layouts in
|
||||
* .kvk
|
||||
*/
|
||||
private buildForm(vk: VisualKeyboard.VisualKeyboard, layerBags: Map<number, LayerBag>, strs: KMXPlus.Strs) {
|
||||
const baseLayoutName = 'en-us'; // This is the only value we support for 19.0
|
||||
const formName: KMXPlus.LayrFormHardware =
|
||||
vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102
|
||||
? KMXPlus.LayrFormHardware.iso
|
||||
: KMXPlus.LayrFormHardware.us;
|
||||
|
||||
const form = new KMXPlus.LayrForm();
|
||||
|
||||
form.baseLayout = strs.allocString(baseLayoutName);
|
||||
form.flags = 0;
|
||||
|
||||
if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) {
|
||||
form.flags |= KMXPlus.LayrFormFlags.showBaseLayout;
|
||||
}
|
||||
|
||||
if(vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkhAltGr) {
|
||||
form.flags |= KMXPlus.LayrFormFlags.chiralSeparate;
|
||||
}
|
||||
|
||||
// We will reserve space for the font facename to be rewritten, with a magic
|
||||
// token that the package compiler will search for; see kmp-compiler.ts.
|
||||
form.fontFaceName = strs.allocString(oskFontMagicToken);
|
||||
|
||||
// We only currently support 100% font size
|
||||
form.fontSizePct = 100;
|
||||
form.hardware = strs.allocString(formName);
|
||||
|
||||
// For hardware-style keyboards, device width is not relevant
|
||||
form.minDeviceWidth = 0;
|
||||
|
||||
layerBags.forEach((keys, modifier) => {
|
||||
const layr = new KMXPlus.LayrEntry();
|
||||
|
||||
// layr.id is not relevant for hardware keyboards, but we include it to
|
||||
// make it easier to debug. We can use the existing KVK shift string
|
||||
// generation, because we can only have KVK modifiers here, even though
|
||||
// the LDML modifiers spec supports other modifiers
|
||||
const vkShift = translateLdmlModifiersToVisualKeyboardShift(modifier);
|
||||
layr.id = strs.allocString(visualKeyboardShiftToLayerName(vkShift));
|
||||
layr.mod = modifier;
|
||||
|
||||
// fill the rows
|
||||
|
||||
for(const row of oskLayouts[formName]) {
|
||||
const layrRow = new KMXPlus.LayrRow();
|
||||
layr.rows.push(layrRow);
|
||||
for(const vk of row) {
|
||||
const key = keys.get(vk);
|
||||
layrRow.keys.push(strs.allocString(key?.id?.value ?? 'gap'));
|
||||
}
|
||||
}
|
||||
|
||||
form.layers.push(layr);
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the relevant keys from the visual keyboard, add them to the key
|
||||
* bag, and build a set of key bags, one for each layer in the visual
|
||||
* keyboard.
|
||||
*/
|
||||
private buildLayerBags(vk: VisualKeyboard.VisualKeyboard, strs: KMXPlus.Strs, keys: KMXPlus.Keys) {
|
||||
const layerBags = new Map<ModifierKeyConstant, LayerBag>();
|
||||
|
||||
let hasHintedAboutNonUnicode = false;
|
||||
|
||||
for (const key of vk.keys) {
|
||||
const keyId = visualKeyboardShiftToLayerName(key.shift) + '-' + (usVirtualKeyName(key.vkey) ?? ('Unknown_'+key.vkey.toString()));
|
||||
|
||||
if(!(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode)) {
|
||||
if(!hasHintedAboutNonUnicode) {
|
||||
this.callbacks.reportMessage(KmnCompilerMessages.Hint_EmbeddedOskDoesNotSupportNonUnicode({keyId}));
|
||||
hasHintedAboutNonUnicode = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(key.flags & VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap) {
|
||||
this.callbacks.reportMessage(KmnCompilerMessages.Warn_EmbeddedOskDoesNotSupportBitmaps({keyId}));
|
||||
continue;
|
||||
}
|
||||
|
||||
const keykey: KMXPlus.KeysKeys = {
|
||||
id: strs.allocString(keyId),
|
||||
to: strs.allocString(key.text),
|
||||
flags: 0, // available flags are: gap, extend; neither needed
|
||||
flicks: "",
|
||||
longPress: null,
|
||||
longPressDefault: strs.allocString(),
|
||||
multiTap: null,
|
||||
switch: strs.allocString(),
|
||||
width: 100,
|
||||
};
|
||||
|
||||
const mod = translateVisualKeyboardShiftToLdmlModifiers(key.shift);
|
||||
if (!layerBags.has(mod)) {
|
||||
const bag = new Map<number, KMXPlus.KeysKeys>();
|
||||
layerBags.set(mod, bag);
|
||||
}
|
||||
|
||||
layerBags.get(mod).set(key.vkey, keykey);
|
||||
keys.keys.push(keykey);
|
||||
}
|
||||
return layerBags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an existing KMX file, which must have a version >= 19.0, and must
|
||||
* have space pre-allocated for the KMX+ header, and injects the prebuilt KMX+
|
||||
|
|
@ -263,10 +137,8 @@ export class EmbedOskInKmx {
|
|||
}
|
||||
|
||||
public readonly unitTestEndpoints = {
|
||||
transformVisualKeyboardToKmxPlus: this.transformVisualKeyboardToKmxPlus.bind(this),
|
||||
buildForm: this.buildForm.bind(this),
|
||||
buildLayerBags: this.buildLayerBags.bind(this),
|
||||
injectKmxPlusIntoKmxFile: this.injectKmxPlusIntoKmxFile.bind(this),
|
||||
createEmptyKmxPlusFile: this.createEmptyKmxPlusFile.bind(this),
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
|||
315
developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts
Normal file
315
developer/src/kmc-kmn/test/embed-osk-kvk.tests.ts
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
import { KMX, KMXPlus, ModifierKeyConstant, USVirtualKeyCodes, VisualKeyboard } from '@keymanapp/common-types';
|
||||
import { KMXPlusBuilder, oskFontMagicToken } from '@keymanapp/developer-utils';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { KmnCompilerMessages } from '../src/main.js';
|
||||
import { EmbedOskInKmx } from '../src/compiler/embed-osk/embed-osk.js';
|
||||
import { loadKvkFile } from '../src/compiler/osk.js';
|
||||
import { EmbedOskKvkInKmx } from '../src/compiler/embed-osk/embed-osk-kvk.js';
|
||||
|
||||
// VK header is not used in all functions, e.g. buildLayerBags, so this is a
|
||||
// default header for those tests
|
||||
const NullVisualKeyboardHeader: VisualKeyboard.VisualKeyboardHeader = {
|
||||
flags: VisualKeyboard.VisualKeyboardHeaderFlags.kvkhNone,
|
||||
ansiFont: null,
|
||||
unicodeFont: null,
|
||||
};
|
||||
|
||||
describe('Compiler OSK Embedding', function() {
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
||||
this.beforeEach(function() {
|
||||
callbacks.clear();
|
||||
});
|
||||
|
||||
this.afterEach(function() {
|
||||
if(this.currentTest.isFailed()) {
|
||||
callbacks.printMessages();
|
||||
}
|
||||
});
|
||||
|
||||
describe('EmbedOskKvkInKmx', function() {
|
||||
const embedder = new EmbedOskKvkInKmx(callbacks);
|
||||
|
||||
describe('EmbedOskKvkInKmx.buildLayerBags', function() {
|
||||
it('should build a bag of layers from an in-memory .kvks structure', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_A,
|
||||
text: 'a',
|
||||
shift: 0,
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
},
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_B,
|
||||
text: 'B',
|
||||
shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT,
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
},
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_C,
|
||||
text: 'Ctrl+Shift+C',
|
||||
shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT,
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
|
||||
assert.lengthOf(strs.strings, 7);
|
||||
assert.equal(strs.strings[0].value, '');
|
||||
assert.equal(strs.strings[1].value, 'default-K_A');
|
||||
assert.equal(strs.strings[2].value, 'a');
|
||||
assert.equal(strs.strings[3].value, 'shift-K_B');
|
||||
assert.equal(strs.strings[4].value, 'B');
|
||||
assert.equal(strs.strings[5].value, 'shift-ctrl-K_C');
|
||||
assert.equal(strs.strings[6].value, 'Ctrl+Shift+C');
|
||||
|
||||
assert.lengthOf(keys.flicks, 1);
|
||||
assert.lengthOf(keys.flicks[0].flicks, 0);
|
||||
assert.equal(keys.flicks[0].id, strs.strings[0]);
|
||||
|
||||
assert.deepEqual(keys.keys, [
|
||||
{
|
||||
flags: 0,
|
||||
flicks: "",
|
||||
id: strs.strings[1],
|
||||
longPress: null,
|
||||
longPressDefault: strs.strings[0],
|
||||
multiTap: null,
|
||||
switch: strs.strings[0],
|
||||
to: strs.strings[2],
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
flags: 0,
|
||||
flicks: "",
|
||||
id: strs.strings[3],
|
||||
longPress: null,
|
||||
longPressDefault: strs.strings[0],
|
||||
multiTap: null,
|
||||
switch: strs.strings[0],
|
||||
to: strs.strings[4],
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
flags: 0,
|
||||
flicks: "",
|
||||
id: strs.strings[5],
|
||||
longPress: null,
|
||||
longPressDefault: strs.strings[0],
|
||||
multiTap: null,
|
||||
switch: strs.strings[0],
|
||||
to: strs.strings[6],
|
||||
width: 100
|
||||
},
|
||||
]);
|
||||
|
||||
assert.isArray(keys.kmap);
|
||||
assert.isEmpty(keys.kmap);
|
||||
|
||||
// bag will be a map of maps; this test has three layers with an unmodified base layer K_A, a shift+K_B, and Ctrl+Shift+C
|
||||
assert.isNotNull(bag);
|
||||
assert.equal(bag.size, 3);
|
||||
assert.isTrue(bag.has(0));
|
||||
assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG));
|
||||
assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG));
|
||||
|
||||
const defaultLayer = bag.get(0);
|
||||
assert.equal(defaultLayer.size, 1);
|
||||
|
||||
assert.isTrue(defaultLayer.has(USVirtualKeyCodes.K_A));
|
||||
const k_a = defaultLayer.get(USVirtualKeyCodes.K_A);
|
||||
assert.equal(k_a.flags, 0);
|
||||
assert.equal(k_a.flicks, "");
|
||||
assert.equal(k_a.id, strs.strings[1]);
|
||||
assert.equal(k_a.longPress, null);
|
||||
assert.equal(k_a.longPressDefault, strs.strings[0]);
|
||||
assert.equal(k_a.multiTap, null);
|
||||
assert.equal(k_a.switch, strs.strings[0]);
|
||||
assert.equal(k_a.to, strs.strings[2]);
|
||||
assert.equal(k_a.width, 100);
|
||||
|
||||
const shiftLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG);
|
||||
assert.equal(shiftLayer.size, 1);
|
||||
|
||||
assert.isTrue(shiftLayer.has(USVirtualKeyCodes.K_B));
|
||||
const k_b = shiftLayer.get(USVirtualKeyCodes.K_B);
|
||||
assert.equal(k_b.flags, 0);
|
||||
assert.equal(k_b.flicks, "");
|
||||
assert.equal(k_b.id, strs.strings[3]);
|
||||
assert.equal(k_b.longPress, null);
|
||||
assert.equal(k_b.longPressDefault, strs.strings[0]);
|
||||
assert.equal(k_b.multiTap, null);
|
||||
assert.equal(k_b.switch, strs.strings[0]);
|
||||
assert.equal(k_b.to, strs.strings[4]);
|
||||
assert.equal(k_b.width, 100);
|
||||
|
||||
const shiftCtrlLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG);
|
||||
assert.equal(shiftCtrlLayer.size, 1);
|
||||
|
||||
assert.isTrue(shiftCtrlLayer.has(USVirtualKeyCodes.K_C));
|
||||
const k_c = shiftCtrlLayer.get(USVirtualKeyCodes.K_C);
|
||||
assert.equal(k_c.flags, 0);
|
||||
assert.equal(k_c.flicks, "");
|
||||
assert.equal(k_c.id, strs.strings[5]);
|
||||
assert.equal(k_c.longPress, null);
|
||||
assert.equal(k_c.longPressDefault, strs.strings[0]);
|
||||
assert.equal(k_c.multiTap, null);
|
||||
assert.equal(k_c.switch, strs.strings[0]);
|
||||
assert.equal(k_c.to, strs.strings[6]);
|
||||
assert.equal(k_c.width, 100);
|
||||
|
||||
});
|
||||
|
||||
it('should emit WARN_EmbeddedOskDoesNotSupportBitmaps if a key with kvkkBitmap flag is found', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_A,
|
||||
shift: 0,
|
||||
// kvkkUnicode required because otherwise the key is ignored as 'ansi'
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap | VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
bitmap: new Uint8Array()
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
assert.isNotNull(bag);
|
||||
assert.equal(bag.size, 0);
|
||||
assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.WARN_EmbeddedOskDoesNotSupportBitmaps));
|
||||
});
|
||||
|
||||
it('should emit HINT_EmbeddedOskDoesNotSupportNonUnicode if a key without kvkkUnicode flag is found', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_A,
|
||||
shift: 0,
|
||||
// !kvkkUnicode
|
||||
flags: 0 as VisualKeyboard.VisualKeyboardKeyFlags,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
assert.isNotNull(bag);
|
||||
assert.equal(bag.size, 0);
|
||||
assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.HINT_EmbeddedOskDoesNotSupportNonUnicode));
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbedOskKvkInKmx.buildForm', function() {
|
||||
it('should a layout of keys from a layer bag, from an in-memory .kvks structure', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{ vkey: USVirtualKeyCodes.K_A, text: 'a', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, },
|
||||
{ vkey: USVirtualKeyCodes.K_B, text: 'b', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, },
|
||||
{ vkey: USVirtualKeyCodes.K_C, text: 'c', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, },
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const layerBags = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
assert.isNotNull(layerBags);
|
||||
const form = embedder.unitTestEndpoints.buildForm(vk, layerBags, strs);
|
||||
assert.isNotNull(form);
|
||||
assert.equal(form.baseLayout.value, 'en-us'); // For v19
|
||||
assert.equal(form.flags, 0);
|
||||
assert.equal(form.fontFaceName.value, oskFontMagicToken);
|
||||
assert.equal(form.fontSizePct, 100);
|
||||
assert.equal(form.hardware.value, 'us');
|
||||
assert.equal(form.minDeviceWidth, 0);
|
||||
|
||||
assert.lengthOf(form.layers, 1);
|
||||
assert.equal(form.layers[0].id.value, 'default');
|
||||
assert.equal(form.layers[0].mod, 0); // no modifiers
|
||||
assert.equal(form.layers[0].rows.length, 5);
|
||||
|
||||
assert.equal(form.layers[0].rows[0].keys.length, 13);
|
||||
assert.equal(form.layers[0].rows[1].keys.length, 13);
|
||||
assert.equal(form.layers[0].rows[2].keys.length, 11);
|
||||
assert.equal(form.layers[0].rows[3].keys.length, 10);
|
||||
assert.equal(form.layers[0].rows[4].keys.length, 1);
|
||||
|
||||
assert.deepEqual(form.layers[0].rows[0].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[1].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[2].keys.map(key => key.value), ['default-K_A','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[3].keys.map(key => key.value), ['gap','gap','default-K_C','gap','default-K_B','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[4].keys.map(key => key.value), ['gap']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbedOskKvkInKmx.transformVisualKeyboardToKmxPlus', function() {
|
||||
it('should transform a .kvks file into an KMX+ structure', async function() {
|
||||
const vk = loadKvkFile(makePathToFixture('embed-osk', 'khmer_angkor.kvks'), callbacks);
|
||||
assert.isNotNull(vk);
|
||||
|
||||
const kmxPlus = new EmbedOskInKmx(callbacks,{}).unitTestEndpoints.createEmptyKmxPlusFile();
|
||||
assert.isNotNull(kmxPlus);
|
||||
|
||||
embedder.unitTestEndpoints.transformVisualKeyboardToKmxPlus(kmxPlus, vk);
|
||||
|
||||
// Verify various aspects of the kmxPlus based on the source .kvks
|
||||
assert.equal(kmxPlus.kmxplus.keys.flicks.length, 1);
|
||||
|
||||
// number of <key>s in the .kvks = 186, vscode search
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys.length, 186);
|
||||
|
||||
// first key in the file is RA K_B ឞ
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[0].id.value, 'rightalt-K_B');
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[0].to.value, 'ឞ');
|
||||
|
||||
// last key in the file is Shift K_BKQUOTE »
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].id.value, 'shift-K_BKQUOTE');
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].to.value, '»');
|
||||
|
||||
// first layer is ralt
|
||||
// first key on the first row of the RALT layer should be RALT+BKQUOTE
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms.length, 1);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].baseLayout.value, 'en-us');
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].flags, KMXPlus.LayrFormFlags.chiralSeparate);
|
||||
// TODO-EMBED-OSK-IN-KMX: need to test showBaseLayout at some point
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].fontFaceName.value, oskFontMagicToken);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].fontSizePct, 100);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].hardware.value, 'us');
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].minDeviceWidth, 0);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers.length, 4);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].id.value, 'rightalt');
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].mod, KMX.KMXFile.RALTFLAG);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows.length, 5);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys.length, 13);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys[0].value, 'rightalt-K_BKQUOTE');
|
||||
|
||||
// Finally, pass the through KMXPlusBuilder, there should be no errors,
|
||||
// hints, or warnings for this file
|
||||
|
||||
const builder = new KMXPlusBuilder(kmxPlus);
|
||||
const data = builder.compile();
|
||||
|
||||
assert.isNotNull(data);
|
||||
assert.lengthOf(callbacks.messages, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,25 +6,13 @@ import { fileURLToPath } from 'node:url';
|
|||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
import { KMX, KMXPlus, ModifierKeyConstant, USVirtualKeyCodes, VisualKeyboard } from '@keymanapp/common-types';
|
||||
import { KMXPlusBuilder, oskFontMagicToken } from '@keymanapp/developer-utils';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { KmnCompiler, KmnCompilerMessages } from '../src/main.js';
|
||||
import { KMX } from '@keymanapp/common-types';
|
||||
import { KmnCompiler } from '../src/main.js';
|
||||
import { EmbedOskInKmx } from '../src/compiler/embed-osk/embed-osk.js';
|
||||
import { loadKvkFile } from '../src/compiler/osk.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/');
|
||||
const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/';
|
||||
|
||||
|
||||
// VK header is not used in all functions, e.g. buildLayerBags, so this is a
|
||||
// default header for those tests
|
||||
const NullVisualKeyboardHeader: VisualKeyboard.VisualKeyboardHeader = {
|
||||
flags: VisualKeyboard.VisualKeyboardHeaderFlags.kvkhNone,
|
||||
ansiFont: null,
|
||||
unicodeFont: null,
|
||||
};
|
||||
|
||||
describe('Compiler OSK Embedding', function() {
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
|
@ -261,285 +249,6 @@ describe('Compiler OSK Embedding', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('EmbedOskInKmx.buildLayerBags', function() {
|
||||
const embedder = new EmbedOskInKmx(callbacks, {});
|
||||
|
||||
it('should build a bag of layers from an in-memory .kvks structure', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_A,
|
||||
text: 'a',
|
||||
shift: 0,
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
},
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_B,
|
||||
text: 'B',
|
||||
shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT,
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
},
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_C,
|
||||
text: 'Ctrl+Shift+C',
|
||||
shift: VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT,
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
|
||||
assert.lengthOf(strs.strings, 7);
|
||||
assert.equal(strs.strings[0].value, '');
|
||||
assert.equal(strs.strings[1].value, 'default-K_A');
|
||||
assert.equal(strs.strings[2].value, 'a');
|
||||
assert.equal(strs.strings[3].value, 'shift-K_B');
|
||||
assert.equal(strs.strings[4].value, 'B');
|
||||
assert.equal(strs.strings[5].value, 'shift-ctrl-K_C');
|
||||
assert.equal(strs.strings[6].value, 'Ctrl+Shift+C');
|
||||
|
||||
assert.lengthOf(keys.flicks, 1);
|
||||
assert.lengthOf(keys.flicks[0].flicks, 0);
|
||||
assert.equal(keys.flicks[0].id, strs.strings[0]);
|
||||
|
||||
assert.deepEqual(keys.keys, [
|
||||
{
|
||||
flags: 0,
|
||||
flicks: "",
|
||||
id: strs.strings[1],
|
||||
longPress: null,
|
||||
longPressDefault: strs.strings[0],
|
||||
multiTap: null,
|
||||
switch: strs.strings[0],
|
||||
to: strs.strings[2],
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
flags: 0,
|
||||
flicks: "",
|
||||
id: strs.strings[3],
|
||||
longPress: null,
|
||||
longPressDefault: strs.strings[0],
|
||||
multiTap: null,
|
||||
switch: strs.strings[0],
|
||||
to: strs.strings[4],
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
flags: 0,
|
||||
flicks: "",
|
||||
id: strs.strings[5],
|
||||
longPress: null,
|
||||
longPressDefault: strs.strings[0],
|
||||
multiTap: null,
|
||||
switch: strs.strings[0],
|
||||
to: strs.strings[6],
|
||||
width: 100
|
||||
},
|
||||
]);
|
||||
|
||||
assert.isArray(keys.kmap);
|
||||
assert.isEmpty(keys.kmap);
|
||||
|
||||
// bag will be a map of maps; this test has three layers with an unmodified base layer K_A, a shift+K_B, and Ctrl+Shift+C
|
||||
assert.isNotNull(bag);
|
||||
assert.equal(bag.size, 3);
|
||||
assert.isTrue(bag.has(0));
|
||||
assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG));
|
||||
assert.isTrue(bag.has(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG));
|
||||
|
||||
const defaultLayer = bag.get(0);
|
||||
assert.equal(defaultLayer.size, 1);
|
||||
|
||||
assert.isTrue(defaultLayer.has(USVirtualKeyCodes.K_A));
|
||||
const k_a = defaultLayer.get(USVirtualKeyCodes.K_A);
|
||||
assert.equal(k_a.flags, 0);
|
||||
assert.equal(k_a.flicks, "");
|
||||
assert.equal(k_a.id, strs.strings[1]);
|
||||
assert.equal(k_a.longPress, null);
|
||||
assert.equal(k_a.longPressDefault, strs.strings[0]);
|
||||
assert.equal(k_a.multiTap, null);
|
||||
assert.equal(k_a.switch, strs.strings[0]);
|
||||
assert.equal(k_a.to, strs.strings[2]);
|
||||
assert.equal(k_a.width, 100);
|
||||
|
||||
const shiftLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG);
|
||||
assert.equal(shiftLayer.size, 1);
|
||||
|
||||
assert.isTrue(shiftLayer.has(USVirtualKeyCodes.K_B));
|
||||
const k_b = shiftLayer.get(USVirtualKeyCodes.K_B);
|
||||
assert.equal(k_b.flags, 0);
|
||||
assert.equal(k_b.flicks, "");
|
||||
assert.equal(k_b.id, strs.strings[3]);
|
||||
assert.equal(k_b.longPress, null);
|
||||
assert.equal(k_b.longPressDefault, strs.strings[0]);
|
||||
assert.equal(k_b.multiTap, null);
|
||||
assert.equal(k_b.switch, strs.strings[0]);
|
||||
assert.equal(k_b.to, strs.strings[4]);
|
||||
assert.equal(k_b.width, 100);
|
||||
|
||||
const shiftCtrlLayer = bag.get(ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_CTRLFLAG);
|
||||
assert.equal(shiftCtrlLayer.size, 1);
|
||||
|
||||
assert.isTrue(shiftCtrlLayer.has(USVirtualKeyCodes.K_C));
|
||||
const k_c = shiftCtrlLayer.get(USVirtualKeyCodes.K_C);
|
||||
assert.equal(k_c.flags, 0);
|
||||
assert.equal(k_c.flicks, "");
|
||||
assert.equal(k_c.id, strs.strings[5]);
|
||||
assert.equal(k_c.longPress, null);
|
||||
assert.equal(k_c.longPressDefault, strs.strings[0]);
|
||||
assert.equal(k_c.multiTap, null);
|
||||
assert.equal(k_c.switch, strs.strings[0]);
|
||||
assert.equal(k_c.to, strs.strings[6]);
|
||||
assert.equal(k_c.width, 100);
|
||||
|
||||
});
|
||||
|
||||
it('should emit WARN_EmbeddedOskDoesNotSupportBitmaps if a key with kvkkBitmap flag is found', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_A,
|
||||
shift: 0,
|
||||
// kvkkUnicode required because otherwise the key is ignored as 'ansi'
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkBitmap | VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
bitmap: new Uint8Array()
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
assert.isNotNull(bag);
|
||||
assert.equal(bag.size, 0);
|
||||
assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.WARN_EmbeddedOskDoesNotSupportBitmaps));
|
||||
});
|
||||
|
||||
it('should emit HINT_EmbeddedOskDoesNotSupportNonUnicode if a key without kvkkUnicode flag is found', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{
|
||||
vkey: USVirtualKeyCodes.K_A,
|
||||
shift: 0,
|
||||
// !kvkkUnicode
|
||||
flags: 0 as VisualKeyboard.VisualKeyboardKeyFlags,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const bag = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
assert.isNotNull(bag);
|
||||
assert.equal(bag.size, 0);
|
||||
assert.isTrue(callbacks.hasMessage(KmnCompilerMessages.HINT_EmbeddedOskDoesNotSupportNonUnicode));
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbedOskInKmx.buildForm', function() {
|
||||
const embedder = new EmbedOskInKmx(callbacks, {});
|
||||
|
||||
it('should a layout of keys from a layer bag, from an in-memory .kvks structure', async function() {
|
||||
const vk: VisualKeyboard.VisualKeyboard = {
|
||||
header: NullVisualKeyboardHeader,
|
||||
keys: [
|
||||
{ vkey: USVirtualKeyCodes.K_A, text: 'a', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, },
|
||||
{ vkey: USVirtualKeyCodes.K_B, text: 'b', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, },
|
||||
{ vkey: USVirtualKeyCodes.K_C, text: 'c', shift: 0, flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, },
|
||||
]
|
||||
};
|
||||
|
||||
const strs = new KMXPlus.Strs();
|
||||
const keys = new KMXPlus.Keys(strs);
|
||||
const layerBags = embedder.unitTestEndpoints.buildLayerBags(vk, strs, keys);
|
||||
assert.isNotNull(layerBags);
|
||||
const form = embedder.unitTestEndpoints.buildForm(vk, layerBags, strs);
|
||||
assert.isNotNull(form);
|
||||
assert.equal(form.baseLayout.value, 'en-us'); // For v19
|
||||
assert.equal(form.flags, 0);
|
||||
assert.equal(form.fontFaceName.value, oskFontMagicToken);
|
||||
assert.equal(form.fontSizePct, 100);
|
||||
assert.equal(form.hardware.value, 'us');
|
||||
assert.equal(form.minDeviceWidth, 0);
|
||||
|
||||
assert.lengthOf(form.layers, 1);
|
||||
assert.equal(form.layers[0].id.value, 'default');
|
||||
assert.equal(form.layers[0].mod, 0); // no modifiers
|
||||
assert.equal(form.layers[0].rows.length, 5);
|
||||
|
||||
assert.equal(form.layers[0].rows[0].keys.length, 13);
|
||||
assert.equal(form.layers[0].rows[1].keys.length, 13);
|
||||
assert.equal(form.layers[0].rows[2].keys.length, 11);
|
||||
assert.equal(form.layers[0].rows[3].keys.length, 10);
|
||||
assert.equal(form.layers[0].rows[4].keys.length, 1);
|
||||
|
||||
assert.deepEqual(form.layers[0].rows[0].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[1].keys.map(key => key.value), ['gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[2].keys.map(key => key.value), ['default-K_A','gap','gap','gap','gap','gap','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[3].keys.map(key => key.value), ['gap','gap','default-K_C','gap','default-K_B','gap','gap','gap','gap','gap']);
|
||||
assert.deepEqual(form.layers[0].rows[4].keys.map(key => key.value), ['gap']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbedOskInKmx.transformVisualKeyboardToKmxPlus', function() {
|
||||
const embedder = new EmbedOskInKmx(callbacks, {});
|
||||
|
||||
it('should transform a .kvks file into an KMX+ structure', async function() {
|
||||
const vk = loadKvkFile(makePathToFixture('embed-osk', 'khmer_angkor.kvks'), callbacks);
|
||||
assert.isNotNull(vk);
|
||||
|
||||
const kmxPlus = embedder.unitTestEndpoints.transformVisualKeyboardToKmxPlus(vk);
|
||||
assert.isNotNull(kmxPlus);
|
||||
|
||||
// Verify various aspects of the kmxPlus based on the source .kvks
|
||||
assert.equal(kmxPlus.kmxplus.keys.flicks.length, 1);
|
||||
|
||||
// number of <key>s in the .kvks = 186, vscode search
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys.length, 186);
|
||||
|
||||
// first key in the file is RA K_B ឞ
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[0].id.value, 'rightalt-K_B');
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[0].to.value, 'ឞ');
|
||||
|
||||
// last key in the file is Shift K_BKQUOTE »
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].id.value, 'shift-K_BKQUOTE');
|
||||
assert.equal(kmxPlus.kmxplus.keys.keys[kmxPlus.kmxplus.keys.keys.length-1].to.value, '»');
|
||||
|
||||
// first layer is ralt
|
||||
// first key on the first row of the RALT layer should be RALT+BKQUOTE
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms.length, 1);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].baseLayout.value, 'en-us');
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].flags, KMXPlus.LayrFormFlags.chiralSeparate);
|
||||
// TODO-EMBED-OSK-IN-KMX: need to test showBaseLayout at some point
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].fontFaceName.value, oskFontMagicToken);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].fontSizePct, 100);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].hardware.value, 'us');
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].minDeviceWidth, 0);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers.length, 4);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].id.value, 'rightalt');
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].mod, KMX.KMXFile.RALTFLAG);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows.length, 5);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys.length, 13);
|
||||
assert.equal(kmxPlus.kmxplus.layr.forms[0].layers[0].rows[0].keys[0].value, 'rightalt-K_BKQUOTE');
|
||||
|
||||
// Finally, pass the through KMXPlusBuilder, there should be no errors,
|
||||
// hints, or warnings for this file
|
||||
|
||||
const builder = new KMXPlusBuilder(kmxPlus);
|
||||
const data = builder.compile();
|
||||
|
||||
assert.isNotNull(data);
|
||||
assert.lengthOf(callbacks.messages, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbedOskInKmx.embed', function() {
|
||||
// const embedder = new EmbedOskInKmx(callbacks, {});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue