Merge pull request #7380 from keymanapp/feat/common/epic-ldml/7379-kvks-writer-for-ts

feat(common): kvks writer for TypeScript 🙀
This commit is contained in:
Marc Durdin 2022-09-30 08:13:50 +10:00 committed by GitHub
commit 7baa9f01cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 30 deletions

View file

@ -75,18 +75,27 @@
"$ref": "#/definitions/kvk-layer"
}
},
"name": {
"$ref": "#/definitions/kvk-encoding-name"
},
"fontname": {
"type": "string"
},
"fontsize": {
"type": "string"
"$": {
"type": "object",
"properties": {
"name": {
"$ref": "#/definitions/kvk-encoding-name"
},
"fontname": {
"type": "string"
},
"fontsize": {
"type": "string"
}
},
"required": [
"name"
],
"additionalProperties": false
}
},
"required": [
"name"
"$"
],
"additionalProperties": false
},
@ -99,12 +108,21 @@
"$ref": "#/definitions/kvk-key"
}
},
"shift": {
"$ref": "#/definitions/kvk-layer-shift"
"$": {
"type": "object",
"properties": {
"shift": {
"$ref": "#/definitions/kvk-layer-shift"
}
},
"required": [
"shift"
],
"additionalProperties": false
}
},
"required": [
"shift"
"$"
],
"additionalProperties": false
},
@ -114,15 +132,24 @@
"bitmap": {
"type": "string"
},
"vkey": {
"type": "string"
"$": {
"type": "object",
"properties": {
"vkey": {
"type": "string"
}
},
"required": [
"vkey"
],
"additionalProperties": false
},
"_": {
"type": "string"
}
},
"required": [
"vkey"
"$"
],
"additionalProperties": false
},

View file

@ -18,7 +18,7 @@ export default class KVKSFileReader {
const parser = new xml2js.Parser({
explicitArray: false,
mergeAttrs: true,
mergeAttrs: false,
includeWhiteChars: true,
normalize: false,
emptyTag: {} as any
@ -81,7 +81,8 @@ export default class KVKSFileReader {
flags: 0,
ansiFont: { name: "Arial", size: -12, color: 0xFF000008 }, // TODO-LDML: consider defaults
unicodeFont: { name: "Arial", size: -12, color: 0xFF000008 }, // TODO-LDML: consider defaults
associatedKeyboard: source.visualkeyboard?.header?.kbdname
associatedKeyboard: source.visualkeyboard?.header?.kbdname,
underlyingLayout: source.visualkeyboard?.header?.layout,
},
keys: []
};
@ -100,19 +101,19 @@ export default class KVKSFileReader {
}
for(let encoding of source.visualkeyboard.encoding) {
let isUnicode = (encoding.name == 'unicode'),
let isUnicode = (encoding.$?.name == 'unicode'),
font = isUnicode ? result.header.unicodeFont : result.header.ansiFont;
font.name = encoding.fontname;
font.size = parseInt(encoding.fontsize,10);
font.name = encoding.$?.fontname;
font.size = parseInt(encoding.$?.fontsize,10);
for(let layer of encoding.layer) {
let shift = this.kvksShiftToKvkShift(layer.shift);
let shift = this.kvksShiftToKvkShift(layer.$?.shift);
for(let sourceKey of layer.key) {
let vkey = (USVirtualKeyCodes as any)[sourceKey.vkey];
let vkey = (USVirtualKeyCodes as any)[sourceKey.$?.vkey];
if(!vkey) {
if(errors) {
let e = new KVKSParseError();
e.type = KVKSParseErrorType.invalidVkey;
e.vkey = sourceKey.vkey;
e.vkey = sourceKey.$?.vkey;
errors.push(e);
}
continue;

View file

@ -0,0 +1,113 @@
import * as xml2js from 'xml2js';
import KVKSourceFile, { KVKSEncoding, KVKSFlags, KVKSKey, KVKSLayer } from './kvks-file.js';
import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js';
import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js';
export default class KVKSFileWriter {
public write(vk: VisualKeyboard): string {
const builder = new xml2js.Builder({
allowSurrogateChars: true,
attrkey: '$',
charkey: '_',
xmldec: {
version: '1.0',
encoding: 'UTF-8',
standalone: true
}
})
let flags: KVKSFlags = {};
if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) {
flags.displayunderlying = '';
}
if(vk.header.flags & VisualKeyboardHeaderFlags.kvkh102) {
flags.key102 = '';
}
if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhAltGr) {
flags.usealtgr = '';
}
if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhUseUnderlying) {
flags.useunderlying = '';
}
let kvks: KVKSourceFile = {
visualkeyboard: {
header: {
version: '10.0',
kbdname: vk.header.associatedKeyboard,
flags: flags,
},
encoding: []
}
};
if(vk.header.underlyingLayout) kvks.visualkeyboard.header.layout = vk.header.underlyingLayout;
let encodings: {ansi: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}, unicode: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}} = {ansi:null,unicode:null};
for(let key of vk.keys) {
const encoding = key.flags & VisualKeyboardKeyFlags.kvkkUnicode ? 'unicode' : 'ansi';
const shift = this.kvkShiftToKvksShift(key.shift);
if(!encodings[encoding]) {
encodings[encoding] = {
o: {
layer: [],
$: {name: encoding,
fontname: encoding == 'ansi' ? vk.header.ansiFont.name : vk.header.unicodeFont.name,
fontsize: (encoding == 'ansi' ? vk.header.ansiFont.size : vk.header.unicodeFont.size).toString(),
},
},
l: {}
};
kvks.visualkeyboard.encoding.push(encodings[encoding].o);
}
let e = encodings[encoding];
if(!e.l[shift]) {
e.l[shift] = {
key: [],
$: {shift: shift},
};
e.o.layer.push(e.l[shift]);
}
let l = e.l[shift];
// TODO-LDML: map
let vkeyName = '';
for(let vkey of Object.keys(USVirtualKeyCodes)) {
if((USVirtualKeyCodes as any)[vkey] == key.vkey) {
vkeyName = vkey;
break;
}
}
if(vkeyName == '') {
//TODO-LDML: warn
continue;
}
let k: KVKSKey = {
$: {vkey: vkeyName},
_: key.text,
}
l.key.push(k);
}
// console.dir(kvks, {depth:8});
let result = builder.buildObject(kvks);
return result; //Uint8Array.from(result);
}
public kvkShiftToKvksShift(shift: VisualKeyboardShiftState): string {
// TODO-LDML(lowpri): make a map of this?
for(let state of VisualKeyboardLegalShiftStates) {
if(state.shift == shift) {
return state.name;
}
}
return '';
}
}

View file

@ -17,7 +17,6 @@ export interface KVKSHeader {
version?: string;
kbdname?: string;
flags?: KVKSFlags;
keybitmap?: string;
layout?: string;
};
@ -29,19 +28,25 @@ export interface KVKSFlags {
};
export interface KVKSEncoding {
name?: string;
fontname?: string;
fontsize?: string;
$?: {
name?: string;
fontname?: string;
fontsize?: string;
};
layer?: KVKSLayer[];
};
export interface KVKSLayer {
shift?: string;
$?: {
shift?: string;
};
key?: KVKSKey[];
};
export interface KVKSKey {
vkey?: string;
$?: {
vkey?: string;
}
bitmap?: string;
_?: string;
};

View file

@ -19,6 +19,7 @@ export class VisualKeyboardHeader {
associatedKeyboard?: string;
ansiFont: VisualKeyboardFont; // generally unused
unicodeFont: VisualKeyboardFont;
underlyingLayout?: string;
};
export class VisualKeyboardFont {

View file

@ -2,11 +2,12 @@ import * as fs from 'fs';
import 'mocha';
import { loadKvksJsonSchema, makePathToFixture } from '../helpers/index.js';
import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js";
import KvksFileWriter from "../../src/kvk/kvks-file-writer.js";
import { verify_khmer_angkor } from './test-kvk-utils.js';
import { assert } from 'chai';
describe('kvks-file-reader', function() {
it('kvks-file-reader should read a valid file', function() {
it('should read a valid file', function() {
const path = makePathToFixture('kvk', 'khmer_angkor.kvks');
const input = fs.readFileSync(path);
@ -21,3 +22,24 @@ describe('kvks-file-reader', function() {
verify_khmer_angkor(vk);
});
});
describe('kvks-file-writer', function() {
it('should write a valid file', function() {
const path = makePathToFixture('kvk', 'khmer_angkor.kvks');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
const kvksExpected = reader.read(input);
const errors: KVKSParseError[] = [];
const vk = reader.transform(kvksExpected, errors);
assert.isEmpty(errors);
const writer = new KvksFileWriter();
const output = writer.write(vk);
// We compare the (re)loaded data, because there may be
// minor, irrelevant formatting differences in the emitted xml
const kvks = reader.read(Buffer.from(output, 'utf8'));
assert.deepEqual(kvks, kvksExpected);
});
});