diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index 5e2a9990d18..cbaf2cad379 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -3,7 +3,7 @@ * * Compiles a LDML XML keyboard file into a Keyman KMXPlus file */ -import { KMXPlus, UnicodeSetParser, KvkFileWriter } from '@keymanapp/common-types'; +import { KMXPlus, UnicodeSetParser, KvkFileWriter, KMX } from '@keymanapp/common-types'; import { CompilerCallbacks, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts, defaultCompilerOptions, LDMLKeyboardXMLSourceFileReader, LDMLKeyboard, @@ -149,13 +149,27 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { KMXPlusMetadataCompiler.addKmxMetadata(kmx.kmxplus, kmx.keyboard, compilerOptions); // Use the builder to generate the binary output file - const builder = new KMXBuilder(kmx, compilerOptions.saveDebug); - const kmx_binary = builder.compile(); + const kmxBuilder = new KMXBuilder(kmx, compilerOptions.saveDebug); + const keyboardId = this.callbacks.path.basename(outputFilename, '.kmx'); + const vkCompiler = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks); + const vkCompilerResult = vkCompiler.compile(kmx.kmxplus, keyboardId); + if(vkCompilerResult === null) { + return null; + } + const vkData = typeof vkCompilerResult == 'object' ? vkCompilerResult : null; - const vkcompiler = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks); - const vk = vkcompiler.compile(kmx.kmxplus, this.callbacks.path.basename(outputFilename, '.kmx')); - const writer = new KvkFileWriter(); - const kvk_binary = writer.write(vk); + if(vkData) { + kmx.keyboard.stores.push({ + dpName: '', + dpString: keyboardId + '.kvk', + dwSystemID: KMX.KMXFile.TSS_VISUALKEYBOARD + }); + } + + const kmxBinary = kmxBuilder.compile(); + + const kvkWriter = new KvkFileWriter(); + const kvkBinary = vkData ? kvkWriter.write(vkData) : null; // Note: we could have a step of generating source files here // KvksFileWriter()... @@ -168,11 +182,10 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { //KMW17.0: const encoder = new TextEncoder(); //KMW17.0: const kmw_binary = encoder.encode(kmw_string); - return { artifacts: { - kmx: { data: kmx_binary, filename: outputFilename }, - kvk: { data: kvk_binary, filename: outputFilename.replace(/\.kmx$/, '.kvk') }, + kmx: { data: kmxBinary, filename: outputFilename }, + kvk: kvkBinary ? { data: kvkBinary, filename: outputFilename.replace(/\.kmx$/, '.kvk') } : null, //KMW17.0: js: { data: kmw_binary, filename: outputFilename.replace(/\.kmx$/, '.js') }, } }; diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts index 92c364e4dc9..44b04182a3f 100644 --- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts @@ -6,7 +6,7 @@ import { VarsCompiler } from './vars.js'; import { LdmlCompilerMessages } from './ldml-compiler-messages.js'; /** - * Compiler for typrs that don't actually consume input XML + * Compiler for types that don't actually consume input XML */ export abstract class EmptyCompiler extends SectionCompiler { private _id: SectionIdent; diff --git a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts index 7fda122dd37..7783d42405c 100644 --- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts @@ -40,7 +40,15 @@ export class LdmlKeyboardVisualKeyboardCompiler { public constructor(private callbacks: CompilerCallbacks) { } - public compile(source: KMXPlus.KMXPlusData, keyboardId: string): VisualKeyboard.VisualKeyboard { + /** + * Generate a visual keyboard + * @param source Compiled KMX+ data; note that this is modified to add + * &VISUALKEYBOARD system store on success + * @param keyboardId Basename of keyboard, without file extension + * @returns Visual keyboard data on success, null on failure, or + * false if no VK was generated for this keyboard + */ + public compile(source: KMXPlus.KMXPlusData, keyboardId: string): VisualKeyboard.VisualKeyboard | boolean | null { let result = new VisualKeyboard.VisualKeyboard(); /* TODO-LDML: consider VisualKeyboardHeaderFlags.kvkhUseUnderlying kvkhDisplayUnderlying kvkhAltGr kvkh102 */ @@ -50,6 +58,8 @@ export class LdmlKeyboardVisualKeyboardCompiler { result.header.ansiFont = {...VisualKeyboard.DEFAULT_KVK_FONT}; result.header.unicodeFont = {...VisualKeyboard.DEFAULT_KVK_FONT}; + let hasVisualKeyboard = false; + for(let layersList of source.layr.lists) { const formId = layersList.hardware.value; if(formId == 'touch') { @@ -57,9 +67,23 @@ export class LdmlKeyboardVisualKeyboardCompiler { } for(let layer of layersList.layers) { - this.compileHardwareLayer(source, result, layer, formId); + const res = this.compileHardwareLayer(source, result, layer, formId); + if(res === false) { + // failed to compile the layer + return null; + } + if(res === null) { + // not a supported layer type, but not an error + continue; + } + hasVisualKeyboard = true; } } + + if(!hasVisualKeyboard) { + return false; + } + return result; } @@ -76,9 +100,10 @@ export class LdmlKeyboardVisualKeyboardCompiler { const shift = this.translateLayerModifiersToVisualKeyboardShift(layer.mod); if(shift === null) { // Caps (num, scroll) is not a supported shift state in .kvk - return; + return null; } + let result = true; let y = -1; for(let row of layer.rows) { y++; @@ -94,6 +119,7 @@ export class LdmlKeyboardVisualKeyboardCompiler { this.callbacks.reportMessage( LdmlCompilerMessages.Error_KeyNotFoundInKeyBag({ keyId: key.value, layer: layerId, row: y, col: x, form: hardware }) ); + result = false; } else { vk.keys.push({ flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, @@ -104,6 +130,7 @@ export class LdmlKeyboardVisualKeyboardCompiler { } } } + return result; } private getDisplayFromKey(keydef: KMXPlus.KeysKeys, source: KMXPlus.KMXPlusData) { diff --git a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts index 5435e32286a..f92868d5854 100644 --- a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import {assert} from 'chai'; import { stripIndent } from 'common-tags'; -import { KvkFileWriter, VisualKeyboard } from '@keymanapp/common-types'; +import { KMX, KvkFileWriter, VisualKeyboard } from '@keymanapp/common-types'; import hextobin from '@keymanapp/hextobin'; import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js'; @@ -39,9 +39,16 @@ describe('visual-keyboard-compiler', function() { let kmx = await k.compile(source); assert(kmx, 'k.compile should not have failed'); - const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, path.basename(inputFilename, '.xml')); + const keyboardId = path.basename(inputFilename, '.xml'); + + const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, keyboardId); checkMessages(); assert.isNotNull(vk, 'LdmlKeyboardVisualKeyboardCompiler.compile should not have returned null'); + assert.isNotNull(kmx.keyboard.stores.find(store => + store.dwSystemID == KMX.KMXFile.TSS_VISUALKEYBOARD && + store.dpString == keyboardId + '.kvk' + )); + assert(typeof vk == 'object'); // Use the builder to generate the binary output file const writer = new KvkFileWriter(); @@ -221,6 +228,7 @@ async function loadVisualKeyboardFromXml(xml: string, id: string) { assert(kmx, 'k.compile should not have failed'); const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, id); + assert(typeof vk == 'object'); assert.isEmpty(compilerTestCallbacks.messages); assert.isOk(vk);