@keymanapp/common-types
Version:
Keyman Developer keyboard file types
841 lines (839 loc) • 26 kB
JavaScript
!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]="ad78b0e3-8501-5143-b520-5ff321c70a34")}catch(e){}}();
import { constants } from '@keymanapp/ldml-keyboard-constants';
import * as r from 'restructure';
import { ElementString } from './element-string.js';
import { ListItem } from '../../ldml-keyboard/string-list.js';
import * as util from '../../util/util.js';
import * as KMX from '../kmx.js';
import { VariableParser } from '../../ldml-keyboard/pattern-parser.js';
import { MarkerParser } from '../../ldml-keyboard/pattern-parser.js';
var isOneChar = util.isOneChar;
var toOneChar = util.toOneChar;
var unescapeString = util.unescapeString;
var escapeStringForRegex = util.escapeStringForRegex;
var KMXFile = KMX.KMXFile;
// Implementation of file structures from /core/src/ldml/C7043_ldml.md
// Writer in kmx-builder.ts
// Reader in kmx-loader.ts
export class Section {
}
// 'sect'
export class Sect extends Section {
}
;
// 'bksp' -- see 'tran'
// 'elem'
export class Elem extends Section {
strings = [];
constructor(sections) {
super();
this.strings.push(ElementString.fromStrings(sections, '')); // C7043: null element string
}
/**
* @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem.
* If it is a string, will be interpreted per reorder element ruls.
*/
allocElementString(sections, source, order, tertiary, tertiary_base, prebase) {
let s = ElementString.fromStrings(sections, source, order, tertiary, tertiary_base, prebase);
if (!s)
return s;
let result = this.strings.find(item => item.isEqual(s));
if (result === undefined) {
result = s;
this.strings.push(result);
}
return result;
}
}
;
// 'keys' is now `keys2.kmap`
// 'loca'
export class Loca extends Section {
locales = [];
}
;
// 'meta'
export var KeyboardSettings;
(function (KeyboardSettings) {
KeyboardSettings[KeyboardSettings["none"] = 0] = "none";
KeyboardSettings[KeyboardSettings["normalizationDisabled"] = constants.meta_settings_normalization_disabled] = "normalizationDisabled";
})(KeyboardSettings || (KeyboardSettings = {}));
;
export class Meta extends Section {
author;
conform;
layout;
name;
indicator;
version; // semver version string, defaults to "0"
settings;
/** convenience for checking settings */
get normalizationDisabled() {
return this?.settings & KeyboardSettings.normalizationDisabled;
}
}
;
// 'strs'
/**
* A string item in memory. This will be replaced with an index
* into the string table at finalization.
*/
export class StrsItem {
/** string value */
value;
/** char value if this is a single-char placeholder item (CharStrsItem) */
char;
constructor(value, char) {
if (char !== undefined) {
if (!isOneChar(value)) {
throw new Error(`StrsItem: ${value} is not a single char`);
}
if (char !== toOneChar(value)) {
throw new Error(`StrsItem: ${char} is not the right codepoint for ${value}`);
}
}
this.value = value;
this.char = char;
}
compareTo(o) {
return StrsItem.binaryStringCompare(this.value, o.value);
}
static binaryStringCompare(a, b) {
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islessthan
if (typeof a != 'string' || typeof b != 'string') {
throw new Error('binaryStringCompare: inputs must be strings');
}
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}
get isOneChar() {
return this.char !== undefined;
}
isEqual(a) {
return a.value === this.value && a.char === this.char;
}
}
;
/**
* A StrsItem for a single char. Used as a placeholder and hint to the builder
*/
export class CharStrsItem extends StrsItem {
constructor(value) {
if (!isOneChar(value)) {
throw RangeError(`not a 1-char string`);
}
super(value, toOneChar(value));
}
}
;
;
export class Strs extends Section {
/** the in-memory string table */
strings = [new StrsItem('')]; // C7043: The null string is always required
/** for validating */
allProcessedStrings = new Set();
/**
* Allocate a StrsItem given the string, unescaping if necessary.
* @param s escaped string
* @param opts options for allocation
* @param sections other sections, if needed
* @returns StrsItem
*/
allocString(s, opts, sections) {
// Run the string processing pipeline
s = this.processString(s, opts, sections);
// if it's a single char, don't push it into the strs table
if (opts?.singleOk && isOneChar(s)) {
return new CharStrsItem(s);
}
// default: look to see if the string is already present
let result = this.strings.find(item => item.value === s);
if (result === undefined) {
// only add if not already present
result = new StrsItem(s);
this.strings.push(result);
}
return result;
}
/** process everything according to opts, and add the string to this.allProcessedStrings */
processString(s, opts, sections) {
s = s ?? '';
// type check everything else
if (typeof s !== 'string') {
throw new Error('alloc_string: s must be a string, undefined, or null.');
}
// substitute variables
if (opts?.stringVariables) {
s = sections.vars.substituteStrings(s, sections);
}
// substitute markers
if (opts?.markers) {
s = sections.vars.substituteMarkerString(s);
}
// unescape \u{…}
if (opts?.unescape) {
s = unescapeString(s);
}
if (s) {
// add all processed strings here, so that we catch denormalized strings in the input
this.allProcessedStrings.add(s);
}
// nfd
if (opts?.nfd) {
if (!sections?.meta?.normalizationDisabled) {
if (opts?.markers) {
s = MarkerParser.nfd_markers(s, false);
}
else {
s = s.normalize("NFD");
}
}
}
return s;
}
}
;
/**
* See LKVariables
*/
export class Vars extends Section {
totalCount() {
return this.strings.length + this.sets.length + this.usets.length;
}
markers;
strings = []; // ≠ StrsItem
sets = [];
usets = [];
/**
*
* @returns false if any invalid variables
*/
valid() {
for (const t of [this.sets, this.strings, this.usets]) {
for (const i of t) {
if (!i.valid()) {
return false;
}
}
}
return true;
}
// utilities for making use of Vars in other sections
substituteSets(str, sections) {
return str.replaceAll(VariableParser.SET_REFERENCE, (_entire, id) => {
const val = Vars.findVariable(this.sets, id);
if (val === null) {
// Should have been caught during validation.
throw Error(`Internal Error: reference to missing set variable ${id}`);
}
return val.value.value;
});
}
substituteUnicodeSets(value, sections) {
return value.replaceAll(VariableParser.SET_REFERENCE, (_entire, id) => {
const v = Vars.findVariable(this.usets, id);
if (v === null) {
// Should have been caught during validation.
throw Error(`Internal Error: reference to missing UnicodeSet variable ${id}`);
}
return v.value.value; // string value
});
}
substituteStrings(str, sections, forMatch) {
if (!str)
return str;
return str.replaceAll(VariableParser.STRING_REFERENCE, (_entire, id) => {
const val = this.findStringVariableValue(id);
if (val === null) {
// Should have been caught during validation.
throw Error(`Internal Error: reference to missing string variable ${id}`);
}
if (forMatch)
return escapeStringForRegex(val);
return val;
});
}
findStringVariableValue(id) {
return Vars.findVariable(this.strings, id)?.value?.value ?? null; // Unwrap: Variable, StrsItem
}
substituteSetRegex(str, sections) {
return str.replaceAll(VariableParser.SET_REFERENCE, (_entire, id) => {
// try as set
const set = Vars.findVariable(this.sets, id);
if (set !== null) {
const { items } = set;
const escapedStrings = items.map(v => escapeStringForRegex(v.value.value));
const inner = escapedStrings.join('|');
return `(?:${inner})`;
}
// try as unicodeset
const uset = Vars.findVariable(this.usets, id);
if (uset !== null) {
const { unicodeSet } = uset;
const inner = unicodeSet.ranges.map(([start, end]) => {
const s = String.fromCodePoint(start);
if (start === end) {
return s;
}
else {
const e = String.fromCodePoint(end);
return `${s}-${e}`;
}
}).join('');
return `[${inner}]`;
}
// else, missing
throw Error(`Internal Error: reference to missing set variable ${id}`);
});
}
/**
* Variable locator facility
* @param array
* @param id
* @returns
*/
static findVariable(array, id) {
const v = array.filter(e => e.id.value === id);
if (v.length === 0) {
return null;
}
else if (v.length !== 1) {
// Should have been caught during validation
throw Error(`Internal Error: Duplicate variable id ${id} crept into a variable list.`);
}
else {
return v[0];
}
}
substituteMarkerString(s, forMatch) {
return MarkerParser.toSentinelString(s, this.markers, forMatch);
}
}
;
/**
* Common base for variable sections
* See Variable
*/
export class VarsItem extends Section {
id;
value;
constructor(id, value, sections) {
super();
this.id = sections.strs.allocString(id);
this.value = sections.strs.allocString(value, { unescape: true });
}
valid() {
return true;
}
}
;
export class UnicodeSetItem extends VarsItem {
constructor(id, value, sections, usetparser) {
super(id, value, sections);
const needRanges = sections.usetparser.sizeUnicodeSet(value);
if (needRanges >= 0) {
this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges);
} // otherwise: error (was recorded via callback)
}
unicodeSet;
valid() {
return !!this.unicodeSet;
}
}
;
export class SetVarItem extends VarsItem {
constructor(id, value, sections, rawItems) {
super(id, value.join(' '), sections);
this.items = sections.elem.allocElementString(sections, value);
this.rawItems = rawItems;
}
// element string array
items;
// like items, but with unprocessed marker strings
rawItems;
valid() {
return !!this.items;
}
}
;
export class StringVarItem extends VarsItem {
constructor(id, value, sections) {
super(id, value, sections);
}
}
;
// 'tran'
export class TranTransform {
from;
to;
mapFrom; // var name
mapTo; // var name
}
export class TranGroup {
type; // tran_group_type_transform | tran_group_type_reorder
transforms = [];
reorders = [];
}
export class TranReorder {
elements;
before;
}
;
export class Tran extends Section {
groups = [];
get id() {
return constants.section.tran;
}
}
;
export class UsetItem {
uset;
str;
constructor(uset, str) {
this.uset = uset;
this.str = str;
}
compareTo(other) {
return this.str.compareTo(other.str);
}
}
;
export class Uset extends Section {
usets = [];
allocUset(set, sections) {
// match the same pattern
let result = this.usets.find(s => set.pattern == s.uset.pattern);
if (result === undefined) {
result = new UsetItem(set, sections.strs.allocString(set.pattern));
this.usets.push(result);
}
return result;
}
}
;
// alias type for 'bksp'
export class Bksp extends Tran {
get id() {
return constants.section.bksp;
}
}
;
// 'disp'
export class DispItem {
to;
id;
display;
}
;
export class Disp extends Section {
baseCharacter;
disps = [];
}
;
// 'layr'
/**
* In-memory `<layers>`
*/
export class LayrList {
hardware;
layers = [];
minDeviceWidth; // millimeters
}
;
/**
* In-memory `<layer>`
*/
export class LayrEntry {
id;
mod;
rows = [];
}
;
/**
* In-memory `<row>`
*/
export class LayrRow {
keys = [];
}
;
export class Layr extends Section {
lists = [];
}
;
export class KeysKeys {
flags;
flicks; // for in-memory only
id;
longPress;
longPressDefault;
multiTap;
switch;
to;
width;
}
;
export class KeysKmap {
vkey;
mod;
key; // for in-memory only
}
;
export class KeysFlicks {
flicks = [];
id;
compareTo(b) {
return this.id.compareTo(b.id);
}
constructor(id) {
this.id = id;
}
}
;
export class KeysFlick {
directions;
keyId;
}
;
export class Keys extends Section {
keys = [];
flicks = [];
kmap = [];
constructor(strs) {
super();
let nullFlicks = new KeysFlicks(strs.allocString(''));
this.flicks.push(nullFlicks); // C7043: null element string
}
}
;
export class List extends Section {
/**
* Allocate a list from a space-separated list of items.
* Note that passing undefined or null or `''` will
* end up being the same as the empty list `[]`
* @param s space-separated list of items
* @param opts string options
* @param sections sections
* @returns a List object
*/
allocListFromSpaces(s, opts, sections) {
s = s ?? '';
return this.allocList(s.split(' '), opts, sections);
}
/**
* Return a List object referring to the string list.
* Note that a falsy list, or a list containing only an empty string
* `['']` will be stored as an empty list `[]`.
* @param strs Strs section for allocation
* @param s string list to allocate
* @returns
*/
allocList(s, opts, sections) {
// Special case the 'null' list for [] or ['']
if (!s || (s.length === 1 && s[0] === '')) {
return this.lists[0];
}
let result = this.lists.find(item => item.isEqual(s));
if (result === undefined) {
// allocate a new ListItem
result = ListItem.fromStrings(s, opts, sections);
this.lists.push(result);
}
return result;
}
constructor(strs) {
super();
this.lists.push(ListItem.fromStrings([], {}, { strs })); // C7043: null element string
}
lists = [];
}
;
export { ListItem as ListItem };
;
export class KMXPlusFile extends KMXFile {
/* KMXPlus file structures */
COMP_PLUS_SECT_ITEM;
COMP_PLUS_SECT;
// COMP_PLUS_BKSP == COMP_PLUS_TRAN
COMP_PLUS_BKSP_ITEM;
COMP_PLUS_BKSP;
COMP_PLUS_DISP_ITEM;
COMP_PLUS_DISP;
COMP_PLUS_ELEM_ELEMENT;
COMP_PLUS_ELEM_STRING;
COMP_PLUS_ELEM;
// COMP_PLUS_KEYS is now COMP_PLUS_KEYS_KMAP
COMP_PLUS_LAYR_ENTRY;
COMP_PLUS_LAYR_KEY;
COMP_PLUS_LAYR_LIST;
COMP_PLUS_LAYR_ROW;
COMP_PLUS_LAYR;
COMP_PLUS_KEYS_FLICK;
COMP_PLUS_KEYS_FLICKS;
COMP_PLUS_KEYS_KEY;
COMP_PLUS_KEYS_KMAP;
COMP_PLUS_KEYS;
COMP_PLUS_LIST_LIST;
COMP_PLUS_LIST_INDEX;
COMP_PLUS_LIST;
COMP_PLUS_LOCA_ITEM;
COMP_PLUS_LOCA;
COMP_PLUS_META;
COMP_PLUS_STRS_ITEM;
COMP_PLUS_STRS;
COMP_PLUS_TRAN_GROUP;
COMP_PLUS_TRAN_TRANSFORM;
COMP_PLUS_TRAN_REORDER;
COMP_PLUS_TRAN;
COMP_PLUS_USET_USET;
COMP_PLUS_USET_RANGE;
COMP_PLUS_USET;
COMP_PLUS_VKEY_ITEM;
COMP_PLUS_VKEY;
COMP_PLUS_VARS;
COMP_PLUS_VARS_ITEM;
/* File in-memory data */
kmxplus = {};
constructor() {
super();
// Binary-correct structures matching kmx_plus.h
// helpers
const STR_REF = r.uint32le;
const ELEM_REF = r.uint32le;
const LIST_REF = r.uint32le;
const STR_OR_CHAR32 = r.uint32le;
const CHAR32 = r.uint32le;
const STR_OR_CHAR32_OR_USET = r.uint32le;
const IDENT = r.uint32le;
// 'sect'
this.COMP_PLUS_SECT_ITEM = new r.Struct({
sect: r.uint32le,
offset: r.uint32le //? new r.VoidPointer(r.uint32le, {type: 'global'})
});
this.COMP_PLUS_SECT = new r.Struct({
ident: IDENT,
size: r.uint32le,
total: r.uint32le,
count: r.uint32le,
items: new r.Array(this.COMP_PLUS_SECT_ITEM, 'count')
});
// 'bksp' - see 'tran'
// 'disp'
this.COMP_PLUS_DISP_ITEM = new r.Struct({
to: STR_REF,
id: STR_REF,
display: STR_REF,
});
this.COMP_PLUS_DISP = new r.Struct({
ident: IDENT,
size: r.uint32le,
count: r.uint32le,
baseCharacter: CHAR32,
items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'),
});
// 'elem'
this.COMP_PLUS_ELEM_ELEMENT = new r.Struct({
element: STR_OR_CHAR32_OR_USET,
flags: r.uint32le
});
this.COMP_PLUS_ELEM_STRING = new r.Struct({
offset: r.uint32le,
length: r.uint32le
});
this.COMP_PLUS_ELEM = new r.Struct({
ident: IDENT,
size: r.uint32le,
count: r.uint32le,
strings: new r.Array(this.COMP_PLUS_ELEM_STRING, 'count')
// + variable subtable: Element data (see KMXPlusBuilder.emitElements())
});
// 'finl' - see 'tran'
// 'keys' - see 'keys.kmap'
// 'layr'
this.COMP_PLUS_LAYR_ENTRY = new r.Struct({
id: r.uint32le, // str
mod: r.uint32le, // bitfield
row: r.uint32le, // index into rows
count: r.uint32le,
});
this.COMP_PLUS_LAYR_KEY = new r.Struct({
key: r.uint32le, // str: key id
});
this.COMP_PLUS_LAYR_LIST = new r.Struct({
hardware: STR_REF, // str: hardware name
layer: r.uint32le, // index into layers
count: r.uint32le,
minDeviceWidth: r.uint32le, // integer: millimeters
});
this.COMP_PLUS_LAYR_ROW = new r.Struct({
key: r.uint32le,
count: r.uint32le,
});
this.COMP_PLUS_LAYR = new r.Struct({
ident: IDENT,
size: r.uint32le,
listCount: r.uint32le,
layerCount: r.uint32le,
rowCount: r.uint32le,
keyCount: r.uint32le,
lists: new r.Array(this.COMP_PLUS_LAYR_LIST, 'listCount'),
layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'),
rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'),
keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'),
});
this.COMP_PLUS_KEYS_FLICK = new r.Struct({
directions: LIST_REF, // list
to: STR_OR_CHAR32, // str | codepoint
});
this.COMP_PLUS_KEYS_FLICKS = new r.Struct({
count: r.uint32le,
flick: r.uint32le,
id: STR_REF, // str
});
this.COMP_PLUS_KEYS_KEY = new r.Struct({
to: STR_OR_CHAR32, // str | codepoint
flags: r.uint32le,
id: STR_REF, // str
switch: STR_REF, // str
width: r.uint32le, // width*10 ( 1 = 0.1 keys)
longPress: LIST_REF, // list index
longPressDefault: STR_REF, // str
multiTap: LIST_REF, // list index
flicks: r.uint32le, // index into flicks table
});
this.COMP_PLUS_KEYS_KMAP = new r.Struct({
vkey: r.uint32le,
mod: r.uint32le,
key: r.uint32le, // index into 'keys' subtable
});
this.COMP_PLUS_KEYS = new r.Struct({
ident: IDENT,
size: r.uint32le,
keyCount: r.uint32le,
flicksCount: r.uint32le,
flickCount: r.uint32le,
kmapCount: r.uint32le,
keys: new r.Array(this.COMP_PLUS_KEYS_KEY, 'keyCount'),
flicks: new r.Array(this.COMP_PLUS_KEYS_FLICKS, 'flicksCount'),
flick: new r.Array(this.COMP_PLUS_KEYS_FLICK, 'flickCount'),
kmap: new r.Array(this.COMP_PLUS_KEYS_KMAP, 'kmapCount'),
});
// 'list'
this.COMP_PLUS_LIST_LIST = new r.Struct({
index: r.uint32le,
count: r.uint32le,
});
this.COMP_PLUS_LIST_INDEX = new r.Struct({
str: STR_REF, // str
});
this.COMP_PLUS_LIST = new r.Struct({
ident: IDENT,
size: r.uint32le,
listCount: r.uint32le,
indexCount: r.uint32le,
lists: new r.Array(this.COMP_PLUS_LIST_LIST, 'listCount'),
indices: new r.Array(this.COMP_PLUS_LIST_INDEX, 'indexCount'),
});
// 'loca'
this.COMP_PLUS_LOCA_ITEM = r.uint32le; //str
this.COMP_PLUS_LOCA = new r.Struct({
ident: IDENT,
size: r.uint32le,
count: r.uint32le,
items: new r.Array(this.COMP_PLUS_LOCA_ITEM, 'count')
});
// 'meta'
this.COMP_PLUS_META = new r.Struct({
ident: IDENT,
size: r.uint32le,
author: STR_REF, //str
conform: STR_REF, //str
layout: STR_REF, //str
name: STR_REF, //str
indicator: STR_REF, //str
version: STR_REF, //str
settings: r.uint32le, //new r.Bitfield(r.uint32le, ['normalizationDisabled'])
});
// 'name' is gone
// 'ordr' now part of 'tran'
// 'strs'
this.COMP_PLUS_STRS_ITEM = new r.Struct({
// While we use length which is number of utf-16 code units excluding null terminator,
// we always write a null terminator, so we can get restructure to do that for us here
offset: r.uint32le, //? new r.Pointer(r.uint32le, new r.String(null, 'utf16le')),
length: r.uint32le
});
this.COMP_PLUS_STRS = new r.Struct({
ident: IDENT,
size: r.uint32le,
count: r.uint32le,
items: new r.Array(this.COMP_PLUS_STRS_ITEM, 'count')
// + variable subtable: String data (see KMXPlusBuilder.emitStrings())
});
// 'tran'
this.COMP_PLUS_TRAN_GROUP = new r.Struct({
type: r.uint32le, //type of group
count: r.uint32le, //number of items
index: r.uint32le, //index into subtable
});
this.COMP_PLUS_TRAN_TRANSFORM = new r.Struct({
from: STR_REF, //str
to: STR_REF, //str
mapFrom: ELEM_REF, //elem
mapTo: ELEM_REF //elem
});
this.COMP_PLUS_TRAN_REORDER = new r.Struct({
elements: ELEM_REF, //elem
before: ELEM_REF, //elem
});
this.COMP_PLUS_TRAN = new r.Struct({
ident: IDENT,
size: r.uint32le,
groupCount: r.uint32le,
transformCount: r.uint32le,
reorderCount: r.uint32le,
groups: new r.Array(this.COMP_PLUS_TRAN_GROUP, 'groupCount'),
transforms: new r.Array(this.COMP_PLUS_TRAN_TRANSFORM, 'transformCount'),
reorders: new r.Array(this.COMP_PLUS_TRAN_REORDER, 'reorderCount'),
});
// 'uset'
this.COMP_PLUS_USET_USET = new r.Struct({
range: r.uint32le,
count: r.uint32le,
pattern: STR_REF, // str
});
this.COMP_PLUS_USET_RANGE = new r.Struct({
start: CHAR32,
end: CHAR32,
});
this.COMP_PLUS_USET = new r.Struct({
ident: IDENT,
size: r.uint32le,
usetCount: r.uint32le,
rangeCount: r.uint32le,
usets: new r.Array(this.COMP_PLUS_USET_USET, 'usetCount'),
ranges: new r.Array(this.COMP_PLUS_USET_RANGE, 'rangeCount'),
});
// 'vars'
this.COMP_PLUS_VARS_ITEM = new r.Struct({
type: r.uint32le,
id: STR_REF, // str
value: STR_REF, // str
elem: ELEM_REF,
});
this.COMP_PLUS_VARS = new r.Struct({
ident: IDENT,
size: r.uint32le,
markers: LIST_REF,
varCount: r.uint32le,
varEntries: new r.Array(this.COMP_PLUS_VARS_ITEM, 'varCount'),
});
// 'vkey' is removed
// Aliases
this.COMP_PLUS_BKSP = this.COMP_PLUS_TRAN;
}
}
//# sourceMappingURL=kmx-plus.js.map
//# debugId=ad78b0e3-8501-5143-b520-5ff321c70a34