From 9093c0719b27d0d6fa126f6bf2586e0f7fb87d99 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 12 Feb 2024 08:47:47 +0700 Subject: [PATCH 001/262] change(web): preps for wordbreaker child projects --- common/models/wordbreakers/build.sh | 2 +- .../wordbreakers/src/{ => main}/ascii.ts | 0 .../src/{ => main}/default/data.ts | 0 .../src/{ => main}/default/index.ts | 0 .../wordbreakers/src/{ => main}/index.ts | 0 .../src/{ => main}/placeholder.ts | 0 .../wordbreakers/src/main/tsconfig.json | 19 +++++++++++++++++++ common/models/wordbreakers/tsconfig.json | 15 ++++----------- 8 files changed, 24 insertions(+), 12 deletions(-) rename common/models/wordbreakers/src/{ => main}/ascii.ts (100%) rename common/models/wordbreakers/src/{ => main}/default/data.ts (100%) rename common/models/wordbreakers/src/{ => main}/default/index.ts (100%) rename common/models/wordbreakers/src/{ => main}/index.ts (100%) rename common/models/wordbreakers/src/{ => main}/placeholder.ts (100%) create mode 100644 common/models/wordbreakers/src/main/tsconfig.json diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index fadd3501184..a2ff0d14dd1 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -34,7 +34,7 @@ function do_build() { tsc -b # Declaration bundling. - tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts + tsc -p src/main/tsconfig.json --emitDeclarationOnly --outFile ./build/lib/index.d.ts } function do_test() { diff --git a/common/models/wordbreakers/src/ascii.ts b/common/models/wordbreakers/src/main/ascii.ts similarity index 100% rename from common/models/wordbreakers/src/ascii.ts rename to common/models/wordbreakers/src/main/ascii.ts diff --git a/common/models/wordbreakers/src/default/data.ts b/common/models/wordbreakers/src/main/default/data.ts similarity index 100% rename from common/models/wordbreakers/src/default/data.ts rename to common/models/wordbreakers/src/main/default/data.ts diff --git a/common/models/wordbreakers/src/default/index.ts b/common/models/wordbreakers/src/main/default/index.ts similarity index 100% rename from common/models/wordbreakers/src/default/index.ts rename to common/models/wordbreakers/src/main/default/index.ts diff --git a/common/models/wordbreakers/src/index.ts b/common/models/wordbreakers/src/main/index.ts similarity index 100% rename from common/models/wordbreakers/src/index.ts rename to common/models/wordbreakers/src/main/index.ts diff --git a/common/models/wordbreakers/src/placeholder.ts b/common/models/wordbreakers/src/main/placeholder.ts similarity index 100% rename from common/models/wordbreakers/src/placeholder.ts rename to common/models/wordbreakers/src/main/placeholder.ts diff --git a/common/models/wordbreakers/src/main/tsconfig.json b/common/models/wordbreakers/src/main/tsconfig.json new file mode 100644 index 00000000000..ce66f0bbfc1 --- /dev/null +++ b/common/models/wordbreakers/src/main/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.kmw-worker-base.json", + + "compilerOptions": { + "baseUrl": "./", + "outDir": "../../build/obj", + "tsBuildInfoFile": "../../build/obj/tsconfig.tsbuildinfo", + "rootDir": "./" + }, + "references": [ + { "path": "../../../types" } + ], + "include": [ + "./**/*" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json index 54d12d5d8c7..57df26333b7 100644 --- a/common/models/wordbreakers/tsconfig.json +++ b/common/models/wordbreakers/tsconfig.json @@ -4,17 +4,10 @@ "compilerOptions": { "baseUrl": "./", "outDir": "build/obj", - "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo", - "rootDir": "./src" + "tsBuildInfoFile": "build/tsconfig.tsbuildinfo", + "rootDir": "./" }, "references": [ - { "path": "../types" } - ], - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "test/**/*.ts" + { "path": "./src/main" } ] -} +} \ No newline at end of file From 901861a75d22cf51bf2fb2f56753b079789bfa6f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 12 Feb 2024 09:39:31 +0700 Subject: [PATCH 002/262] feat(web): implements string-encoded prop-array search --- common/models/wordbreakers/package.json | 5 ++- .../src/data-compiler/tsconfig.json | 19 ++++++++ .../src/main/default/searchForProperty.ts | 44 +++++++++++++++++++ .../wordbreakers/src/main/test-index.ts | 6 +++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 common/models/wordbreakers/src/data-compiler/tsconfig.json create mode 100644 common/models/wordbreakers/src/main/default/searchForProperty.ts create mode 100644 common/models/wordbreakers/src/main/test-index.ts diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 6fe09dea628..ee9de24237a 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -24,7 +24,10 @@ "./lib": { "types": "./build/lib/index.d.ts" }, - "./obj/*.js": "./build/obj/*.js" + "./obj/*.js": "./build/obj/*.js", + "./test-index": { + "default": "./build/obj/test-index.js" + } }, "directories": { "lib": "lib", diff --git a/common/models/wordbreakers/src/data-compiler/tsconfig.json b/common/models/wordbreakers/src/data-compiler/tsconfig.json new file mode 100644 index 00000000000..ce66f0bbfc1 --- /dev/null +++ b/common/models/wordbreakers/src/data-compiler/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.kmw-worker-base.json", + + "compilerOptions": { + "baseUrl": "./", + "outDir": "../../build/obj", + "tsBuildInfoFile": "../../build/obj/tsconfig.tsbuildinfo", + "rootDir": "./" + }, + "references": [ + { "path": "../../../types" } + ], + "include": [ + "./**/*" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/common/models/wordbreakers/src/main/default/searchForProperty.ts b/common/models/wordbreakers/src/main/default/searchForProperty.ts new file mode 100644 index 00000000000..45a1641bd9d --- /dev/null +++ b/common/models/wordbreakers/src/main/default/searchForProperty.ts @@ -0,0 +1,44 @@ +import { WordBreakProperty } from "./data.js"; + +export function searchForProperty(codePoint: number): WordBreakProperty { + const bucketSize = codePoint <= 0xFFFF ? 2 : 3; + + // SMP chars take a bit more space to encode. + // TODO: encode the strings & import them here. + const encodedArray = bucketSize == 2 ? "" /* BMP string */ : "" /* non-BMP string */; + + return _searchForProperty(encodedArray, codePoint, bucketSize, 0, encodedArray.length / bucketSize - 1); +} + +/** + * Binary search for the word break property of a given CODE POINT. + * + * The auto-generated data.ts master array defines a **character range** + * lookup table. If a character's codepoint is equal to or greater than + * the start-of-range value for an entry and exclusively less than the next + * entry's start-of-range, it falls within the first entry's range bucket + * and is classified accordingly by this method. + */ +function _searchForProperty(encodedArray: string, codePoint: number, bucketSize: number, left: number, right: number): WordBreakProperty { + // All items that are not found in the array are assigned the 'Other' property. + if (right < left) { // May need special handling at end of BMP / start of non-BMP. + return WordBreakProperty.Other; + } + + let midpoint = left + ~~((right - left) / 2); + let candidate = encodedArray.codePointAt(bucketSize * midpoint); + + // If out-of-bounds, gives NaN. + let nextRange = encodedArray.codePointAt(bucketSize * (midpoint + 1)); + let startOfNextRange = isNaN(nextRange) ? Infinity : nextRange; + + if (codePoint < candidate) { + return _searchForProperty(encodedArray, codePoint, bucketSize, left, midpoint - 1); + } else if (codePoint >= startOfNextRange) { + return _searchForProperty(encodedArray, codePoint, bucketSize, midpoint + 1, right); + } + + // We found it! + const propertyCode = encodedArray.charCodeAt(bucketSize * (midpoint + 1) - 1); + return propertyCode as WordBreakProperty; +} \ No newline at end of file diff --git a/common/models/wordbreakers/src/main/test-index.ts b/common/models/wordbreakers/src/main/test-index.ts new file mode 100644 index 00000000000..a69cc42ae3d --- /dev/null +++ b/common/models/wordbreakers/src/main/test-index.ts @@ -0,0 +1,6 @@ +// Include all standard exports. +export * from './index.js'; + +// Exposes some internal properties for unit-test accessibility +export { WordBreakProperty } from './default/data.js'; +export { searchForProperty } from './default/searchForProperty.js'; From 58c7575d4bc73e1514a840b9bd00030be29afbf1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 13 Feb 2024 08:21:40 +0700 Subject: [PATCH 003/262] feat(web): full property data-table generation functionality; data is exact match --- common/models/wordbreakers/UNICODE_VERSION.md | 1 + common/models/wordbreakers/package.json | 6 +- .../wordbreakers/src/data-compiler/index.ts | 323 ++++++++++++++++++ .../src/data-compiler/tsconfig.json | 8 +- .../imports/WordBreakProperty-13.0.0.txt.gz | Bin 0 -> 23663 bytes .../src/imports/emoji-data-13.0.0.txt.gz | Bin 0 -> 23400 bytes common/models/wordbreakers/tsconfig.json | 7 +- package-lock.json | 13 +- 8 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 common/models/wordbreakers/UNICODE_VERSION.md create mode 100644 common/models/wordbreakers/src/data-compiler/index.ts create mode 100644 common/models/wordbreakers/src/imports/WordBreakProperty-13.0.0.txt.gz create mode 100644 common/models/wordbreakers/src/imports/emoji-data-13.0.0.txt.gz diff --git a/common/models/wordbreakers/UNICODE_VERSION.md b/common/models/wordbreakers/UNICODE_VERSION.md new file mode 100644 index 00000000000..70045d3c9a6 --- /dev/null +++ b/common/models/wordbreakers/UNICODE_VERSION.md @@ -0,0 +1 @@ +13.0.0 \ No newline at end of file diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index ee9de24237a..bd530aaeab1 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -27,6 +27,9 @@ "./obj/*.js": "./build/obj/*.js", "./test-index": { "default": "./build/obj/test-index.js" + }, + "./UNICODE_VERSION.md": { + "default": "./UNICODE_VERSION.md" } }, "directories": { @@ -60,7 +63,8 @@ "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "zlib": "^1.0.5" }, "type": "module", "dependencies": { diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts new file mode 100644 index 00000000000..e217bc51f15 --- /dev/null +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -0,0 +1,323 @@ +#!/usr/bin/env node + +// Original version found at: https://github.com/eddieantonio/unicode-default-word-boundary/blob/master/libexec/compile-word-break.js + +// TODO: Adapt to produce two string-encoded arrays - one for BMP chars, one for non-BMP chars. + +import zlib from 'zlib'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +/* + * Generates the TypeScript file for data required for the word boundary + * function: + * + * - a sorted array to facilitate binary search of the Word_Break property. + * - a regular expression that matches characters that have Extended_Pictographic=Yes. + * + * For internal use only. Please keep away from children. + * + * The generated file is saved to ../src/gen/WordBreakProperty.ts + */ + +const MAX_CODE_POINT = 0x10FFFF; + +// Where to get the data: +// - http://www.unicode.org/reports/tr51/#emoji_data +// - https://www.unicode.org/reports/tr41/tr41-26.html#Props0 + +//////////////////////////////////// Main //////////////////////////////////// + +const projectDir = path.dirname(require.resolve("@keymanapp/models-wordbreakers/UNICODE_VERSION.md")); +const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data_test.ts'); + +// Ensure this package's major version number is in sync with the Unicode +// major version. + +const packageVersion = new String(fs.readFileSync(`${projectDir}/UNICODE_VERSION.md`)); +const UNICODE_VERSION = packageVersion.split('.')[0] + '.0.0'; + +// The data files should be in this repository, with names matching the +// Unicode version. +let wordBoundaryFilename = path.join(projectDir, `./src/imports/WordBreakProperty-${UNICODE_VERSION}.txt.gz`); +let emojiDataFilename = path.join(projectDir, `./src/imports/emoji-data-${UNICODE_VERSION}.txt.gz`); + +///////////////////////////// Word_Boundary file ///////////////////////////// + +// Extract the ranges IN ASCENDING ORDER from the file. +// This will be the big binary search table. +let ranges = readZippedCharacterPropertyFile(wordBoundaryFilename) + .sort((a, b) => { + return a.start - b.start; + }); + +// The list of ranges are initially sparse — having gaps between assigned +// ranges. Fill in those gaps: +ranges = makeDense(ranges); +ensureDense(ranges); + +// The possible Word_Break property assignments. +let categories = new Set(); +for (let {property} of ranges) { + categories.add(property); +} +// Also add pseudo-categories of start-of-text and end-of-text: +categories.add('sot'); +categories.add('eot'); + +///////////////////////// Extended_Pictographic=Yes ////////////////////////// + +let extendedPictographicCodePoints = readZippedCharacterPropertyFile(emojiDataFilename) + .filter(({property}) => property === 'Extended_Pictographic'); + +// Try generating the regular expression both in a way that is +// backwards-compatbile and one that only works in ES6+. +let extendedPictographicRegExp; +let compatibleRegexp = utf16AlternativesStrategy(); +let es6Regexp = unicodeRangeStrategy(); + +// Choose the shortest regular expression. +// In my experience, the ES6 regexp is an order of magnitude smaller! +if (es6Regexp.length < compatibleRegexp.length) { + extendedPictographicRegExp = es6Regexp; + console.warn(`Using ES6 regexp [${es6Regexp.length} chars]`); +} else { + extendedPictographicRegExp = compatibleRegexp; + console.warn(`Using compatibility regexp [${compatibleRegexp.length} chars]`); +} + +let catIndexSeed = 0; +const categoryMap = new Map(); + +for(let cat of categories) { + categoryMap.set(cat, catIndexSeed++); +} + +//////////////////////// Creating the generated file ///////////////////////// + +// Save the output in the gen/ directory. +let stream = fs.createWriteStream(generatedFilename); + +// // Former entry in the original version by Eddie that was never included in our repo: +// export const extendedPictographic = ${extendedPictographicRegExp}; + +// Generate the file! +stream.write(`// Automatically generated file. DO NOT MODIFY. + +/** + * Valid values for a word break property. + */ +export const enum WordBreakProperty { +${ /* Create enum values for each word break property */ + Array.from(categories) + .map(x => ` ${x}`) + .join(',\n') +} +}; + +export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ +${ + // TODO: Two versions: one that's BMP-encoded, one that's non-BMP encoded. + ranges.map(({start, property}) => (` [` + + `/*start*/ 0x${start.toString(16).toUpperCase()}, ` + + `WordBreakProperty.${property}],` + )).join('\n') +} +]; +`); + +/** + * Reads a Unicode character property file. + * + * Character property files are composed of comment lines, empty lines, and + * property lines. Comments lines begin with '#' and should be ignored, as + * well as empty lines. + * + * Property lines have a code point or a code point range, followed by a + * semi-colon, followed by the property text. e.g., + * + * 1F600 ; Emoji # 6.1 [1] (😀) grinning face + * 26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind butt + * + * This will read the file at the given filename, and return an ordered array + * or property lines, with attributes: + * + * {start: number, end: number, property: string} + * + * If the property specifies a single code point (i.e., not a range of code + * points), then end === start. + */ +function readZippedCharacterPropertyFile(filename) { + let textContents = zlib.gunzipSync( + fs.readFileSync(filename) + ).toString('utf8'); + + return textContents.split('\n') + .filter(line => !line.startsWith('#') && line.trim()) + .map(line => { + let [_, startText, endText, property] = line.match( + // Parse lines that look like this: + // 0000 .. 0000 ; CategoryName + /^([0-9A-F]{4,6})(?:..([0-9A-F]{4,6}))?\s+;\s+([A-Za-z_]+)/ + ); + + let start = parseCodepoint(startText); + let end = endText !== undefined ? parseCodepoint(endText) : start; + + return { start, end, property }; + }); +} + +/** + * Parses a code point, expressed as a 4 or 6 digit hexadecimal string. + * Does some bounds checking in order to determine if the string is in fact a + * valid code point. + */ +function parseCodepoint(hexString) { + let number = parseInt(hexString, 16); + if (Number.isNaN(number)) { + throw new SyntaxError(`Cannot parse codepoint: ${hexString}`); + } + + if (number < 0 || number > MAX_CODE_POINT) { + throw new RangeError(`Codepoint out of range: ${number}`); + } + + return number; +} + +function toUnicodeEscape(codePoint) { + let isBMP = codePoint <= 0xFFFF; + let simpleConversion = codePoint.toString(16).toUpperCase(); + + let padding = (isBMP ? 4 : 6) - simpleConversion.length; + let digits = '0'.repeat(padding) + simpleConversion; + + if (isBMP) { + return '\\u' + digits; + } else { + return `\\u{${digits}}`; + } +} + +function utf16AlternativesStrategy() { + let codePoints = []; + for (let {start, end} of extendedPictographicCodePoints) { + for (let current = start; current <= end; current ++) { + codePoints.push(current); + } + } + + let alternatives = codePoints.map(codePointToUTF16Escape); + return `/^(?:${alternatives.join('|')})/`; +} + +function codePointToUTF16Escape(codePoint) { + // Scalar values remain the same + if (codePoint <= 0xFFFF) { + return toUnicodeEscape(codePoint); + } + + const LOWEST_TEN_BITS_MASK = 0x03FF; + let astralBits = codePoint - 0x10000; + + let highSurrogate = 0xD800 + (astralBits >>> 10); + let lowSurrogate = 0xDC00 + (astralBits & LOWEST_TEN_BITS_MASK); + + console.assert(highSurrogate <= 0xDBFF); + console.assert(lowSurrogate <= 0xDFFF); + console.assert(String.fromCharCode(highSurrogate) + String.fromCharCode(lowSurrogate) === + String.fromCodePoint(codePoint)); + return codePointToUTF16Escape(highSurrogate) + codePointToUTF16Escape(lowSurrogate); +} + +function unicodeRangeStrategy() { + let regexp = ''; + for (let {start, end} of extendedPictographicCodePoints) { + if (start === end) { + regexp += toUnicodeEscape(start); + } else { + regexp += toUnicodeEscape(start) + '-' + toUnicodeEscape(end); + } + } + return `/^[${regexp}]/u`; +} + +function makeDense(ranges) { + return joinSameAdjacentProperties(fillInGaps(ranges)); +} + +function ensureDense(ranges) { + let lastEnd = -1; + let lastProperty = 'sot'; + for (let range of ranges) { + let {start, end, property} = range + + if (lastEnd + 1 !== start) { + throw new Error(`Non-adjacent range: ${JSON.stringify(range)}`); + } + + if (lastProperty === property) { + throw new Error(`adjacent ranges have same property: ${JSON.stringify(range)}`); + } + + lastEnd = end; + lastProperty = property; + } +} + + +function joinSameAdjacentProperties(ranges) { + console.assert(ranges.length > 1); + + let conjoinedRanges = []; + conjoinedRanges.push(ranges.shift()); + + for (let range of ranges) { + let lastRange = conjoinedRanges[conjoinedRanges.length - 1]; + if (range.property === lastRange.property) { + lastRange.end = range.end; + } else { + conjoinedRanges.push(range); + } + } + + return conjoinedRanges; +} + +function fillInGaps(ranges) { + console.assert(ranges.length > 1); + + let denseRanges = []; + let nextUnaccountedCodepoint = 0x0000; + + for (let range of ranges) { + if (range.start > nextUnaccountedCodepoint) { + // Need to create a range BEFORE the next start of ranges + denseRanges.push({ + start: nextUnaccountedCodepoint, + end: range.start - 1, + // If it's unassigned in the file, it should be 'Other'. + property: 'Other', + }); + } + + denseRanges.push(range); + nextUnaccountedCodepoint = range.end + 1; + } + + // Create the last range (till the end) + if (nextUnaccountedCodepoint < MAX_CODE_POINT) { + denseRanges.push({ + start: nextUnaccountedCodepoint, + end: MAX_CODE_POINT, + property: 'Other', + }) + } + + return denseRanges; +} \ No newline at end of file diff --git a/common/models/wordbreakers/src/data-compiler/tsconfig.json b/common/models/wordbreakers/src/data-compiler/tsconfig.json index ce66f0bbfc1..19f5dfbab07 100644 --- a/common/models/wordbreakers/src/data-compiler/tsconfig.json +++ b/common/models/wordbreakers/src/data-compiler/tsconfig.json @@ -3,9 +3,11 @@ "compilerOptions": { "baseUrl": "./", - "outDir": "../../build/obj", - "tsBuildInfoFile": "../../build/obj/tsconfig.tsbuildinfo", - "rootDir": "./" + "outDir": "../../build/obj/data-compiler", + "tsBuildInfoFile": "../../build/obj/data-compiler/tsconfig.tsbuildinfo", + "rootDir": "./", + "module": "node16", + "moduleResolution": "node16" }, "references": [ { "path": "../../../types" } diff --git a/common/models/wordbreakers/src/imports/WordBreakProperty-13.0.0.txt.gz b/common/models/wordbreakers/src/imports/WordBreakProperty-13.0.0.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..d3d188ae8249794dfe489b2dabaab286bf7f4725 GIT binary patch literal 23663 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 SwySxQKmGqYUQY-}lK}wuYX*M+ literal 0 HcmV?d00001 diff --git a/common/models/wordbreakers/src/imports/emoji-data-13.0.0.txt.gz b/common/models/wordbreakers/src/imports/emoji-data-13.0.0.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..bf2d917698e8109106f2ae2da8f778c222ce7f4c GIT binary patch literal 23400 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%0DIL-cBHi5~rE~}k z9TL)wbl2v&@4esmIQFOg2d*Em)^#q|BY`Tj3(9?`G^PU)7G35$btEg~-88iHh8iI{rURU4{LNb!eP+6x^o}7vWm2cCABp|~a2|*v z%w`*~doKVz!+HCkq$#`zQokJ184s)>*=;-1H|Rzi!+oq>0=t8I(M9((>=tArp-_h~Q;t@svl!IC{Hsk9(yCjlb`*F8*ngJNGqUn@>Vp^x;8s+; zpUqa7Od+4YLx?o(GHz{PX^dv@X#TwUg}cqSb(fT1>~Qw-=ML!YoB{2J6okT(d2Ci4 zvE$QL^6~T`TYlR^>bP|6%zZWbs0>D#kMbG-uN1LekLBAqq3u8zgZ!$hIC=vL~k1qj;F7Au8=&s+|d0N7G%+h{GGUR8^E9Z4UqejW8XAkIsKQ@wYFg^-+uHi*;* zb+CKv=4$_sLXn*~A0=$p1X|R^RkPJUE-0=SEgxL8QOwIR<+tM51ENtXIBZo6n3=_T z?H|GH#NRE@G6R%($(=P1FY6OfCo@m`eMsZhWJWhOn9cZEn+lt93f9T2?HJ>i1 zTv}{Nx1W+VDRC#4JWdffv)il^?sPWlJ(3Fl!jG(w*1E6O`dEkx%|ncCD^X+B?09=S z9w3wZ`Sz;0UE{Fm51)5Uh^T#!d)giXC${jL@G5p^`_D}6)dc@blRWp{>(u?({c20$ zRb%|~okB;Azm`UqzUKzcY1(d+2ox>8`(Jzb|uQxA= zEloA%Oa(^ow5?)i{bZ3U9^s1p^?Q$gaui2mvD;Ngx#S0;03Q72IBf*Gc%#d zY{Ve7Y`D5{yzE-vx8BhNI+ohV+>>PuHYcIo1zGqNG6~CwHky_Z#zJ2tG}VT7^`S3j zXncCMwtIW1kJ4zp&qp<|yg>SBouTud;9ATsm%mu~Roo8NOqcIknug1unJHKlc#bU? z*6yO}7&CHE?t*b~C!ASDeqQdPeTNPl|M<%APOanIZ~ppkkOupAs<+u*i<&)+W?gZT z{vI`Jzo~f5gx=Si%I;iq*55fv5hfAHAIo>w)!ht>&qjD&_K85%P=tq}tS?4|vqCIw zz%`5)>%_~)&y9xR)gpFAiVNW86)8Uj~iF#(#DYm=ScHRgvJS}_)t|1l#%?=h$h~0D;k)?&-EjsR4V-ky<_#@ICpL__$ z=)Jlzt2?Il<_z3L?bJG>1UTkuYTxe!c>twEA`)GYD7p_d9g(IoxsY)3lqWRyM$){= zBALlC(k!tULOD5j01FVRzZJQu$@if`@S{IsA;%GE{%V@H+u zoTK0|#zCvA=hiwth$@$)64aYU-GC%$Xjm_>C)~Y);-hqJ-|O^>Ic*F?kWgMv-(q!e zX8uS1=MfCgNH?ozy|;#RkH1=`?gpO&n;3)K2F}vwa(ng7wWuM`)qaoqZu81Lb|$&# zn@0f!U5T3@#}x-7)AprKE-m09p9?M%Tc~qtH6Ru|Y*4{UB^yb8*ZalGNOa8;$Jfj| z8-4E2N0z;thZC-nmX=6f2kstz1Tm~IeKdt z{g`6{e$-?Vkm35}VCA}bI1x?I2R9u~%~1wIGHtscAxkjk^*Y!mv@)LD$s9Je4sWws zK8lTRpRel358>BRskY-G)!q6Ixb!12w0*umV_m5@TZpJHd@|w%x4D6#i@NVFR++2 zd~e*LvRD4o`?tm#L522)vCh<9@(h6eX9brWtob9e_tEi}PZb<4zL|pp&Sl{KW%WE+ zN*9$DRm?5O`La527#%YGm}W=>>!N6&i>kYL{rMVKSJ5m}{-*9}Us<`DyONu5{{~GD zNjN-;9UlGOAZhDHOXMQungT0Ik_Zd`Ae2_OI3*wt^22-NJx>`)I(IcFr+{!}N%SM* z10AKJNVx0uuhZ1S96!|QM4d+FByU&;whKt^p9o4Oo(=Z`%v zY}oyaloHhJ;5_Q@j1-($wt6Yi{*h7-^F>&hJ$%DT^+C+^En^pnEpPaR`Cmcr_t#a#()!Pd19v#%kLa@p(YDJ5kq7vIx&nn}vCGBqkfRz-fN2&(>%mD(Oe z$nM7)Bs}yORimLZZLuDKTph82qpA*9sg@$z{QtF5YQHECBTR532-lL%`A{^`zGCeo zQb1XDtg!P8%$Gv2p0@ktKL&%`xBtJDg1T^jiy5Pw@-_`s=(1 zEUFJ#Tl2F-PEq*Lv^3Q;IcQRE4IK&uuxrEC7wn}z7EeV5cOEtOomzfs|Kb|u;vch# z2+*BPBQ-t{^8p7OMT`$0%lwO_w7lv5!BS_OFI1!cwNnb7d{7bRGp@P6W=2;kSJYp- z_@1LDRPVht9Tu+a2{CCR=M$_yyw!^*?^J~EGKe?1sze6*HOCaGUL%?%pL(*sK4;;X26eli)~>%?;C%{%5oWeY|6wcQ0- zvg<)_1*$dTunmv$zc#DGDSX)NM*q=LoIhT)6l>&vYbnhI-wW--So(o#eZTRrS+3F$ zfZ*4IhMGb#28CqHWS5Oloecw(!AKyX*{H4B-3Vs9K#!0o1=WCkYX?Gmd(5J%7-vJX z+&^Hdg4#?KV`ZwB&(3Npn5ef`WT3WYw00R_yJaX$XyhOK*23bcgGA^WRh&d7=>sTq zZoLI#zJyy-Udyjt!pFE|B*fwrL4m1BXLF9=95FPhmD0qS`(Y^CB+0UT*wv6^k^oOu zgvSy_x34h&BBqQPo!KpV%wQj24?R~C;P3)l1lBKGNvcCUMO%cv^xyZ&h zO%6p&(Z$+_l76B{C>!1$>w4Hlw7y}C^<^?w1}v!XJUq?+UF6S1jRsf0JMN>%I}?4N zE%Q;Lmwj;|+!bvXj%JE)1 zsd8_v{zh7kTp$OOtUVD_K9EMP5nN!Jpsi83SfiKZcjM`A&Eims%q{by%T+7fO!)EA zZ8!e4zj?Hd+n(bt;`TC?53c*r42couJ&=RT$f(2=mFk|oV2W#u176)b(W2Fu=aoU} z!`vC{^kS?eyZrqrky!OtQRZTK$kmXG(v!*r)&Yj1V3=dj0QyFikS&46P;e;P&=X~A zQ3XLCm7`Zo1diZOeS#G-!s5Onfe2l4*D-7jk#z|hY3&Vs_Pk~#U5Y~Th)g{h(Qlsq zuXA7n>B%qUhNwakiH+#G(k;kE(}q8kFm<4@tE_``*qZNjA5?s*K8c zRCuu4ZzJ}@8JJCqy;Ss|VFwXjM0fU>M0y4Lq`ho-8I2{FQb}Qe z#_A0ERW&&03hQ(KZZNh;Nuu_M3Ze$}$yo39v(N1(s8T;)ozPkaNcsqCDTz*hjlq(= z2BiOuJR8Enb~5cr7w>&LB8LuXp*HvI%@pbaP|UWWKrq#kEm-Z(Q^#;lLk4@$e0XE# zK55P!rd8EC(MiR#vCUW$Z(r)Y4gk4 zVb7^xoNh|Qb=*F=d-*VkWajh+2sD~0EI@9q3R)l0^Fgw%)VYRHH2dm zV&bXYT?S}Ik|xUnnS_PvHX*#UI`nZv=T5 z|AIG=jhxsm5zP)#F|k`(el-kE>te?i(g-YO{7d_|Rk1HAc?WHA)!!~k(=APQDHGb5 zn42O%KrVAHn_!c!G)P!|#i9pf=8Wn5Olx~mPc(3xQ8o1it>|1cD_IaNX8cKHy~?2N z{%Zt_N?aTit>tT(-Dg5w>Ac}Ce~qm;uUcU*~zkQ zsxd)Q0}y^gW>=$m^j4c$UU8D(!r&c^ z9P|KBbcfov`;U&toGLtoa#gkUhnpz7my8!s>W&a`FF=3b(A8kj-}*ZT$!T~G)%@ZmIscC*Hz|MpI%Hu2Xm>XWIX3$*2JY;VsA3nIi=j*xV*Fy@tRxLVH#G5C zeKB^w@0ELD`inTv7f%zlcXfe+4EPW*Mpi!A`R4Gu8u5(bLHZ+jFfG5yeIN@*_ zyfo|>hbmoCyeniF_>Va`S|}+M22oB~9mdnCcCH6&QpQq)9;v_tGkLzY3qJ50sMBYwN`CPJjTvx zn3E_doZK!mCjaV4w6jD!Ni_NgLo8Vz?eL1Pec8~nByL0zJc zKR>C$ZBtl$pEs6zSjgP5oSJQcFJ!5G#-;V$T2nrp7Gr%Zi3 z`)+d~m<-f!Db1$D8juxN#yqylVxLw_AJSxCc^B0EH+@vFLvD^mNmJ^(C9*N#H>J8J zu7s};yApC^&Z>rcZY}2(x*O%v+j@(@DitY8zb{v_Q%+1IJ)&dy&dz(}wVRv!r*t}l zAH?rp1YX_6vKx#ObAr-6f`8;c1kcP-buQ6ut;T%oLzLKm2%g(b)0Qj3e7+g2s08|) z+pQm1{q%Bm>XaGpu2Br5eyu{j;LN_=Tv%8&%1Xd!HuD8x;^ZFMiW z(vVDl0DZ!M;VS_rM3d+av-!%(;jF^>Wn>d}AGRWOX54UpaIlFxu{SnTekQRk*;Dzn z5c~*{Sb@fLt&8t+PTHKcKVx2RSb4Wc+x@YIoGA2!1-`e$`6IP2podgil}>LX)nkp9 ztqKYJJ&zy9)~r0I*^vc0$Y$|u7oE{*c?I_)rwBHsq6qGK+V2iAE&v-Ua@13nL*0;{ aQ%ik#j4iV%A}=wXX`((eecZ`JMEEab8)bz6 literal 0 HcmV?d00001 diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json index 57df26333b7..1cebab27c4a 100644 --- a/common/models/wordbreakers/tsconfig.json +++ b/common/models/wordbreakers/tsconfig.json @@ -5,9 +5,12 @@ "baseUrl": "./", "outDir": "build/obj", "tsBuildInfoFile": "build/tsconfig.tsbuildinfo", - "rootDir": "./" + "rootDir": "./", + "module": "Node16", + "moduleResolution": "Node16" }, "references": [ - { "path": "./src/main" } + { "path": "./src/main" }, + { "path": "./src/data-compiler" } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f6a9e8a6928..9b20b0299c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,7 +109,8 @@ "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "zlib": "^1.0.5" } }, "common/models/wordbreakers/node_modules/@types/node": { @@ -12343,6 +12344,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zlib": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", + "integrity": "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=0.2.0" + } + }, "resources/build/version": { "name": "@keymanapp/auto-history-action", "license": "MIT", From 0b79b86197a2ce335e01b299b634c6a25a2e971f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 13 Feb 2024 08:26:27 +0700 Subject: [PATCH 004/262] feat(common/models): initial pass for encoded-string wordbreaker data tables --- .../wordbreakers/src/data-compiler/index.ts | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts index e217bc51f15..f639f670a84 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -95,6 +95,37 @@ const categoryMap = new Map(); for(let cat of categories) { categoryMap.set(cat, catIndexSeed++); + if(catIndexSeed == '`'.charCodeAt(0)) { + catIndexSeed++; // Skip the back-tick as an encoding symbol. + // Reduces complications, as it's the encoding string start/end char. + } +} + +const bmpRanges: typeof ranges = []; +const nonBmpRanges: typeof ranges = []; + +// { start: number, property: number}[] +for(let range of ranges) { // already sorted + if(range.start <= 0xFFFF) { + bmpRanges.push(range); + } else { + if(nonBmpRanges.length == 0) { + const finalBmpRange = bmpRanges[bmpRanges.length - 1]; + bmpRanges.push({ + start: 0xFFFF, + property: range.property, + end: undefined + }); + + nonBmpRanges.push({ + start: 0x10000, + property: finalBmpRange.property, + end: undefined + }); + } + + nonBmpRanges.push(range); + } } //////////////////////// Creating the generated file ///////////////////////// @@ -107,28 +138,40 @@ let stream = fs.createWriteStream(generatedFilename); // Generate the file! stream.write(`// Automatically generated file. DO NOT MODIFY. - /** * Valid values for a word break property. */ export const enum WordBreakProperty { ${ /* Create enum values for each word break property */ Array.from(categories) - .map(x => ` ${x}`) + .map(x => ` ${x} = ${categoryMap.get(x)}`) .join(',\n') } }; -export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ -${ - // TODO: Two versions: one that's BMP-encoded, one that's non-BMP encoded. - ranges.map(({start, property}) => (` [` + - `/*start*/ 0x${start.toString(16).toUpperCase()}, ` + - `WordBreakProperty.${property}],` - )).join('\n') +export const WORD_BREAK_PROPERTY_BMP: string = \`${ + // To consider: emit `\uxxxx` codes instead of the raw char? + bmpRanges.map(({start, property}) => { + let codedStart = String.fromCodePoint(start); + if(codedStart == '`') { + // Prevents accidental unescaped use of the string start/end char. + // The backslash gets removed on file-load. + codedStart = '\\`'; + } + const codedProp = String.fromCharCode(categoryMap.get(property)); + return `${codedStart}${codedProp}`; + }).join('') +}\` + +export const WORD_BREAK_PROPERTY_NON_BMP: string = \`${ + // To consider: emit `\uxxxx` codes instead of the raw char? + nonBmpRanges.map(({start, property}) => { + const codedStart = String.fromCodePoint(start); + const codedProp = String.fromCharCode(categoryMap.get(property)); + return `${codedStart}${codedProp}`; + }).join('') } -]; -`); +\``); /** * Reads a Unicode character property file. From 47d945f30934bd12b0c16af8a104308521ed6cef Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 16 Feb 2024 11:56:44 +0700 Subject: [PATCH 005/262] fix(web): conditional import path --- common/models/wordbreakers/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index bd530aaeab1..774ceee0422 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -18,7 +18,7 @@ "types": "build/obj/index.d.ts", "exports": { ".": { - "es6-bundling": "./src/index.ts", + "es6-bundling": "./src/main/index.ts", "default": "./build/obj/index.js" }, "./lib": { From 346556b74f75766d98156724e869ad9218d5d415 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 16 Feb 2024 11:56:44 +0700 Subject: [PATCH 006/262] fix(web): conditional import path --- common/models/wordbreakers/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index bd530aaeab1..774ceee0422 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -18,7 +18,7 @@ "types": "build/obj/index.d.ts", "exports": { ".": { - "es6-bundling": "./src/index.ts", + "es6-bundling": "./src/main/index.ts", "default": "./build/obj/index.js" }, "./lib": { From 39ae61a8358cd7988c1d4c3b6c5d6ed323affbe7 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Thu, 6 Jun 2024 16:31:11 +0700 Subject: [PATCH 007/262] fix(mac): add unit tests to confirm first calls to compliance check Also makes public the description method used for logging. A private description method may have been an issue with the older logging code. To build successfully with Xcode, it was necessary to change runOnlyForDeploymentPostprocessing=1. This was apparently needed because of the upgrade to Xcode 15 --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 4 +- .../Keyman4MacIM/TextApiCompliance.h | 1 + .../Keyman4MacIM/TextApiCompliance.m | 2 +- .../KeymanTests/InputMethodTests.m | 53 +++++++++++++++++-- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 35a362f2617..8e0b973442e 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -114,13 +114,13 @@ }; E211CCF420B5FE7A00505C36 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 8; dstPath = ""; dstSubfolderSpec = 16; files = ( E211CCF620B600A500505C36 /* KeymanEngine4Mac.framework.dSYM in CopyFiles */, ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; }; E27BA9CF202036BA00D273E7 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.h b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.h index 6c80417faf3..81cc4c3b348 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly) NSString *clientApplicationId; -(instancetype)initWithClient:(id) client applicationId:(NSString *)appId; +-(NSString *)description; -(void)checkCompliance:(id) client; -(void) checkComplianceAfterInsert:(id) client delete:(NSString *)textToDelete insert:(NSString *)textToInsert; -(BOOL)isComplianceUncertain; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m index 0f62fa61a8a..e83439794b6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m @@ -73,7 +73,7 @@ -(instancetype)initWithClient:(id) client applicationId:(NSString *)appId { -(NSString *)description { - return [NSString stringWithFormat:@"complianceUncertain: %d, hasCompliantSelectionApi: %d, canReadText: %d, canReplaceText: %d, mustBackspaceUsingEvents: %d, clientAppId: %@, client: %@", self.complianceUncertain, self.hasCompliantSelectionApi, [self canReadText], [self canReplaceText], [self mustBackspaceUsingEvents], _clientApplicationId, _client]; + return [NSString stringWithFormat:@"complianceUncertain: %d, hasCompliantSelectionApi: %d, canReadText: %d, canReplaceText: %d, mustBackspaceUsingEvents: %d, clientApplicationId: %@, client: %@", self.complianceUncertain, self.hasCompliantSelectionApi, [self canReadText], [self canReplaceText], [self mustBackspaceUsingEvents], _clientApplicationId, _client]; } /** test to see if the API selectedRange functions properly for the text input client */ diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 8eb9d974ceb..a2953c44cf5 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -15,23 +15,27 @@ #import "TextApiCompliance.h" KMInputMethodEventHandler *testEventHandler = nil; +id testClient = nil; @interface InputMethodTests : XCTestCase @end @interface KMInputMethodEventHandler (Testing) - +@property (nonatomic, retain) TextApiCompliance* apiCompliance; +@property (nonatomic, retain) NSString* clientApplicationId; +@property BOOL contextChanged; - (instancetype)initWithClient:(NSString *)clientAppId client:(id) sender; - (NSRange) calculateInsertRangeForDeletedText:(NSString*)textToDelete selectionRange:(NSRange) selection; +- (void)checkTextApiCompliance:(id)client; @end @implementation InputMethodTests - (void)setUp { - id client = [[AppleCompliantTestClient alloc] init]; + testClient = [[AppleCompliantTestClient alloc] init]; NSString *clientAppId = @"com.compliant.app"; - testEventHandler = [[KMInputMethodEventHandler alloc]initWithClient:clientAppId client:client]; + testEventHandler = [[KMInputMethodEventHandler alloc]initWithClient:clientAppId client:testClient]; } - (void)tearDown { @@ -85,4 +89,47 @@ - (void)testCalculateInsertRange_deleteOneBMPCharacterWithOneSelected_returnsRan XCTAssertTrue(correctResult, @"insert or replacement range expected to be {1,2}"); } +/** + * test compliance check of a KMInputMethodEventHandler with a nil client application ID + * not sure if this can ever happen, but the lifecycle of the input method is not 100% clear, + * and we would like this scenario, if it can occur, to not result in a crash + */ +- (void)testCheckCompliance_withUnknownApplicationId_createsComplianceObject { + id client = [[AppleCompliantTestClient alloc] init]; + //NSString *clientAppId = @"com.compliant.app"; + KMInputMethodEventHandler *eventHandler = [[KMInputMethodEventHandler alloc]initWithClient:nil client:client]; + [eventHandler checkTextApiCompliance:client]; + XCTAssertNotNil(eventHandler.apiCompliance, @"apiCompliance object was not created"); +} + +- (void)testCheckCompliance_withNilComplianceObject_createsComplianceObject { + [testEventHandler checkTextApiCompliance:testClient]; + XCTAssertNotNil(testEventHandler.apiCompliance, @"apiCompliance object was not created"); +} + +- (void)testCheckCompliance_withChangedClientApplicationId_createsNewComplianceObject { + // first call causes textApiCompliance object to be created + [testEventHandler checkTextApiCompliance:testClient]; + TextApiCompliance *originalComplianceObject = testEventHandler.apiCompliance; + + testEventHandler.clientApplicationId = @"com.different.app"; + // second call causes new textApiCompliance object to be created due to stale application ID + [testEventHandler checkTextApiCompliance:testClient]; + XCTAssertNotEqualObjects(originalComplianceObject, testEventHandler.apiCompliance, @"New TextApiCompliance object not created for new client application ID"); +} + +- (void)testCheckCompliance_withContextChanged_createsNewComplianceObject { + // first call causes textApiCompliance object to be created + [testEventHandler checkTextApiCompliance:testClient]; + TextApiCompliance *originalComplianceObject = testEventHandler.apiCompliance; + + testEventHandler.contextChanged = YES; + // second call causes new textApiCompliance object to be created due to setting contextChanged flag + [testEventHandler checkTextApiCompliance:testClient]; + XCTAssertNotEqualObjects(originalComplianceObject, testEventHandler.apiCompliance, @"New TextApiCompliance object not created after contextChanged flag set"); +} + + + + @end From 83c06299ba1bf1996511d762c6f0d093d4e36eb7 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 7 Jun 2024 09:35:02 +0700 Subject: [PATCH 008/262] fix(mac): rollback runOnlyForDeploymentPostprocessing change break into separate PR --- mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 8e0b973442e..f0af727d567 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -114,13 +114,13 @@ }; E211CCF420B5FE7A00505C36 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; + buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 16; files = ( E211CCF620B600A500505C36 /* KeymanEngine4Mac.framework.dSYM in CopyFiles */, ); - runOnlyForDeploymentPostprocessing = 1; + runOnlyForDeploymentPostprocessing = 0; }; E27BA9CF202036BA00D273E7 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; From e1f4be9a3ef48b406d478bed7606dd5333932a0b Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:41:34 +1000 Subject: [PATCH 009/262] fix(windows): check IM window will be in a visible location The IM display call back is used to show the IM window. With this change deadcode is removed and a check is made to ensure the IM window is completley visible coordinates for the monitor that has focus. If not it will be position close to where the caret in the application that has focus. --- windows/src/engine/keyman32/calldll.cpp | 64 +++++++++++++++++++------ 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/windows/src/engine/keyman32/calldll.cpp b/windows/src/engine/keyman32/calldll.cpp index c91fab64a99..adab1b7cac2 100644 --- a/windows/src/engine/keyman32/calldll.cpp +++ b/windows/src/engine/keyman32/calldll.cpp @@ -438,25 +438,61 @@ extern "C" BOOL _declspec(dllexport) WINAPI KMDisplayIM(HWND hwnd, BOOL FShowAlw *Globals::hwndIM() = hwnd; *Globals::hwndIMAlways() = FShowAlways; - POINT pt; - RECT r; + POINT IMTop, Caret; + RECT RectIM, RectApp; + HWND hFocus; + int n = 0; + + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (hMonitor == NULL) { + return FALSE; + } + + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(monitor_info); + if (!GetMonitorInfo(hMonitor, &monitor_info)) { + return FALSE; + } - GetCaretPos(&pt); + if (!GetWindowRect(hwnd, &RectIM)) { + return FALSE; + } - int n; - //if(pt.x == 0 && pt.y == 0) - n = SWP_NOMOVE; - //else n = 0; + // Only adjust the IM window coordinates if it is not going to be visible on the + // monitor it is currently showing on. Otherwise leave it to the 3rd paryt app + // to control the location of the IM Window. + // If changing the coordinates place the IM window use the caret postion of the + // application that has focus. + if ((RectIM.left < monitor_info.rcMonitor.left) || (RectIM.right > monitor_info.rcMonitor.right) || + (RectIM.top < monitor_info.rcMonitor.top) || (RectIM.bottom > monitor_info.rcMonitor.bottom)) { - ClientToScreen(hwndFocus, &pt); - GetWindowRect(hwnd, &r); - if(pt.x + r.right - r.left >= GetSystemMetrics(SM_CXSCREEN)) - pt.x = GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left); + if (!GetCaretPos(&Caret)) { + return FALSE; + } - if(pt.y - (r.bottom-r.top) > 0) pt.y -= (r.bottom-r.top); - else pt.y += 32; // guessing... + hFocus = GetFocus(); + if (!GetWindowRect(hFocus, &RectApp)) { + return FALSE; + } + + IMTop.x = Caret.x + RectApp.left; + IMTop.y = Caret.y + RectApp.top + 24; + + if (IMTop.x + RectIM.right - RectIM.left > RectApp.right) { + IMTop.x = RectApp.right - (RectIM.right - RectIM.left); + } + + if (IMTop.y + RectIM.bottom - RectIM.top > RectApp.bottom) { + IMTop.y = RectApp.top + Caret.y - (RectIM.bottom - RectIM.top); + } + n = 0; // Move to tracked postion + } else { + IMTop.x = 0; // will be ignored by SWP_NOMOVE; + IMTop.y = 0; + n = SWP_NOMOVE; + } - SetWindowPos(hwnd, HWND_TOPMOST, pt.x,pt.y,0,0, n|SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOACTIVATE); + SetWindowPos(hwnd, HWND_TOPMOST, IMTop.x, IMTop.y, 0, 0, n|SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOACTIVATE); //SendDebugMessageFormat(0, sdmKeyboard, 0, "KMDisplayIM: Exit"); return TRUE; } From 5488e32be28ab06b83db45ec3f6ea7c960ff4b98 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Mon, 29 Jul 2024 16:01:58 +0700 Subject: [PATCH 010/262] change(mac): add class to encapsulate settings --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 6 ++ .../Keyman4MacIM/KMInputMethodAppDelegate.m | 18 +++++- .../Keyman4MacIM/KMInputMethodEventHandler.m | 16 +++++- .../Keyman4MacIM/KMSettingsRepository.h | 20 +++++++ .../Keyman4MacIM/KMSettingsRepository.m | 57 +++++++++++++++++++ 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 8e0b973442e..46b7f70e24b 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ 98FE10631B4DEE5600525F54 /* KMInfoWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98FE10611B4DEE5600525F54 /* KMInfoWindowController.m */; }; 9A3D6C5D221531B0008785A3 /* KMOSVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A3D6C5C221531B0008785A3 /* KMOSVersion.m */; }; B90818AF7ED302187DE0E026 /* Pods_Keyman.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B644E63217FFE54B91C71C94 /* Pods_Keyman.framework */; }; + D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; E211769D20E182DD00F8065D /* NoContextTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E211769C20E182DD00F8065D /* NoContextTestClient.m */; }; E21176A020E18C5200F8065D /* AppleCompliantTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E211769F20E18C5200F8065D /* AppleCompliantTestClient.m */; }; E211CCF620B600A500505C36 /* KeymanEngine4Mac.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = E211CCF520B600A500505C36 /* KeymanEngine4Mac.framework.dSYM */; }; @@ -350,6 +351,8 @@ CEFFECDB2A417FEC00D58C36 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/KMKeyboardHelpWindowController.strings; sourceTree = ""; }; CEFFECDC2A417FEC00D58C36 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainMenu.strings; sourceTree = ""; }; CEFFECDD2A4180FD00D58C36 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + D861B03D2C5747F70003675E /* KMSettingsRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMSettingsRepository.h; sourceTree = ""; }; + D861B03E2C5747F70003675E /* KMSettingsRepository.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = KMSettingsRepository.m; sourceTree = ""; tabWidth = 2; }; E211769B20E1826800F8065D /* NoContextTestClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NoContextTestClient.h; sourceTree = ""; }; E211769C20E182DD00F8065D /* NoContextTestClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NoContextTestClient.m; sourceTree = ""; }; E211769E20E18C0B00F8065D /* AppleCompliantTestClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppleCompliantTestClient.h; sourceTree = ""; }; @@ -548,6 +551,8 @@ 29B4A0D32BF7675A00682049 /* KMLogs.m */, 299ABD6F29ECE75B00AA5948 /* KeySender.m */, 299ABD7029ECE75B00AA5948 /* KeySender.h */, + D861B03D2C5747F70003675E /* KMSettingsRepository.h */, + D861B03E2C5747F70003675E /* KMSettingsRepository.m */, 297A501128DF4D360074EB1B /* Privacy */, 98FE105B1B4DE86300525F54 /* Categories */, 98D6DA791A799EE700B09822 /* Frameworks */, @@ -974,6 +979,7 @@ 29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */, 98BF924F1BF02DC20002126A /* KMBarView.m in Sources */, E240F599202DED740000067D /* KMPackage.m in Sources */, + D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */, 984B8F441AF1C3D900E096A8 /* OSKWindowController.m in Sources */, 9836B3711AE5F11D00780482 /* mztools.c in Sources */, 9836B3701AE5F11D00780482 /* ioapi.c in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 0675a78adec..b7acec58e83 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -16,6 +16,7 @@ // Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (sessionFinished) block performed very slowly (0.00 secs) #import "KMInputMethodAppDelegate.h" +#import "KMSettingsRepository.h" #import "KMConfigurationWindowController.h" #import "KMDownloadKBWindowController.h" #import "ZipArchive.h" @@ -509,7 +510,9 @@ - (NSString *)keymanDataPath { if(_keymanDataPath == nil) { NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; _keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; - + + os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataPath); + NSFileManager *fm = [NSFileManager defaultManager]; if (![fm fileExistsAtPath:_keymanDataPath]) { [fm createDirectoryAtPath:_keymanDataPath withIntermediateDirectories:YES attributes:nil error:nil]; @@ -531,6 +534,7 @@ - (NSString *)keyboardsPath { } - (NSArray *)kmxFileList { + os_log_debug([KMLogs dataLog], "kmxFileList"); if (_kmxFileList == nil) { NSArray *kmxFiles = [self KMXFiles]; _kmxFileList = [[NSMutableArray alloc] initWithCapacity:0]; @@ -650,6 +654,7 @@ - (NSString *)packageNameFromPackageInfo:(NSString *)packageFolder { } - (NSArray *)keyboardNamesFromFolder:(NSString *)packageFolder { + os_log_debug([KMLogs dataLog], "keyboardNamesFromFolder, folder = %{public}@", packageFolder); NSMutableArray *kbNames = [[NSMutableArray alloc] initWithCapacity:0];; for (NSString *kmxFile in [self KMXFilesAtPath:packageFolder]) { NSDictionary * infoDict = [KMXFile keyboardInfoFromKmxFile:kmxFile]; @@ -756,6 +761,12 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { + if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { + os_log_info([KMLogs startupLog], "keyboards migration needed"); + } else { + os_log_info([KMLogs startupLog], "keyboards migration not needed"); + } + [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; } @@ -928,13 +939,16 @@ - (NSArray *)KMXFiles { } - (NSArray *)KMXFilesAtPath:(NSString *)path { + os_log_debug([KMLogs dataLog], "Reading KMXFiles at path %{public}@", path); NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path]; NSMutableArray *kmxFiles = [[NSMutableArray alloc] initWithCapacity:0]; NSString *filePath; while (filePath = (NSString *)[dirEnum nextObject]) { NSString *extension = [[filePath pathExtension] lowercaseString]; - if ([extension isEqualToString:@"kmx"]) + if ([extension isEqualToString:@"kmx"]) { [kmxFiles addObject:[path stringByAppendingPathComponent:filePath]]; + os_log_debug([KMLogs dataLog], "file = %{public}@", filePath); + } } return kmxFiles; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index fa912ca67d5..cf9eea07024 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -10,6 +10,7 @@ #import /* For kVK_ constants. */ #import "KeySender.h" #import "TextApiCompliance.h" +#import "KMSettingsRepository.h" #import "KMLogs.h" @import Sentry; @@ -238,6 +239,17 @@ - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { // return NO to pass through to client app return NO; } + + // TODO: remove test code + /* + if (event.keyCode == kVK_ANSI_Slash) { + if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { + os_log_info([KMLogs startupLog], "keyboards migration needed"); + } else { + os_log_info([KMLogs startupLog], "keyboards migration not needed"); + } + } + */ } if (event.type == NSEventTypeFlagsChanged) { @@ -412,11 +424,11 @@ -(void) persistOptions:(NSDictionary*)options{ for(NSString *key in options) { NSString *value = [options objectForKey:key]; if(key && value) { - os_log_debug([KMLogs keyLog], "applyNonTextualOutput calling writePersistedOptions, key: %{public}@, value: %{public}@", key, value); + os_log_debug([KMLogs keyLog], "persistOptions, key: %{public}@, value: %{public}@", key, value); [self.appDelegate writePersistedOptions:key withValue:value]; } else { - os_log_debug([KMLogs keyLog], "applyNonTextualOutput, invalid values in optionsToPersist, not writing to UserDefaults, key: %{public}@, value: %{public}@", key, value); + os_log_debug([KMLogs keyLog], "invalid values in persistOptions, not writing to UserDefaults, key: %{public}@, value: %{public}@", key, value); } } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h new file mode 100644 index 00000000000..e4b9f1c158a --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -0,0 +1,20 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMSettingsRepository.h + * Keyman + * + * Created by Shawn Schantz on 2024-07-29. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KMSettingsRepository : NSObject ++ (KMSettingsRepository *)shared; +- (BOOL)keyboardsMigrationNeeded; +@end + +NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m new file mode 100644 index 00000000000..d5a26cccac7 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -0,0 +1,57 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMSettingsRepository.h + * Keyman + * + * Created by Shawn Schantz on 2024-07-29. + * + * Singleton object for reading and writing Keyman application settings. + * Serves as an abstraction to StandardUserDefaults which is currently used to persist application settings. + */ + +#import "KMSettingsRepository.h" +#import "KMLogs.h" + +NSString *const kStoreKeyboardsInLibraryKey = @"KMStoreKeyboardsInLibraryKey"; +NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; + +@implementation KMSettingsRepository + ++ (KMSettingsRepository *)shared +{ + static KMSettingsRepository *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[KMSettingsRepository alloc] init]; + }); + return shared; +} + +- (BOOL)settingsExist +{ + return [[NSUserDefaults standardUserDefaults] objectForKey:kActiveKeyboardsKey] != nil; +} + +- (BOOL)keyboardsStoredInLibraryFolder +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreKeyboardsInLibraryKey]; +} + +/** + * Determines whether the keyboards data needs to be moved from the old location in the Documents folder to the new location under /username/Library... + * This is true if + * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and + * 2) the value for KMStoreKeyboardsInLibraryKey is not set to true + */ +- (BOOL)keyboardsMigrationNeeded { + BOOL keyboardSettingsExist = [self settingsExist]; + os_log([KMLogs startupLog], " keyboard settings exist: %@", keyboardSettingsExist ? @"YES" : @"NO" ); + + BOOL keyboardsInLibrary = [self keyboardsStoredInLibraryFolder]; + os_log([KMLogs startupLog], " keyboards stored in Library: %@", keyboardsInLibrary ? @"YES" : @"NO" ); + + return !(keyboardSettingsExist && keyboardsInLibrary); +} + +@end From 37dcacc0cb092201edea1e00905ec40961fb9dd0 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 30 Jul 2024 16:08:34 +0700 Subject: [PATCH 011/262] change(mac): add KMDataRepository class to handle data (keyboards) migration --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 6 ++ .../Keyman4MacIM/KMDataRepository.h | 21 +++++ .../Keyman4MacIM/KMDataRepository.m | 85 +++++++++++++++++++ .../Keyman4MacIM/KMInputMethodAppDelegate.m | 3 + 4 files changed, 115 insertions(+) create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 46b7f70e24b..9d63aac4685 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 29015ABD2C58D86F00CCBB94 /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; 290BC680274B9DB1005CD1C3 /* KMPackageInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 290BC67F274B9DB1005CD1C3 /* KMPackageInfo.m */; }; 290BC75E274F3FD7005CD1C3 /* KMKeyboardInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 290BC75D274F3FD7005CD1C3 /* KMKeyboardInfo.m */; }; 2915DC512BFE35DB0051FC52 /* KMLogs.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B4A0D32BF7675A00682049 /* KMLogs.m */; }; @@ -136,6 +137,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 29015ABB2C58D86F00CCBB94 /* KMDataRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMDataRepository.h; sourceTree = ""; }; + 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMDataRepository.m; sourceTree = ""; }; 2901BA8A292332B3009903EC /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/KMAboutWindowController.strings"; sourceTree = ""; }; 2901BA8B292332B3009903EC /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/preferences.strings"; sourceTree = ""; }; 2901BA8C292332B3009903EC /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/KMInfoWindowController.strings"; sourceTree = ""; }; @@ -553,6 +556,8 @@ 299ABD7029ECE75B00AA5948 /* KeySender.h */, D861B03D2C5747F70003675E /* KMSettingsRepository.h */, D861B03E2C5747F70003675E /* KMSettingsRepository.m */, + 29015ABB2C58D86F00CCBB94 /* KMDataRepository.h */, + 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */, 297A501128DF4D360074EB1B /* Privacy */, 98FE105B1B4DE86300525F54 /* Categories */, 98D6DA791A799EE700B09822 /* Frameworks */, @@ -982,6 +987,7 @@ D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */, 984B8F441AF1C3D900E096A8 /* OSKWindowController.m in Sources */, 9836B3711AE5F11D00780482 /* mztools.c in Sources */, + 29015ABD2C58D86F00CCBB94 /* KMDataRepository.m in Sources */, 9836B3701AE5F11D00780482 /* ioapi.c in Sources */, E21799051FC5B7BC00F2D66A /* KMInputMethodEventHandler.m in Sources */, 98E6729F1B532F5E00DBDE2F /* KMDownloadKBWindowController.m in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h new file mode 100644 index 00000000000..ed1b8701fe4 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -0,0 +1,21 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMDataRepository.h + * Keyman + * + * Created by Shawn Schantz on 2024-07-30. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KMDataRepository : NSObject ++ (KMDataRepository *)shared; +- (void)migrateResources; +- (NSString *)keymanDataDirectory; +@end + +NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m new file mode 100644 index 00000000000..fa422472e5d --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -0,0 +1,85 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMResourcesRepository.m + * Keyman + * + * Created by Shawn Schantz on 2024-07-30. + * + * Singleton object which serves as an abstraction for the reading and writing of Keyman data. + * The 'data' currently consists of keyman keyboards installed by the user. All data is saved locally on disk using NSFileManager. + * This is in contrast with the lighter weight Settings which is stored using UserDefaults and handled by KMSettingsRepository. + */ + +#import "KMDataRepository.h" +#import "KMLogs.h" + +@implementation KMDataRepository + +NSString* _obsoleteKeymanDataDirectory = nil; +NSString* _keymanDataDirectory = nil; + ++ (KMDataRepository *)shared +{ + static KMDataRepository *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[KMDataRepository alloc] init]; + }); + return shared; +} + +- (void)migrateResources { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; + + os_log_info([KMLogs startupLog], "applicationSupportDirectory: '%{public}@'", applicationSupportUrl); + + NSString *appId = [[NSBundle mainBundle] bundleIdentifier]; + os_log_info([KMLogs startupLog], "application bundleIdentifier: '%{public}@'", appId); + + NSURL *keymanUrl = [applicationSupportUrl URLByAppendingPathComponent: appId isDirectory: TRUE]; + + //NSURL *keymanUrl = [NSURL fileURLWithPath:appId isDirectory:YES relativeToURL:applicationSupportUrl]; + + os_log_info([KMLogs startupLog], "keymanUrl: '%{public}@'", keymanUrl); + // returns -> '/Users/sgschantz/Library/Application Support' + + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:keymanUrl.path isDirectory:&isDir]; + + if (exists) { + os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' exists", keymanUrl); + if (isDir) { + os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' is a directory", keymanUrl); + } + } else { + os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' does not exist", keymanUrl); + } +} + +/** + * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards + */ +- (NSString *)_obsoleteKeymanDataDirectory { + if(_keymanDataDirectory == nil) { + NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + _keymanDataDirectory = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; + + os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataDirectory); + + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm fileExistsAtPath:_keymanDataDirectory]) { + [fm createDirectoryAtPath:_keymanDataDirectory withIntermediateDirectories:YES attributes:nil error:nil]; + } + } + return _keymanDataDirectory; +} + +- (NSString *)keymanDataPath { + return _keymanDataDirectory; +} + +@end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index b7acec58e83..9afba1fa0a8 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -17,6 +17,7 @@ #import "KMInputMethodAppDelegate.h" #import "KMSettingsRepository.h" +#import "KMDataRepository.h" #import "KMConfigurationWindowController.h" #import "KMDownloadKBWindowController.h" #import "ZipArchive.h" @@ -763,10 +764,12 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { - (void)awakeFromNib { if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { os_log_info([KMLogs startupLog], "keyboards migration needed"); + [KMDataRepository.shared migrateResources]; } else { os_log_info([KMLogs startupLog], "keyboards migration not needed"); } + [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; } From bff17dc1c243b8fa92c1d3b51111110201ec31f5 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 31 Jul 2024 10:52:34 +0700 Subject: [PATCH 012/262] change(mac): customize menus after all views are created rather than reading from disk and adding keyboards include awakeFromNib, do so in applicationDidFinishLaunching --- mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 9afba1fa0a8..87a53824321 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -182,6 +182,9 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { }]; // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; + + [self setDefaultKeymanMenuItems]; + [self updateKeyboardMenuItems]; } #ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE @@ -769,9 +772,6 @@ - (void)awakeFromNib { os_log_info([KMLogs startupLog], "keyboards migration not needed"); } - - [self setDefaultKeymanMenuItems]; - [self updateKeyboardMenuItems]; } - (void)setDefaultKeymanMenuItems { From ec2e48c11ff16b12bcfe75ac59179fcf2e11a97c Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 31 Jul 2024 15:58:40 +0700 Subject: [PATCH 013/262] change(mac): moved directory locations into KMDataRepository --- .../Keyman4MacIM/KMDataRepository.h | 2 +- .../Keyman4MacIM/KMDataRepository.m | 105 +++++++++++++++--- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index ed1b8701fe4..53790a21524 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject + (KMDataRepository *)shared; - (void)migrateResources; -- (NSString *)keymanDataDirectory; +- (NSURL *)keymanDataDirectory; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index fa422472e5d..40f8858aee6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -14,10 +14,36 @@ #import "KMDataRepository.h" #import "KMLogs.h" +@interface KMDataRepository () +@property (readonly) NSURL *applicationSupportSubDirectory; // '~/Library/Application Support' +@property (readonly) NSURL *documentsSubDirectory; // '~/Documents' +@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' +@property (readonly) NSURL *keymanKeyboardsDirectory; +// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' +@property (readonly) NSURL *obsoleteKeymanDataDirectory; // '~/Library/Documents/Keyman-Keyboards' +@end + @implementation KMDataRepository +@synthesize applicationSupportSubDirectory = _applicationSupportSubDirectory; +@synthesize documentsSubDirectory = _documentsSubDirectory; +@synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; +@synthesize obsoleteKeymanDataDirectory = _obsoleteKeymanDataDirectory; +@synthesize keymanDataDirectory = _keymanDataDirectory; + +NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; +/* + name of the subdirectory within '~/Library/Application Support' + the convention is to use bundle identifier ("keyman.inputmethod.Keyman") + but we'll use this name, which matches our logging subsystem + */ +NSString *const kKeymanSubdirectoryName = @"com.keyman.app"; +/* NSString* _obsoleteKeymanDataDirectory = nil; NSString* _keymanDataDirectory = nil; +NSURL* _ApplicationSupportSubDirectory = nil; +NSURL* _DocumentsSubDirectory = nil; +*/ + (KMDataRepository *)shared { @@ -29,24 +55,69 @@ + (KMDataRepository *)shared return shared; } -- (void)migrateResources { - NSError *directoryError = nil; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; - - os_log_info([KMLogs startupLog], "applicationSupportDirectory: '%{public}@'", applicationSupportUrl); +- (NSURL *)documentsSubDirectory { + if (self.documentsSubDirectory == nil) { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *documentsUrl = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; + + if (directoryError) { + os_log_error([KMLogs startupLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "Documents subdirectory: '%{public}@'", documentsUrl); + _documentsSubDirectory = documentsUrl; + } + } + return self.documentsSubDirectory; +} - NSString *appId = [[NSBundle mainBundle] bundleIdentifier]; - os_log_info([KMLogs startupLog], "application bundleIdentifier: '%{public}@'", appId); - - NSURL *keymanUrl = [applicationSupportUrl URLByAppendingPathComponent: appId isDirectory: TRUE]; +- (NSURL *)applicationSupportSubDirectory { + if (self.applicationSupportSubDirectory == nil) { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; - //NSURL *keymanUrl = [NSURL fileURLWithPath:appId isDirectory:YES relativeToURL:applicationSupportUrl]; + if (directoryError) { + os_log_error([KMLogs startupLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl); + _applicationSupportSubDirectory = applicationSupportUrl; + } + } + return self.applicationSupportSubDirectory; +} + +- (NSURL *)keymanDataDirectory { + if (self.keymanDataDirectory == nil) { + NSURL *keymanDataUrl = [self.applicationSupportSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; + _keymanDataDirectory = keymanDataUrl; + } + return self.keymanDataDirectory; +} + +- (NSURL *)keymanKeyboardsDirectory { + if (self.keymanKeyboardsDirectory == nil) { + NSURL *keyboardsUrl = [self.keymanDataDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; + _keymanKeyboardsDirectory = keyboardsUrl; + } + return self.keymanKeyboardsDirectory; +} + +- (NSURL *)obsoleteKeymanDataDirectory { + if (self.obsoleteKeymanDataDirectory == nil) { + NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; + _obsoleteKeymanDataDirectory = keymanUrl; + } + return self.obsoleteKeymanDataDirectory; +} - os_log_info([KMLogs startupLog], "keymanUrl: '%{public}@'", keymanUrl); - // returns -> '/Users/sgschantz/Library/Application Support' +- (void)migrateResources { + os_log_info([KMLogs startupLog], "keymanKeyboardsDirectory: '%{public}@'", self.keymanKeyboardsDirectory); + os_log_info([KMLogs startupLog], "obsoleteKeymanDataDirectory: '%{public}@'", self.obsoleteKeymanDataDirectory); + /* BOOL isDir; BOOL exists = [fileManager fileExistsAtPath:keymanUrl.path isDirectory:&isDir]; @@ -58,11 +129,13 @@ - (void)migrateResources { } else { os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' does not exist", keymanUrl); } + */ } /** * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards */ +/* - (NSString *)_obsoleteKeymanDataDirectory { if(_keymanDataDirectory == nil) { NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; @@ -78,8 +151,8 @@ - (NSString *)_obsoleteKeymanDataDirectory { return _keymanDataDirectory; } -- (NSString *)keymanDataPath { +- (NSString *)keymanDataDirectory { return _keymanDataDirectory; } - +*/ @end From 4fb5d74f496bfb102eb6e47e8c199db2415ca26d Mon Sep 17 00:00:00 2001 From: sgschantz Date: Thu, 1 Aug 2024 17:00:07 +0700 Subject: [PATCH 014/262] change(mac): implemented data migration and settings path updates --- .../Keyman4MacIM/KMDataRepository.h | 7 +- .../Keyman4MacIM/KMDataRepository.m | 112 +++++++++++------- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 29 +++-- .../Keyman4MacIM/KMSettingsRepository.h | 4 +- .../Keyman4MacIM/KMSettingsRepository.m | 103 ++++++++++++++-- 5 files changed, 194 insertions(+), 61 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 53790a21524..836b8b742b4 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -13,9 +13,12 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject +@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' +@property (readonly) NSURL *keymanKeyboardsDirectory; +// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' + (KMDataRepository *)shared; -- (void)migrateResources; -- (NSURL *)keymanDataDirectory; +- (void)createDataDirectoriesIfNecessary; +- (BOOL)migrateData; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 40f8858aee6..2143d8b7f50 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -17,10 +17,7 @@ @interface KMDataRepository () @property (readonly) NSURL *applicationSupportSubDirectory; // '~/Library/Application Support' @property (readonly) NSURL *documentsSubDirectory; // '~/Documents' -@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' -@property (readonly) NSURL *keymanKeyboardsDirectory; -// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' -@property (readonly) NSURL *obsoleteKeymanDataDirectory; // '~/Library/Documents/Keyman-Keyboards' +@property (readonly) NSURL *obsoleteKeymanKeyboardsDirectory; // '~/Library/Documents/Keyman-Keyboards' @end @implementation KMDataRepository @@ -28,22 +25,17 @@ @implementation KMDataRepository @synthesize applicationSupportSubDirectory = _applicationSupportSubDirectory; @synthesize documentsSubDirectory = _documentsSubDirectory; @synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; -@synthesize obsoleteKeymanDataDirectory = _obsoleteKeymanDataDirectory; +@synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; @synthesize keymanDataDirectory = _keymanDataDirectory; NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; /* - name of the subdirectory within '~/Library/Application Support' - the convention is to use bundle identifier ("keyman.inputmethod.Keyman") - but we'll use this name, which matches our logging subsystem + The name of the subdirectory within '~/Library/Application Support'. + The convention is to use bundle identifier. + (Using the subsystem id, "com.keyman.app", is a poor choice because the API + createDirectoryAtPath sees the .app extension and creates an application file. */ -NSString *const kKeymanSubdirectoryName = @"com.keyman.app"; -/* -NSString* _obsoleteKeymanDataDirectory = nil; -NSString* _keymanDataDirectory = nil; -NSURL* _ApplicationSupportSubDirectory = nil; -NSURL* _DocumentsSubDirectory = nil; -*/ +NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; + (KMDataRepository *)shared { @@ -56,7 +48,7 @@ + (KMDataRepository *)shared } - (NSURL *)documentsSubDirectory { - if (self.documentsSubDirectory == nil) { + if (_documentsSubDirectory == nil) { NSError *directoryError = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -69,11 +61,11 @@ - (NSURL *)documentsSubDirectory { _documentsSubDirectory = documentsUrl; } } - return self.documentsSubDirectory; + return _documentsSubDirectory; } - (NSURL *)applicationSupportSubDirectory { - if (self.applicationSupportSubDirectory == nil) { + if (_applicationSupportSubDirectory == nil) { NSError *directoryError = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -86,50 +78,88 @@ - (NSURL *)applicationSupportSubDirectory { _applicationSupportSubDirectory = applicationSupportUrl; } } - return self.applicationSupportSubDirectory; + return _applicationSupportSubDirectory; } - (NSURL *)keymanDataDirectory { - if (self.keymanDataDirectory == nil) { + if (_keymanDataDirectory == nil) { NSURL *keymanDataUrl = [self.applicationSupportSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; _keymanDataDirectory = keymanDataUrl; } - return self.keymanDataDirectory; + return _keymanDataDirectory; } - (NSURL *)keymanKeyboardsDirectory { - if (self.keymanKeyboardsDirectory == nil) { + if (_keymanKeyboardsDirectory == nil) { NSURL *keyboardsUrl = [self.keymanDataDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; _keymanKeyboardsDirectory = keyboardsUrl; } - return self.keymanKeyboardsDirectory; + return _keymanKeyboardsDirectory; } -- (NSURL *)obsoleteKeymanDataDirectory { - if (self.obsoleteKeymanDataDirectory == nil) { - NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; - _obsoleteKeymanDataDirectory = keymanUrl; +/** + * creates Keyman data directories if they do not exist yet + * This includes 1) the main data subdirectory: keyman.inputmethod.Keyman + * and 2) its subdirectory, Keyman-Keyboards + * + */ +- (void)createDataDirectoriesIfNecessary { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]; + + if (!exists) { + NSError *createError = nil; + [fileManager createDirectoryAtPath:self.keymanKeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; + if (createError) { + os_log_error([KMLogs startupLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); + } + } else { + os_log_info([KMLogs startupLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); } - return self.obsoleteKeymanDataDirectory; } -- (void)migrateResources { - os_log_info([KMLogs startupLog], "keymanKeyboardsDirectory: '%{public}@'", self.keymanKeyboardsDirectory); - os_log_info([KMLogs startupLog], "obsoleteKeymanDataDirectory: '%{public}@'", self.obsoleteKeymanDataDirectory); +- (NSURL *)obsoleteKeymanKeyboardsDirectory { + if (_obsoleteKeymanKeyboardsDirectory == nil) { + NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; + _obsoleteKeymanKeyboardsDirectory = keymanUrl; + } + return _obsoleteKeymanKeyboardsDirectory; +} - /* +- (BOOL)keyboardsExistInDocumentsFolder { + NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; - BOOL exists = [fileManager fileExistsAtPath:keymanUrl.path isDirectory:&isDir]; + BOOL exists = ([fileManager fileExistsAtPath:self.obsoleteKeymanKeyboardsDirectory.path isDirectory:&isDir]); + return exists; +} + +- (BOOL)migrateData { + BOOL didMoveData = NO; + NSString *obsoleteKeymanKeyboardsDirectory = self.obsoleteKeymanKeyboardsDirectory.path; + NSString *dataDirectory = self.keymanDataDirectory.path; + os_log_info([KMLogs startupLog], "migrateData, move obsoleteKeymanKeyboardsDirectory: '%{public}@' to '%{public}@'", obsoleteKeymanKeyboardsDirectory, dataDirectory); + + // delete, happens whether migration or not + //[self createKeyboardsDirectoriesIfNecessary]; - if (exists) { - os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' exists", keymanUrl); - if (isDir) { - os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' is a directory", keymanUrl); - } - } else { - os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' does not exist", keymanUrl); + BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; + os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + + if (dataExistsInOldLocation) { + NSError *moveError = nil; + NSFileManager *fileManager = [NSFileManager defaultManager]; + didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory + toURL:self.keymanDataDirectory + error:&moveError]; + if (moveError) { + os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); + } } - */ + + return didMoveData; } /** diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 87a53824321..ecd3e659e82 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -511,6 +511,7 @@ - (BOOL)useVerboseLogging { * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards */ - (NSString *)keymanDataPath { + /* if(_keymanDataPath == nil) { NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; _keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; @@ -523,18 +524,22 @@ - (NSString *)keymanDataPath { } } return _keymanDataPath; + */ + return [KMDataRepository shared].keymanDataDirectory.path; } /** * Returns the root folder where keyboards are stored; currently the same * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) + * + * Actually divering now, get this from KMDataRepository */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { _keyboardsPath = [self keymanDataPath]; } - return _keyboardsPath; + return [KMDataRepository shared].keymanKeyboardsDirectory.path; } - (NSArray *)kmxFileList { @@ -765,13 +770,23 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { - if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { - os_log_info([KMLogs startupLog], "keyboards migration needed"); - [KMDataRepository.shared migrateResources]; - } else { - os_log_info([KMLogs startupLog], "keyboards migration not needed"); - } + [self preparePersistence]; +} +/** + * Prepare the app for all the things that need to be persisted: + * namely, the settings in UserDefaults and keyboard data on disk + */ +- (void)preparePersistence { + [KMDataRepository.shared createDataDirectoriesIfNecessary]; + + if ([KMSettingsRepository.shared dataMigrationNeeded]) { + BOOL movedData = [KMDataRepository.shared migrateData]; + //os_log_info([KMLogs startupLog], "test: call migrateData again"); + //[KMDataRepository.shared migrateData]; + [KMSettingsRepository.shared convertSettingsForMigration]; + } + [KMSettingsRepository.shared createStorageFlagIfNecessary]; } - (void)setDefaultKeymanMenuItems { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index e4b9f1c158a..9a2fcb4c413 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -14,7 +14,9 @@ NS_ASSUME_NONNULL_BEGIN @interface KMSettingsRepository : NSObject + (KMSettingsRepository *)shared; -- (BOOL)keyboardsMigrationNeeded; +- (BOOL)dataMigrationNeeded; +- (void)convertSettingsForMigration; +- (void)createStorageFlagIfNecessary; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index d5a26cccac7..84d6623d2c9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -13,8 +13,12 @@ #import "KMSettingsRepository.h" #import "KMLogs.h" -NSString *const kStoreKeyboardsInLibraryKey = @"KMStoreKeyboardsInLibraryKey"; +NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibraryKey"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; +NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; + +NSString *const kObsoletePathComponent = @"/Documents/"; +NSString *const kNewPathComponent = @"/Application Support/keyman.inputmethod.Keyman/"; @implementation KMSettingsRepository @@ -28,30 +32,109 @@ + (KMSettingsRepository *)shared return shared; } +- (void)createStorageFlagIfNecessary { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kStoreDataInLibraryKey]; +} + - (BOOL)settingsExist { return [[NSUserDefaults standardUserDefaults] objectForKey:kActiveKeyboardsKey] != nil; } -- (BOOL)keyboardsStoredInLibraryFolder +- (BOOL)dataStoredInLibraryDirectory { - return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreKeyboardsInLibraryKey]; + return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreDataInLibraryKey]; } /** - * Determines whether the keyboards data needs to be moved from the old location in the Documents folder to the new location under /username/Library... + * Determines whether the keyboard data needs to be moved from the old location in ~/Documents to the new location ~/Library... * This is true if * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and * 2) the value for KMStoreKeyboardsInLibraryKey is not set to true */ -- (BOOL)keyboardsMigrationNeeded { - BOOL keyboardSettingsExist = [self settingsExist]; - os_log([KMLogs startupLog], " keyboard settings exist: %@", keyboardSettingsExist ? @"YES" : @"NO" ); +- (BOOL)dataMigrationNeeded { + BOOL keymanSettingsExist = [self settingsExist]; + os_log([KMLogs startupLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); - BOOL keyboardsInLibrary = [self keyboardsStoredInLibraryFolder]; - os_log([KMLogs startupLog], " keyboards stored in Library: %@", keyboardsInLibrary ? @"YES" : @"NO" ); + BOOL dataInLibrary = [self dataStoredInLibraryDirectory]; + os_log([KMLogs startupLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); - return !(keyboardSettingsExist && keyboardsInLibrary); + return !(keymanSettingsExist && dataInLibrary); +} + +- (void)convertSettingsForMigration { + [self convertSelectedKeyboardPathForMigration]; + [self convertActiveKeyboardArrayForMigration]; +} + +- (void)convertSelectedKeyboardPathForMigration { + NSString *selectedKeyboardPath = [self selectedKeyboard]; + + if (selectedKeyboardPath != nil) { + NSString *newPathString = [self convertOldKeyboardPath:selectedKeyboardPath]; + + if ([selectedKeyboardPath isNotEqualTo:newPathString]) { + [self saveSelectedKeyboard:newPathString]; + os_log([KMLogs startupLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); + } + } +} + +/** + * Convert the path of the keyboard designating the Documents folder to its new location + * in the Application Support folder + */ + +- (NSString *)convertOldKeyboardPath:(NSString *)oldPath { + NSString *newPathString = @""; + if(oldPath != nil) { + newPathString = [oldPath stringByReplacingOccurrencesOfString:kObsoletePathComponent withString:kNewPathComponent]; + } + return newPathString; } +- (NSString *)selectedKeyboard { + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; +} + +- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; +} + +- (void)convertActiveKeyboardArrayForMigration { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + NSMutableArray *activeKeyboards = [self activeKeyboards]; + NSMutableArray *convertedActiveKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + BOOL didConvert = NO; + + for (NSString *oldPath in activeKeyboards) { + NSString *newPath = [self convertOldKeyboardPath:oldPath]; + if ([oldPath isNotEqualTo:newPath]) { + [convertedActiveKeyboards addObject:newPath]; + os_log([KMLogs startupLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); + // if we have adjusted at least one path, set flag + didConvert = YES; + } else { + // if, somehow, the path does not need converting then retain it in new array + [convertedActiveKeyboards addObject:oldPath]; + } + } + + // only update array in UserDefaults if we actually converted something + if (didConvert) { + [[NSUserDefaults standardUserDefaults] setObject:convertedActiveKeyboards forKey:kActiveKeyboardsKey]; + } +} + +- (NSMutableArray *)activeKeyboards { + NSMutableArray * activeKeyboards = [[[NSUserDefaults standardUserDefaults] arrayForKey:kActiveKeyboardsKey] mutableCopy]; + + if (!activeKeyboards) { + activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + } + return activeKeyboards; +} + + @end From 13fdfe9f5480d2350999828ad7dae7161f9fe2bb Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:33:38 +1000 Subject: [PATCH 015/262] fix(windows): review comment suggestions Co-authored-by: Eberhard Beilharz --- windows/src/engine/keyman32/calldll.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/windows/src/engine/keyman32/calldll.cpp b/windows/src/engine/keyman32/calldll.cpp index adab1b7cac2..59ce4d057ed 100644 --- a/windows/src/engine/keyman32/calldll.cpp +++ b/windows/src/engine/keyman32/calldll.cpp @@ -439,11 +439,11 @@ extern "C" BOOL _declspec(dllexport) WINAPI KMDisplayIM(HWND hwnd, BOOL FShowAlw *Globals::hwndIMAlways() = FShowAlways; POINT IMTop, Caret; - RECT RectIM, RectApp; - HWND hFocus; - int n = 0; + RECT RectIM, RectApp; + HWND hFocus; + int n = 0; - HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (hMonitor == NULL) { return FALSE; } @@ -459,9 +459,9 @@ extern "C" BOOL _declspec(dllexport) WINAPI KMDisplayIM(HWND hwnd, BOOL FShowAlw } // Only adjust the IM window coordinates if it is not going to be visible on the - // monitor it is currently showing on. Otherwise leave it to the 3rd paryt app + // monitor it is currently showing on. Otherwise leave it to the 3rd party app // to control the location of the IM Window. - // If changing the coordinates place the IM window use the caret postion of the + // If changing the coordinates to place the IM window use the caret postion of the // application that has focus. if ((RectIM.left < monitor_info.rcMonitor.left) || (RectIM.right > monitor_info.rcMonitor.right) || (RectIM.top < monitor_info.rcMonitor.top) || (RectIM.bottom > monitor_info.rcMonitor.bottom)) { From 336db7b5c5d4af80c6dc22a86dcc90a481c3a206 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:15:33 +1000 Subject: [PATCH 016/262] fix(windows): handle IM bigger than app window --- windows/src/engine/keyman32/calldll.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/windows/src/engine/keyman32/calldll.cpp b/windows/src/engine/keyman32/calldll.cpp index 59ce4d057ed..2c93b0584bc 100644 --- a/windows/src/engine/keyman32/calldll.cpp +++ b/windows/src/engine/keyman32/calldll.cpp @@ -477,9 +477,16 @@ extern "C" BOOL _declspec(dllexport) WINAPI KMDisplayIM(HWND hwnd, BOOL FShowAlw IMTop.x = Caret.x + RectApp.left; IMTop.y = Caret.y + RectApp.top + 24; + LONG IMWidth = RectIM.right - RectIM.left; - if (IMTop.x + RectIM.right - RectIM.left > RectApp.right) { - IMTop.x = RectApp.right - (RectIM.right - RectIM.left); + if (IMTop.x + IMWidth > RectApp.right) { + // align IM right edge with App right edge + if (IMWidth < (RectApp.right - RectApp.left)) { + IMTop.x = RectApp.right - IMWidth; + } + else { // align IM left edge with App left edge + IMTop.x = RectApp.left; + } } if (IMTop.y + RectIM.bottom - RectIM.top > RectApp.bottom) { From c2e2cf53c61571d05883a3c6db904bbd89d140bb Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 2 Aug 2024 15:21:56 +0700 Subject: [PATCH 017/262] change(mac): successful migration but not loading from config --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 4 ++ .../Keyman4MacIM/KMDataRepository.h | 3 +- .../Keyman4MacIM/KMDataRepository.m | 49 ++++++++++++++----- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 12 +++-- .../Keyman4MacIM/KMPackageReader.m | 3 ++ .../Keyman4MacIM/KMSettingsRepository.m | 5 +- .../KeymanTests/InputMethodTests.m | 17 +++++++ 7 files changed, 74 insertions(+), 19 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 9d63aac4685..8f49e2e0f24 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ 29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29B42A622728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib */; }; 29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B4A0D32BF7675A00682049 /* KMLogs.m */; }; 29B6FB732BC39DD60074BF7F /* TextApiComplianceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B6FB722BC39DD60074BF7F /* TextApiComplianceTests.m */; }; + 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; + 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; 37C2B0CB25FF2C350092E16A /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 37C2B0CA25FF2C340092E16A /* Help */; }; @@ -1017,6 +1019,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */, + 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */, 2915DC512BFE35DB0051FC52 /* KMLogs.m in Sources */, 2992F4202A28482800E08929 /* PrivacyWindowController.m in Sources */, 2992F41F2A2847C900E08929 /* TextApiCompliance.m in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 836b8b742b4..a5af2ccb881 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -17,7 +17,8 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly) NSURL *keymanKeyboardsDirectory; // keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' + (KMDataRepository *)shared; -- (void)createDataDirectoriesIfNecessary; +- (void)createDataDirectoryIfNecessary; +- (void)createKeyboardsDirectoryIfNecessary; - (BOOL)migrateData; @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 2143d8b7f50..3d0d0bacd7e 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -74,7 +74,7 @@ - (NSURL *)applicationSupportSubDirectory { if (directoryError) { os_log_error([KMLogs startupLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl); + os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); _applicationSupportSubDirectory = applicationSupportUrl; } } @@ -98,18 +98,39 @@ - (NSURL *)keymanKeyboardsDirectory { } /** - * creates Keyman data directories if they do not exist yet - * This includes 1) the main data subdirectory: keyman.inputmethod.Keyman - * and 2) its subdirectory, Keyman-Keyboards - * + * Creates Keyman data directory if it do not exist yet. This is the main data subdirectory: keyman.inputmethod.Keyman + */ +- (void)createDataDirectoryIfNecessary { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:self.keymanDataDirectory.path isDirectory:&isDir]; + + if (!exists) { + NSError *createError = nil; + os_log_info([KMLogs startupLog], "createDataDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanDataDirectory.path); + [fileManager createDirectoryAtPath:self.keymanDataDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; + if (createError) { + os_log_error([KMLogs startupLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); + } + } else { + os_log_info([KMLogs startupLog], "Keyman data directory already exists: '%{public}@'", self.keymanDataDirectory.path); + } +} + +/** + * Creates Keyman keyboard directory if it does not exist yet. This is the 'Keyman-Keyboards' directory. + * It should not be created until after migrating because its existence would block migrating data from the old location. */ -- (void)createDataDirectoriesIfNecessary { +- (void)createKeyboardsDirectoryIfNecessary { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; BOOL exists = [fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]; if (!exists) { NSError *createError = nil; + os_log_info([KMLogs startupLog], "createKeyboardsDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanKeyboardsDirectory.path); [fileManager createDirectoryAtPath:self.keymanKeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; if (createError) { os_log_error([KMLogs startupLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); @@ -138,24 +159,30 @@ - (BOOL)keyboardsExistInDocumentsFolder { - (BOOL)migrateData { BOOL didMoveData = NO; + NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *obsoleteKeymanKeyboardsDirectory = self.obsoleteKeymanKeyboardsDirectory.path; NSString *dataDirectory = self.keymanDataDirectory.path; os_log_info([KMLogs startupLog], "migrateData, move obsoleteKeymanKeyboardsDirectory: '%{public}@' to '%{public}@'", obsoleteKeymanKeyboardsDirectory, dataDirectory); - // delete, happens whether migration or not - //[self createKeyboardsDirectoriesIfNecessary]; - + BOOL isDir; + BOOL dataDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanDataDirectory.path isDirectory:&isDir]); + os_log([KMLogs startupLog], "data directory exists in new location, %{public}@: %{public}@", self.keymanDataDirectory.path, dataDirectoryExistsInNewLocation?@"YES":@"NO"); + + BOOL keyboardsDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]); + os_log([KMLogs startupLog], "keyboards directory exists in new location, %{public}@: %{public}@", self.keymanKeyboardsDirectory.path, keyboardsDirectoryExistsInNewLocation?@"YES":@"NO"); + BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); if (dataExistsInOldLocation) { NSError *moveError = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory - toURL:self.keymanDataDirectory + toURL:self.keymanKeyboardsDirectory error:&moveError]; if (moveError) { os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); + } else { + os_log_error([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index ecd3e659e82..8352bf2a594 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -182,7 +182,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { }]; // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; - + [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; } @@ -532,7 +532,7 @@ - (NSString *)keymanDataPath { * Returns the root folder where keyboards are stored; currently the same * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) * - * Actually divering now, get this from KMDataRepository + * Actually diverting now, get this from KMDataRepository */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { @@ -774,11 +774,11 @@ - (void)awakeFromNib { } /** - * Prepare the app for all the things that need to be persisted: - * namely, the settings in UserDefaults and keyboard data on disk + * Prepare the app environment for all the things that need to be persisted: + * namely, the keyboard data on disk and the settings in UserDefaults */ - (void)preparePersistence { - [KMDataRepository.shared createDataDirectoriesIfNecessary]; + [KMDataRepository.shared createDataDirectoryIfNecessary]; if ([KMSettingsRepository.shared dataMigrationNeeded]) { BOOL movedData = [KMDataRepository.shared migrateData]; @@ -786,6 +786,8 @@ - (void)preparePersistence { //[KMDataRepository.shared migrateData]; [KMSettingsRepository.shared convertSettingsForMigration]; } + + [KMDataRepository.shared createKeyboardsDirectoryIfNecessary]; [KMSettingsRepository.shared createStorageFlagIfNecessary]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m index 7406488a8a0..186ec8ab5a0 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -69,6 +69,8 @@ - (KMPackageInfo *)loadPackageInfo:(NSString *)path { return packageInfo; } +/* + // TODO: not used, delete - (NSString *)packageNameFromPackageInfo:(NSString *)path { NSString *packageName = nil; @@ -77,6 +79,7 @@ - (NSString *)packageNameFromPackageInfo:(NSString *)path { return packageName; } +*/ /** * read JSON file and load it into KMPackageInfo object diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 84d6623d2c9..d0776e01fc2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -11,14 +11,15 @@ */ #import "KMSettingsRepository.h" +//#import "KMDataRepository.h" #import "KMLogs.h" -NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibraryKey"; +NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibrary"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kObsoletePathComponent = @"/Documents/"; -NSString *const kNewPathComponent = @"/Application Support/keyman.inputmethod.Keyman/"; +NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; @implementation KMSettingsRepository diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 8eb9d974ceb..38994c351a5 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -13,6 +13,9 @@ #import "KMInputMethodEventHandler.h" #import "AppleCompliantTestClient.h" #import "TextApiCompliance.h" +#import "KMSettingsRepository.h" +#import "KMDataRepository.h" +#import KMInputMethodEventHandler *testEventHandler = nil; @@ -85,4 +88,18 @@ - (void)testCalculateInsertRange_deleteOneBMPCharacterWithOneSelected_returnsRan XCTAssertTrue(correctResult, @"insert or replacement range expected to be {1,2}"); } +- (void)testMigrateData_oldDataExists_logsLocations { + os_log_t startupLog = os_log_create("com.keyman.app", "data-migration"); + if ([KMSettingsRepository.shared dataMigrationNeeded]) { + os_log_info(startupLog, "data migration needed, calling migrateData"); + [KMDataRepository.shared migrateData]; + os_log_info(startupLog, "test: call migrateData again"); + [KMDataRepository.shared migrateData]; + } else { + os_log_info(startupLog, "data migration not needed"); + } + + XCTAssertTrue(YES, @"test failed"); +} + @end From b435090a6fba6f9c29c15657e5ecef646b537017 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Mon, 5 Aug 2024 16:43:02 +0700 Subject: [PATCH 018/262] change(mac): migrates options and changes keyboards successfully --- .../Keyman4MacIM/KMDataRepository.m | 40 ++------------- .../Keyman4MacIM/KMInputMethodAppDelegate.h | 1 - .../Keyman4MacIM/KMInputMethodAppDelegate.m | 33 ++----------- .../Keyman4MacIM/KMSettingsRepository.m | 49 +++++++++++++++++++ 4 files changed, 58 insertions(+), 65 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 3d0d0bacd7e..ac2d678ce57 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -157,23 +157,16 @@ - (BOOL)keyboardsExistInDocumentsFolder { return exists; } +/** + * Migrate the keyboards data from the old location in '/Documents' to the new location '/Application Support/keyman.inputmethod.Keyman/' + */ - (BOOL)migrateData { BOOL didMoveData = NO; NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *obsoleteKeymanKeyboardsDirectory = self.obsoleteKeymanKeyboardsDirectory.path; - NSString *dataDirectory = self.keymanDataDirectory.path; - os_log_info([KMLogs startupLog], "migrateData, move obsoleteKeymanKeyboardsDirectory: '%{public}@' to '%{public}@'", obsoleteKeymanKeyboardsDirectory, dataDirectory); - - BOOL isDir; - BOOL dataDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanDataDirectory.path isDirectory:&isDir]); - os_log([KMLogs startupLog], "data directory exists in new location, %{public}@: %{public}@", self.keymanDataDirectory.path, dataDirectoryExistsInNewLocation?@"YES":@"NO"); - - BOOL keyboardsDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]); - os_log([KMLogs startupLog], "keyboards directory exists in new location, %{public}@: %{public}@", self.keymanKeyboardsDirectory.path, keyboardsDirectoryExistsInNewLocation?@"YES":@"NO"); - BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + // only move data if there is something there to move if (dataExistsInOldLocation) { NSError *moveError = nil; didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory @@ -182,34 +175,11 @@ - (BOOL)migrateData { if (moveError) { os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); } else { - os_log_error([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); } } return didMoveData; } -/** - * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards - */ -/* -- (NSString *)_obsoleteKeymanDataDirectory { - if(_keymanDataDirectory == nil) { - NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - _keymanDataDirectory = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; - - os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataDirectory); - - NSFileManager *fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_keymanDataDirectory]) { - [fm createDirectoryAtPath:_keymanDataDirectory withIntermediateDirectories:YES attributes:nil error:nil]; - } - } - return _keymanDataDirectory; -} - -- (NSString *)keymanDataDirectory { - return _keymanDataDirectory; -} -*/ @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 4bf463c177f..7eddf31cb51 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -126,7 +126,6 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; - (NSString *)oskWindowTitle; - (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) virtualKey postCallback:(PostEventCallback)postEvent; - (KeymanVersionInfo)versionInfo; -- (NSString *)keymanDataPath; - (void)registerConfigurationWindow:(NSWindowController *)window; @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 8352bf2a594..fb7edaad012 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -85,7 +85,6 @@ @implementation KMInputMethodAppDelegate @synthesize alwaysShowOSK = _alwaysShowOSK; id _lastServerWithOSKShowing = nil; -NSString* _keymanDataPath = nil; - (id)init { self = [super init]; @@ -508,38 +507,14 @@ - (BOOL)useVerboseLogging { #pragma mark - Keyman Data /** - * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards - */ -- (NSString *)keymanDataPath { - /* - if(_keymanDataPath == nil) { - NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - _keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; - - os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataPath); - - NSFileManager *fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_keymanDataPath]) { - [fm createDirectoryAtPath:_keymanDataPath withIntermediateDirectories:YES attributes:nil error:nil]; - } - } - return _keymanDataPath; - */ - return [KMDataRepository shared].keymanDataDirectory.path; -} - -/** - * Returns the root folder where keyboards are stored; currently the same - * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) - * - * Actually diverting now, get this from KMDataRepository + * Returns the root folder where keyboards are stored */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { - _keyboardsPath = [self keymanDataPath]; + _keyboardsPath = [KMDataRepository shared].keymanKeyboardsDirectory.path; } - return [KMDataRepository shared].keymanKeyboardsDirectory.path; + return _keyboardsPath; } - (NSArray *)kmxFileList { @@ -652,7 +627,7 @@ - (KMPackageInfo *)loadPackageInfo:(NSString *)path { - (NSString *)packageNameFromPackageInfo:(NSString *)packageFolder { NSString *packageName = nil; - NSString *path = [[self keymanDataPath] stringByAppendingPathComponent:packageFolder]; + NSString *path = [[self keyboardsPath] stringByAppendingPathComponent:packageFolder]; KMPackageInfo *packageInfo = [self.packageReader loadPackageInfo:path]; if (packageInfo) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index d0776e01fc2..5381651e313 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -17,6 +17,7 @@ NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibrary"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; +NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; NSString *const kObsoletePathComponent = @"/Documents/"; NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; @@ -66,6 +67,7 @@ - (BOOL)dataMigrationNeeded { - (void)convertSettingsForMigration { [self convertSelectedKeyboardPathForMigration]; [self convertActiveKeyboardArrayForMigration]; + [self convertPersistedOptionsPathsForMigration]; } - (void)convertSelectedKeyboardPathForMigration { @@ -137,5 +139,52 @@ - (NSMutableArray *)activeKeyboards { return activeKeyboards; } +- (void)convertPersistedOptionsPathsForMigration { + NSDictionary * optionsMap = [self persistedOptions]; + NSMutableDictionary *mutableOptionsMap = nil; + BOOL optionsChanged = NO; + + if (optionsMap != nil) { + os_log_info([KMLogs configLog], "optionsMap != nil"); + mutableOptionsMap = [[NSMutableDictionary alloc] initWithCapacity:0]; + for(id key in optionsMap) { + os_log_info([KMLogs configLog], "persisted options found in UserDefaults with key = %{public}@", key); + } + for (NSString *key in optionsMap) { + NSString *newPathString = [self convertOldKeyboardPath:key]; + NSDictionary *optionsValue = [optionsMap objectForKey:key]; + + if ([key isNotEqualTo:newPathString]) { + optionsChanged = YES; + + // insert options into new map with newly converted path as key + [mutableOptionsMap setObject:optionsValue forKey:newPathString]; + os_log([KMLogs startupLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); + } else { + // retain options that did not need converting + [mutableOptionsMap setObject:optionsValue forKey:key]; + } + } + if (optionsChanged) { + [self savePersistedOptions:mutableOptionsMap]; + } + } +} + +- (NSDictionary *)persistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData dictionaryForKey:kPersistedOptionsKey]; +} + +- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; +} + +- (void)removePersistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData removeObjectForKey:kPersistedOptionsKey]; +} + @end From cc023dd5af8ad9b196bbad5b0281929209f33570 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 6 Aug 2024 13:36:30 +0700 Subject: [PATCH 019/262] chore(common/models): reverts unneeded addition of zlib --- common/models/wordbreakers/package.json | 3 +- .../wordbreakers/src/data-compiler/index.ts | 71 +++++++++++++------ package-lock.json | 13 +--- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 0ca47c6f219..c2b295ec421 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -60,8 +60,7 @@ "c8": "^7.12.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^5.4.5", - "zlib": "^1.0.5" + "typescript": "^5.4.5" }, "type": "module" } diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts index e217bc51f15..3ef1dc1fc79 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -4,10 +4,8 @@ // TODO: Adapt to produce two string-encoded arrays - one for BMP chars, one for non-BMP chars. -import zlib from 'zlib'; import fs from 'fs'; import path from 'path'; -import { fileURLToPath } from 'url'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); @@ -27,13 +25,13 @@ const require = createRequire(import.meta.url); const MAX_CODE_POINT = 0x10FFFF; // Where to get the data: -// - http://www.unicode.org/reports/tr51/#emoji_data -// - https://www.unicode.org/reports/tr41/tr41-26.html#Props0 +// - https://www.unicode.org/reports/tr51/#emoji_data +// - https://www.unicode.org/reports/tr41/#Props0 //////////////////////////////////// Main //////////////////////////////////// const projectDir = path.dirname(require.resolve("@keymanapp/models-wordbreakers/UNICODE_VERSION.md")); -const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data_test.ts'); +const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data.ts'); // Ensure this package's major version number is in sync with the Unicode // major version. @@ -43,14 +41,20 @@ const UNICODE_VERSION = packageVersion.split('.')[0] + '.0.0'; // The data files should be in this repository, with names matching the // Unicode version. -let wordBoundaryFilename = path.join(projectDir, `./src/imports/WordBreakProperty-${UNICODE_VERSION}.txt.gz`); -let emojiDataFilename = path.join(projectDir, `./src/imports/emoji-data-${UNICODE_VERSION}.txt.gz`); +let wordBoundaryFilename = path.join(projectDir, `./src/imports/WordBreakProperty-${UNICODE_VERSION}.txt`); +let emojiDataFilename = path.join(projectDir, `./src/imports/emoji-data-${UNICODE_VERSION}.txt`); ///////////////////////////// Word_Boundary file ///////////////////////////// +interface DataRange { + start: number; + end: number; + property: string; +} + // Extract the ranges IN ASCENDING ORDER from the file. // This will be the big binary search table. -let ranges = readZippedCharacterPropertyFile(wordBoundaryFilename) +let ranges = readCharacterPropertyFile(wordBoundaryFilename) .sort((a, b) => { return a.start - b.start; }); @@ -71,22 +75,22 @@ categories.add('eot'); ///////////////////////// Extended_Pictographic=Yes ////////////////////////// -let extendedPictographicCodePoints = readZippedCharacterPropertyFile(emojiDataFilename) +let extendedPictographicCodePoints = readCharacterPropertyFile(emojiDataFilename) .filter(({property}) => property === 'Extended_Pictographic'); // Try generating the regular expression both in a way that is // backwards-compatbile and one that only works in ES6+. -let extendedPictographicRegExp; +// let extendedPictographicRegExp; let compatibleRegexp = utf16AlternativesStrategy(); let es6Regexp = unicodeRangeStrategy(); // Choose the shortest regular expression. // In my experience, the ES6 regexp is an order of magnitude smaller! if (es6Regexp.length < compatibleRegexp.length) { - extendedPictographicRegExp = es6Regexp; + // extendedPictographicRegExp = es6Regexp; console.warn(`Using ES6 regexp [${es6Regexp.length} chars]`); } else { - extendedPictographicRegExp = compatibleRegexp; + // extendedPictographicRegExp = compatibleRegexp; console.warn(`Using compatibility regexp [${compatibleRegexp.length} chars]`); } @@ -110,6 +114,9 @@ stream.write(`// Automatically generated file. DO NOT MODIFY. /** * Valid values for a word break property. + * + * Is optimized away at compile-time; use \`propertyMap\` to find the mapped + * value at runtime for a property name if needed. */ export const enum WordBreakProperty { ${ /* Create enum values for each word break property */ @@ -119,6 +126,26 @@ ${ /* Create enum values for each word break property */ } }; +/** + * Contains property names per associated index, as this is compiled away + * by TypeScript for \`const enum\` cases like \`WordBreakProperty\`. + */ +export const propertyMap = [ +${ /* Enumerate the plain-text names for ease of lookup at runtime */ + Array.from(categories) + .map(x => ` "${x}"`) + .join(',\n') +} +]; + +/** + * Constants for indexing values in WORD_BREAK_PROPERTY. + */ +export const enum I { + Start = 0, + Value = 1 +} + export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ ${ // TODO: Two versions: one that's BMP-encoded, one that's non-BMP encoded. @@ -151,10 +178,8 @@ ${ * If the property specifies a single code point (i.e., not a range of code * points), then end === start. */ -function readZippedCharacterPropertyFile(filename) { - let textContents = zlib.gunzipSync( - fs.readFileSync(filename) - ).toString('utf8'); +function readCharacterPropertyFile(filename: string) { + let textContents = fs.readFileSync(filename, { encoding: 'utf8'}); return textContents.split('\n') .filter(line => !line.startsWith('#') && line.trim()) @@ -177,7 +202,7 @@ function readZippedCharacterPropertyFile(filename) { * Does some bounds checking in order to determine if the string is in fact a * valid code point. */ -function parseCodepoint(hexString) { +function parseCodepoint(hexString: string) { let number = parseInt(hexString, 16); if (Number.isNaN(number)) { throw new SyntaxError(`Cannot parse codepoint: ${hexString}`); @@ -190,7 +215,7 @@ function parseCodepoint(hexString) { return number; } -function toUnicodeEscape(codePoint) { +function toUnicodeEscape(codePoint: number) { let isBMP = codePoint <= 0xFFFF; let simpleConversion = codePoint.toString(16).toUpperCase(); @@ -216,7 +241,7 @@ function utf16AlternativesStrategy() { return `/^(?:${alternatives.join('|')})/`; } -function codePointToUTF16Escape(codePoint) { +function codePointToUTF16Escape(codePoint: number): string { // Scalar values remain the same if (codePoint <= 0xFFFF) { return toUnicodeEscape(codePoint); @@ -247,11 +272,11 @@ function unicodeRangeStrategy() { return `/^[${regexp}]/u`; } -function makeDense(ranges) { +function makeDense(ranges: DataRange[]) { return joinSameAdjacentProperties(fillInGaps(ranges)); } -function ensureDense(ranges) { +function ensureDense(ranges: DataRange[]) { let lastEnd = -1; let lastProperty = 'sot'; for (let range of ranges) { @@ -271,7 +296,7 @@ function ensureDense(ranges) { } -function joinSameAdjacentProperties(ranges) { +function joinSameAdjacentProperties(ranges: DataRange[]) { console.assert(ranges.length > 1); let conjoinedRanges = []; @@ -289,7 +314,7 @@ function joinSameAdjacentProperties(ranges) { return conjoinedRanges; } -function fillInGaps(ranges) { +function fillInGaps(ranges: DataRange[]) { console.assert(ranges.length > 1); let denseRanges = []; diff --git a/package-lock.json b/package-lock.json index 995617cfb19..2f2ca04142d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,8 +105,7 @@ "c8": "^7.12.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^5.4.5", - "zlib": "^1.0.5" + "typescript": "^5.4.5" } }, "common/predictive-text": { @@ -14431,16 +14430,6 @@ "node": "^12.20.0 || >=14" } }, - "node_modules/zlib": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", - "integrity": "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w==", - "dev": true, - "hasInstallScript": true, - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", From fd8b260865fad11155271d548cefa91513c8e9d9 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 6 Aug 2024 14:46:13 +0700 Subject: [PATCH 020/262] change(common/models): adds unicode-data downloader --- .../WordBreakProperty.txt | 1468 +++++++++++++++++ .../unicode-character-database/build.sh | 66 + .../unicode-character-database/emoji-data.txt | 1320 +++++++++++++++ .../unicode-copyright.txt | 56 - 4 files changed, 2854 insertions(+), 56 deletions(-) create mode 100644 resources/standards-data/unicode-character-database/WordBreakProperty.txt create mode 100755 resources/standards-data/unicode-character-database/build.sh create mode 100644 resources/standards-data/unicode-character-database/emoji-data.txt delete mode 100644 resources/standards-data/unicode-character-database/unicode-copyright.txt diff --git a/resources/standards-data/unicode-character-database/WordBreakProperty.txt b/resources/standards-data/unicode-character-database/WordBreakProperty.txt new file mode 100644 index 00000000000..302a2769b39 --- /dev/null +++ b/resources/standards-data/unicode-character-database/WordBreakProperty.txt @@ -0,0 +1,1468 @@ +# WordBreakProperty-15.1.0.txt +# Date: 2023-03-31, 03:19:05 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ + +# ================================================ + +# Property: Word_Break + +# All code points not explicitly listed for Word_Break +# have the value Other (XX). + +# @missing: 0000..10FFFF; Other + +# ================================================ + +0022 ; Double_Quote # Po QUOTATION MARK + +# Total code points: 1 + +# ================================================ + +0027 ; Single_Quote # Po APOSTROPHE + +# Total code points: 1 + +# ================================================ + +05D0..05EA ; Hebrew_Letter # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; Hebrew_Letter # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +FB1D ; Hebrew_Letter # Lo HEBREW LETTER YOD WITH HIRIQ +FB1F..FB28 ; Hebrew_Letter # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB2A..FB36 ; Hebrew_Letter # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; Hebrew_Letter # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; Hebrew_Letter # Lo HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; Hebrew_Letter # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; Hebrew_Letter # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FB4F ; Hebrew_Letter # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATURE ALEF LAMED + +# Total code points: 75 + +# ================================================ + +000D ; CR # Cc + +# Total code points: 1 + +# ================================================ + +000A ; LF # Cc + +# Total code points: 1 + +# ================================================ + +000B..000C ; Newline # Cc [2] .. +0085 ; Newline # Cc +2028 ; Newline # Zl LINE SEPARATOR +2029 ; Newline # Zp PARAGRAPH SEPARATOR + +# Total code points: 5 + +# ================================================ + +0300..036F ; Extend # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X +0483..0487 ; Extend # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0488..0489 ; Extend # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +0591..05BD ; Extend # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BF ; Extend # Mn HEBREW POINT RAFE +05C1..05C2 ; Extend # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C4..05C5 ; Extend # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C7 ; Extend # Mn HEBREW POINT QAMATS QATAN +0610..061A ; Extend # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +064B..065F ; Extend # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0670 ; Extend # Mn ARABIC LETTER SUPERSCRIPT ALEF +06D6..06DC ; Extend # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DF..06E4 ; Extend # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E7..06E8 ; Extend # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06EA..06ED ; Extend # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +0711 ; Extend # Mn SYRIAC LETTER SUPERSCRIPT ALAPH +0730..074A ; Extend # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +07A6..07B0 ; Extend # Mn [11] THAANA ABAFILI..THAANA SUKUN +07EB..07F3 ; Extend # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07FD ; Extend # Mn NKO DANTAYALAN +0816..0819 ; Extend # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081B..0823 ; Extend # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0825..0827 ; Extend # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0829..082D ; Extend # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0859..085B ; Extend # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +0898..089F ; Extend # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08CA..08E1 ; Extend # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E3..0902 ; Extend # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA +0903 ; Extend # Mc DEVANAGARI SIGN VISARGA +093A ; Extend # Mn DEVANAGARI VOWEL SIGN OE +093B ; Extend # Mc DEVANAGARI VOWEL SIGN OOE +093C ; Extend # Mn DEVANAGARI SIGN NUKTA +093E..0940 ; Extend # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0941..0948 ; Extend # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +0949..094C ; Extend # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094D ; Extend # Mn DEVANAGARI SIGN VIRAMA +094E..094F ; Extend # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0951..0957 ; Extend # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0962..0963 ; Extend # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0981 ; Extend # Mn BENGALI SIGN CANDRABINDU +0982..0983 ; Extend # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +09BC ; Extend # Mn BENGALI SIGN NUKTA +09BE..09C0 ; Extend # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C1..09C4 ; Extend # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09C7..09C8 ; Extend # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; Extend # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09CD ; Extend # Mn BENGALI SIGN VIRAMA +09D7 ; Extend # Mc BENGALI AU LENGTH MARK +09E2..09E3 ; Extend # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09FE ; Extend # Mn BENGALI SANDHI MARK +0A01..0A02 ; Extend # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A03 ; Extend # Mc GURMUKHI SIGN VISARGA +0A3C ; Extend # Mn GURMUKHI SIGN NUKTA +0A3E..0A40 ; Extend # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A41..0A42 ; Extend # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; Extend # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; Extend # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; Extend # Mn GURMUKHI SIGN UDAAT +0A70..0A71 ; Extend # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A75 ; Extend # Mn GURMUKHI SIGN YAKASH +0A81..0A82 ; Extend # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0A83 ; Extend # Mc GUJARATI SIGN VISARGA +0ABC ; Extend # Mn GUJARATI SIGN NUKTA +0ABE..0AC0 ; Extend # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC1..0AC5 ; Extend # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; Extend # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0AC9 ; Extend # Mc GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; Extend # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0ACD ; Extend # Mn GUJARATI SIGN VIRAMA +0AE2..0AE3 ; Extend # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AFA..0AFF ; Extend # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; Extend # Mn ORIYA SIGN CANDRABINDU +0B02..0B03 ; Extend # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B3C ; Extend # Mn ORIYA SIGN NUKTA +0B3E ; Extend # Mc ORIYA VOWEL SIGN AA +0B3F ; Extend # Mn ORIYA VOWEL SIGN I +0B40 ; Extend # Mc ORIYA VOWEL SIGN II +0B41..0B44 ; Extend # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B47..0B48 ; Extend # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; Extend # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B4D ; Extend # Mn ORIYA SIGN VIRAMA +0B55..0B56 ; Extend # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B57 ; Extend # Mc ORIYA AU LENGTH MARK +0B62..0B63 ; Extend # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B82 ; Extend # Mn TAMIL SIGN ANUSVARA +0BBE..0BBF ; Extend # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC0 ; Extend # Mn TAMIL VOWEL SIGN II +0BC1..0BC2 ; Extend # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; Extend # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; Extend # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BCD ; Extend # Mn TAMIL SIGN VIRAMA +0BD7 ; Extend # Mc TAMIL AU LENGTH MARK +0C00 ; Extend # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C01..0C03 ; Extend # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04 ; Extend # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE +0C3C ; Extend # Mn TELUGU SIGN NUKTA +0C3E..0C40 ; Extend # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C41..0C44 ; Extend # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C46..0C48 ; Extend # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; Extend # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; Extend # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C62..0C63 ; Extend # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C81 ; Extend # Mn KANNADA SIGN CANDRABINDU +0C82..0C83 ; Extend # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0CBC ; Extend # Mn KANNADA SIGN NUKTA +0CBE ; Extend # Mc KANNADA VOWEL SIGN AA +0CBF ; Extend # Mn KANNADA VOWEL SIGN I +0CC0..0CC4 ; Extend # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC6 ; Extend # Mn KANNADA VOWEL SIGN E +0CC7..0CC8 ; Extend # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; Extend # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CCC..0CCD ; Extend # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CD5..0CD6 ; Extend # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CE2..0CE3 ; Extend # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0CF3 ; Extend # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D00..0D01 ; Extend # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D02..0D03 ; Extend # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D3B..0D3C ; Extend # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D3E..0D40 ; Extend # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D41..0D44 ; Extend # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D46..0D48 ; Extend # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; Extend # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D4D ; Extend # Mn MALAYALAM SIGN VIRAMA +0D57 ; Extend # Mc MALAYALAM AU LENGTH MARK +0D62..0D63 ; Extend # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D81 ; Extend # Mn SINHALA SIGN CANDRABINDU +0D82..0D83 ; Extend # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0DCA ; Extend # Mn SINHALA SIGN AL-LAKUNA +0DCF..0DD1 ; Extend # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD2..0DD4 ; Extend # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; Extend # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD8..0DDF ; Extend # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DF2..0DF3 ; Extend # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0E31 ; Extend # Mn THAI CHARACTER MAI HAN-AKAT +0E34..0E3A ; Extend # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E47..0E4E ; Extend # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0EB1 ; Extend # Mn LAO VOWEL SIGN MAI KAN +0EB4..0EBC ; Extend # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EC8..0ECE ; Extend # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN +0F18..0F19 ; Extend # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F35 ; Extend # Mn TIBETAN MARK NGAS BZUNG NYI ZLA +0F37 ; Extend # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F39 ; Extend # Mn TIBETAN MARK TSA -PHRU +0F3E..0F3F ; Extend # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F71..0F7E ; Extend # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F7F ; Extend # Mc TIBETAN SIGN RNAM BCAD +0F80..0F84 ; Extend # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F86..0F87 ; Extend # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F8D..0F97 ; Extend # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; Extend # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FC6 ; Extend # Mn TIBETAN SYMBOL PADMA GDAN +102B..102C ; Extend # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +102D..1030 ; Extend # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1031 ; Extend # Mc MYANMAR VOWEL SIGN E +1032..1037 ; Extend # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1038 ; Extend # Mc MYANMAR SIGN VISARGA +1039..103A ; Extend # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103B..103C ; Extend # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +103D..103E ; Extend # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +1056..1057 ; Extend # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1058..1059 ; Extend # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105E..1060 ; Extend # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1062..1064 ; Extend # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1067..106D ; Extend # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +1071..1074 ; Extend # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1082 ; Extend # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1083..1084 ; Extend # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1085..1086 ; Extend # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +1087..108C ; Extend # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108D ; Extend # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +108F ; Extend # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5 +109A..109C ; Extend # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +109D ; Extend # Mn MYANMAR VOWEL SIGN AITON AI +135D..135F ; Extend # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1712..1714 ; Extend # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1715 ; Extend # Mc TAGALOG SIGN PAMUDPOD +1732..1733 ; Extend # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1734 ; Extend # Mc HANUNOO SIGN PAMUDPOD +1752..1753 ; Extend # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1772..1773 ; Extend # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +17B4..17B5 ; Extend # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B6 ; Extend # Mc KHMER VOWEL SIGN AA +17B7..17BD ; Extend # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17BE..17C5 ; Extend # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C6 ; Extend # Mn KHMER SIGN NIKAHIT +17C7..17C8 ; Extend # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +17C9..17D3 ; Extend # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17DD ; Extend # Mn KHMER SIGN ATTHACAN +180B..180D ; Extend # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180F ; Extend # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR +1885..1886 ; Extend # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +18A9 ; Extend # Mn MONGOLIAN LETTER ALI GALI DAGALGA +1920..1922 ; Extend # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1923..1926 ; Extend # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1927..1928 ; Extend # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1929..192B ; Extend # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; Extend # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1932 ; Extend # Mn LIMBU SMALL LETTER ANUSVARA +1933..1938 ; Extend # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1939..193B ; Extend # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1A17..1A18 ; Extend # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A19..1A1A ; Extend # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A1B ; Extend # Mn BUGINESE VOWEL SIGN AE +1A55 ; Extend # Mc TAI THAM CONSONANT SIGN MEDIAL RA +1A56 ; Extend # Mn TAI THAM CONSONANT SIGN MEDIAL LA +1A57 ; Extend # Mc TAI THAM CONSONANT SIGN LA TANG LAI +1A58..1A5E ; Extend # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; Extend # Mn TAI THAM SIGN SAKOT +1A61 ; Extend # Mc TAI THAM VOWEL SIGN A +1A62 ; Extend # Mn TAI THAM VOWEL SIGN MAI SAT +1A63..1A64 ; Extend # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A65..1A6C ; Extend # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A6D..1A72 ; Extend # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1A73..1A7C ; Extend # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT +1AB0..1ABD ; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY +1ABF..1ACE ; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B04 ; Extend # Mc BALINESE SIGN BISAH +1B34 ; Extend # Mn BALINESE SIGN REREKAN +1B35 ; Extend # Mc BALINESE VOWEL SIGN TEDUNG +1B36..1B3A ; Extend # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3B ; Extend # Mc BALINESE VOWEL SIGN RA REPA TEDUNG +1B3C ; Extend # Mn BALINESE VOWEL SIGN LA LENGA +1B3D..1B41 ; Extend # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B42 ; Extend # Mn BALINESE VOWEL SIGN PEPET +1B43..1B44 ; Extend # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG +1B6B..1B73 ; Extend # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B80..1B81 ; Extend # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1B82 ; Extend # Mc SUNDANESE SIGN PANGWISAD +1BA1 ; Extend # Mc SUNDANESE CONSONANT SIGN PAMINGKAL +1BA2..1BA5 ; Extend # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA6..1BA7 ; Extend # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BA8..1BA9 ; Extend # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAA ; Extend # Mc SUNDANESE SIGN PAMAAEH +1BAB..1BAD ; Extend # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BE6 ; Extend # Mn BATAK SIGN TOMPI +1BE7 ; Extend # Mc BATAK VOWEL SIGN E +1BE8..1BE9 ; Extend # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BEA..1BEC ; Extend # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BED ; Extend # Mn BATAK VOWEL SIGN KARO O +1BEE ; Extend # Mc BATAK VOWEL SIGN U +1BEF..1BF1 ; Extend # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1BF2..1BF3 ; Extend # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN +1C24..1C2B ; Extend # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C2C..1C33 ; Extend # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C34..1C35 ; Extend # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1C36..1C37 ; Extend # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1CD0..1CD2 ; Extend # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD4..1CE0 ; Extend # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE1 ; Extend # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CE2..1CE8 ; Extend # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CED ; Extend # Mn VEDIC SIGN TIRYAK +1CF4 ; Extend # Mn VEDIC TONE CANDRA ABOVE +1CF7 ; Extend # Mc VEDIC SIGN ATIKRAMA +1CF8..1CF9 ; Extend # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1DC0..1DFF ; Extend # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +200C ; Extend # Cf ZERO WIDTH NON-JOINER +20D0..20DC ; Extend # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20DD..20E0 ; Extend # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E1 ; Extend # Mn COMBINING LEFT RIGHT ARROW ABOVE +20E2..20E4 ; Extend # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +20E5..20F0 ; Extend # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2CEF..2CF1 ; Extend # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2D7F ; Extend # Mn TIFINAGH CONSONANT JOINER +2DE0..2DFF ; Extend # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +302A..302D ; Extend # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +302E..302F ; Extend # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +3099..309A ; Extend # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +A66F ; Extend # Mn COMBINING CYRILLIC VZMET +A670..A672 ; Extend # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN +A674..A67D ; Extend # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A69E..A69F ; Extend # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6F0..A6F1 ; Extend # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A802 ; Extend # Mn SYLOTI NAGRI SIGN DVISVARA +A806 ; Extend # Mn SYLOTI NAGRI SIGN HASANTA +A80B ; Extend # Mn SYLOTI NAGRI SIGN ANUSVARA +A823..A824 ; Extend # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A825..A826 ; Extend # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A827 ; Extend # Mc SYLOTI NAGRI VOWEL SIGN OO +A82C ; Extend # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA +A880..A881 ; Extend # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A8B4..A8C3 ; Extend # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A8C4..A8C5 ; Extend # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8E0..A8F1 ; Extend # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8FF ; Extend # Mn DEVANAGARI VOWEL SIGN AY +A926..A92D ; Extend # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A947..A951 ; Extend # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A952..A953 ; Extend # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A980..A982 ; Extend # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A983 ; Extend # Mc JAVANESE SIGN WIGNYAN +A9B3 ; Extend # Mn JAVANESE SIGN CECAK TELU +A9B4..A9B5 ; Extend # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9B6..A9B9 ; Extend # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BA..A9BB ; Extend # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BC..A9BD ; Extend # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9BE..A9C0 ; Extend # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON +A9E5 ; Extend # Mn MYANMAR SIGN SHAN SAW +AA29..AA2E ; Extend # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA2F..AA30 ; Extend # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA31..AA32 ; Extend # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA33..AA34 ; Extend # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA35..AA36 ; Extend # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA43 ; Extend # Mn CHAM CONSONANT SIGN FINAL NG +AA4C ; Extend # Mn CHAM CONSONANT SIGN FINAL M +AA4D ; Extend # Mc CHAM CONSONANT SIGN FINAL H +AA7B ; Extend # Mc MYANMAR SIGN PAO KAREN TONE +AA7C ; Extend # Mn MYANMAR SIGN TAI LAING TONE-2 +AA7D ; Extend # Mc MYANMAR SIGN TAI LAING TONE-5 +AAB0 ; Extend # Mn TAI VIET MAI KANG +AAB2..AAB4 ; Extend # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB7..AAB8 ; Extend # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AABE..AABF ; Extend # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC1 ; Extend # Mn TAI VIET TONE MAI THO +AAEB ; Extend # Mc MEETEI MAYEK VOWEL SIGN II +AAEC..AAED ; Extend # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAEE..AAEF ; Extend # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF5 ; Extend # Mc MEETEI MAYEK VOWEL SIGN VISARGA +AAF6 ; Extend # Mn MEETEI MAYEK VIRAMA +ABE3..ABE4 ; Extend # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE5 ; Extend # Mn MEETEI MAYEK VOWEL SIGN ANAP +ABE6..ABE7 ; Extend # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE8 ; Extend # Mn MEETEI MAYEK VOWEL SIGN UNAP +ABE9..ABEA ; Extend # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEC ; Extend # Mc MEETEI MAYEK LUM IYEK +ABED ; Extend # Mn MEETEI MAYEK APUN IYEK +FB1E ; Extend # Mn HEBREW POINT JUDEO-SPANISH VARIKA +FE00..FE0F ; Extend # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE20..FE2F ; Extend # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +101FD ; Extend # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +102E0 ; Extend # Mn COPTIC EPACT THOUSANDS MARK +10376..1037A ; Extend # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10A01..10A03 ; Extend # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; Extend # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; Extend # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A38..10A3A ; Extend # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; Extend # Mn KHAROSHTHI VIRAMA +10AE5..10AE6 ; Extend # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EFD..10EFF ; Extend # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +11000 ; Extend # Mc BRAHMI SIGN CANDRABINDU +11001 ; Extend # Mn BRAHMI SIGN ANUSVARA +11002 ; Extend # Mc BRAHMI SIGN VISARGA +11038..11046 ; Extend # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA +11070 ; Extend # Mn BRAHMI SIGN OLD TAMIL VIRAMA +11073..11074 ; Extend # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +1107F..11081 ; Extend # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA +11082 ; Extend # Mc KAITHI SIGN VISARGA +110B0..110B2 ; Extend # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B3..110B6 ; Extend # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B7..110B8 ; Extend # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +110B9..110BA ; Extend # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110C2 ; Extend # Mn KAITHI VOWEL SIGN VOCALIC R +11100..11102 ; Extend # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11127..1112B ; Extend # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112C ; Extend # Mc CHAKMA VOWEL SIGN E +1112D..11134 ; Extend # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11145..11146 ; Extend # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11173 ; Extend # Mn MAHAJANI SIGN NUKTA +11180..11181 ; Extend # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +11182 ; Extend # Mc SHARADA SIGN VISARGA +111B3..111B5 ; Extend # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111B6..111BE ; Extend # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111BF..111C0 ; Extend # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111C9..111CC ; Extend # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CE ; Extend # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E +111CF ; Extend # Mn SHARADA SIGN INVERTED CANDRABINDU +1122C..1122E ; Extend # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +1122F..11231 ; Extend # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11232..11233 ; Extend # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11234 ; Extend # Mn KHOJKI SIGN ANUSVARA +11235 ; Extend # Mc KHOJKI SIGN VIRAMA +11236..11237 ; Extend # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +1123E ; Extend # Mn KHOJKI SIGN SUKUN +11241 ; Extend # Mn KHOJKI VOWEL SIGN VOCALIC R +112DF ; Extend # Mn KHUDAWADI SIGN ANUSVARA +112E0..112E2 ; Extend # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +112E3..112EA ; Extend # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +11300..11301 ; Extend # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +11302..11303 ; Extend # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +1133B..1133C ; Extend # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +1133E..1133F ; Extend # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11340 ; Extend # Mn GRANTHA VOWEL SIGN II +11341..11344 ; Extend # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; Extend # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134D ; Extend # Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA +11357 ; Extend # Mc GRANTHA AU LENGTH MARK +11362..11363 ; Extend # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11366..1136C ; Extend # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; Extend # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11435..11437 ; Extend # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11438..1143F ; Extend # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11440..11441 ; Extend # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11442..11444 ; Extend # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11445 ; Extend # Mc NEWA SIGN VISARGA +11446 ; Extend # Mn NEWA SIGN NUKTA +1145E ; Extend # Mn NEWA SANDHI MARK +114B0..114B2 ; Extend # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B3..114B8 ; Extend # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114B9 ; Extend # Mc TIRHUTA VOWEL SIGN E +114BA ; Extend # Mn TIRHUTA VOWEL SIGN SHORT E +114BB..114BE ; Extend # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114BF..114C0 ; Extend # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C1 ; Extend # Mc TIRHUTA SIGN VISARGA +114C2..114C3 ; Extend # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +115AF..115B1 ; Extend # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B2..115B5 ; Extend # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115B8..115BB ; Extend # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BC..115BD ; Extend # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BE ; Extend # Mc SIDDHAM SIGN VISARGA +115BF..115C0 ; Extend # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115DC..115DD ; Extend # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11630..11632 ; Extend # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +11633..1163A ; Extend # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163B..1163C ; Extend # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163D ; Extend # Mn MODI SIGN ANUSVARA +1163E ; Extend # Mc MODI SIGN VISARGA +1163F..11640 ; Extend # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +116AB ; Extend # Mn TAKRI SIGN ANUSVARA +116AC ; Extend # Mc TAKRI SIGN VISARGA +116AD ; Extend # Mn TAKRI VOWEL SIGN AA +116AE..116AF ; Extend # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B0..116B5 ; Extend # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B6 ; Extend # Mc TAKRI SIGN VIRAMA +116B7 ; Extend # Mn TAKRI SIGN NUKTA +1171D..1171F ; Extend # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11720..11721 ; Extend # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11722..11725 ; Extend # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11726 ; Extend # Mc AHOM VOWEL SIGN E +11727..1172B ; Extend # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +1182C..1182E ; Extend # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +1182F..11837 ; Extend # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11838 ; Extend # Mc DOGRA SIGN VISARGA +11839..1183A ; Extend # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +11930..11935 ; Extend # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; Extend # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193B..1193C ; Extend # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193D ; Extend # Mc DIVES AKURU SIGN HALANTA +1193E ; Extend # Mn DIVES AKURU VIRAMA +11940 ; Extend # Mc DIVES AKURU MEDIAL YA +11942 ; Extend # Mc DIVES AKURU MEDIAL RA +11943 ; Extend # Mn DIVES AKURU SIGN NUKTA +119D1..119D3 ; Extend # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119D4..119D7 ; Extend # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; Extend # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119DC..119DF ; Extend # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E0 ; Extend # Mn NANDINAGARI SIGN VIRAMA +119E4 ; Extend # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A01..11A0A ; Extend # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A33..11A38 ; Extend # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A39 ; Extend # Mc ZANABAZAR SQUARE SIGN VISARGA +11A3B..11A3E ; Extend # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A47 ; Extend # Mn ZANABAZAR SQUARE SUBJOINER +11A51..11A56 ; Extend # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A57..11A58 ; Extend # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A59..11A5B ; Extend # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A8A..11A96 ; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A97 ; Extend # Mc SOYOMBO SIGN VISARGA +11A98..11A99 ; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11C2F ; Extend # Mc BHAIKSUKI VOWEL SIGN AA +11C30..11C36 ; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3E ; Extend # Mc BHAIKSUKI SIGN VISARGA +11C3F ; Extend # Mn BHAIKSUKI SIGN VIRAMA +11C92..11CA7 ; Extend # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CA9 ; Extend # Mc MARCHEN SUBJOINED LETTER YA +11CAA..11CB0 ; Extend # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB1 ; Extend # Mc MARCHEN VOWEL SIGN I +11CB2..11CB3 ; Extend # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB4 ; Extend # Mc MARCHEN VOWEL SIGN O +11CB5..11CB6 ; Extend # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D31..11D36 ; Extend # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; Extend # Mn MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; Extend # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; Extend # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D47 ; Extend # Mn MASARAM GONDI RA-KARA +11D8A..11D8E ; Extend # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D90..11D91 ; Extend # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D93..11D94 ; Extend # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D95 ; Extend # Mn GUNJALA GONDI SIGN ANUSVARA +11D96 ; Extend # Mc GUNJALA GONDI SIGN VISARGA +11D97 ; Extend # Mn GUNJALA GONDI VIRAMA +11EF3..11EF4 ; Extend # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11EF5..11EF6 ; Extend # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11F00..11F01 ; Extend # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F03 ; Extend # Mc KAWI SIGN VISARGA +11F34..11F35 ; Extend # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F36..11F3A ; Extend # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F3E..11F3F ; Extend # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F40 ; Extend # Mn KAWI VOWEL SIGN EU +11F41 ; Extend # Mc KAWI SIGN KILLER +11F42 ; Extend # Mn KAWI CONJOINER +13440 ; Extend # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13447..13455 ; Extend # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +16AF0..16AF4 ; Extend # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16B30..16B36 ; Extend # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16F4F ; Extend # Mn MIAO SIGN CONSONANT MODIFIER BAR +16F51..16F87 ; Extend # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16F8F..16F92 ; Extend # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW +16FE4 ; Extend # Mn KHITAN SMALL SCRIPT FILLER +16FF0..16FF1 ; Extend # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +1BC9D..1BC9E ; Extend # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1CF00..1CF2D ; Extend # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; Extend # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1D165..1D166 ; Extend # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D167..1D169 ; Extend # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D16D..1D172 ; Extend # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 +1D17B..1D182 ; Extend # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D185..1D18B ; Extend # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D1AA..1D1AD ; Extend # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D242..1D244 ; Extend # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1DA00..1DA36 ; Extend # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA3B..1DA6C ; Extend # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA75 ; Extend # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA84 ; Extend # Mn SIGNWRITING LOCATION HEAD NECK +1DA9B..1DA9F ; Extend # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; Extend # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1E000..1E006 ; Extend # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; Extend # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; Extend # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; Extend # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E08F ; Extend # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E130..1E136 ; Extend # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E2AE ; Extend # Mn TOTO SIGN RISING TONE +1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 +E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG +E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 + +# Total code points: 2554 + +# ================================================ + +1F1E6..1F1FF ; Regional_Indicator # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z + +# Total code points: 26 + +# ================================================ + +00AD ; Format # Cf SOFT HYPHEN +061C ; Format # Cf ARABIC LETTER MARK +180E ; Format # Cf MONGOLIAN VOWEL SEPARATOR +200E..200F ; Format # Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK +202A..202E ; Format # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +2060..2064 ; Format # Cf [5] WORD JOINER..INVISIBLE PLUS +2066..206F ; Format # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +FEFF ; Format # Cf ZERO WIDTH NO-BREAK SPACE +FFF9..FFFB ; Format # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +13430..1343F ; Format # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +1BCA0..1BCA3 ; Format # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1D173..1D17A ; Format # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +E0001 ; Format # Cf LANGUAGE TAG + +# Total code points: 58 + +# ================================================ + +3031..3035 ; Katakana # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF +309B..309C ; Katakana # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +30A0 ; Katakana # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN +30A1..30FA ; Katakana # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FC..30FE ; Katakana # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK +30FF ; Katakana # Lo KATAKANA DIGRAPH KOTO +31F0..31FF ; Katakana # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +32D0..32FE ; Katakana # So [47] CIRCLED KATAKANA A..CIRCLED KATAKANA WO +3300..3357 ; Katakana # So [88] SQUARE APAATO..SQUARE WATTO +FF66..FF6F ; Katakana # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU +FF70 ; Katakana # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +1AFF0..1AFF3 ; Katakana # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; Katakana # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; Katakana # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1B000 ; Katakana # Lo KATAKANA LETTER ARCHAIC E +1B120..1B122 ; Katakana # Lo [3] KATAKANA LETTER ARCHAIC YI..KATAKANA LETTER ARCHAIC WU +1B155 ; Katakana # Lo KATAKANA LETTER SMALL KO +1B164..1B167 ; Katakana # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N + +# Total code points: 331 + +# ================================================ + +0041..005A ; ALetter # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +0061..007A ; ALetter # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +00AA ; ALetter # Lo FEMININE ORDINAL INDICATOR +00B5 ; ALetter # L& MICRO SIGN +00BA ; ALetter # Lo MASCULINE ORDINAL INDICATOR +00C0..00D6 ; ALetter # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D8..00F6 ; ALetter # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS +00F8..01BA ; ALetter # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL +01BB ; ALetter # Lo LATIN LETTER TWO WITH STROKE +01BC..01BF ; ALetter # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN +01C0..01C3 ; ALetter # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +01C4..0293 ; ALetter # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL +0294 ; ALetter # Lo LATIN LETTER GLOTTAL STOP +0295..02AF ; ALetter # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +02B0..02C1 ; ALetter # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C2..02C5 ; ALetter # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD +02C6..02D1 ; ALetter # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON +02D2..02D7 ; ALetter # Sk [6] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER MINUS SIGN +02DE..02DF ; ALetter # Sk [2] MODIFIER LETTER RHOTIC HOOK..MODIFIER LETTER CROSS ACCENT +02E0..02E4 ; ALetter # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02E5..02EB ; ALetter # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02EC ; ALetter # Lm MODIFIER LETTER VOICING +02ED ; ALetter # Sk MODIFIER LETTER UNASPIRATED +02EE ; ALetter # Lm MODIFIER LETTER DOUBLE APOSTROPHE +02EF..02FF ; ALetter # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0370..0373 ; ALetter # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI +0374 ; ALetter # Lm GREEK NUMERAL SIGN +0376..0377 ; ALetter # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037A ; ALetter # Lm GREEK YPOGEGRAMMENI +037B..037D ; ALetter # L& [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +037F ; ALetter # L& GREEK CAPITAL LETTER YOT +0386 ; ALetter # L& GREEK CAPITAL LETTER ALPHA WITH TONOS +0388..038A ; ALetter # L& [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; ALetter # L& GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..03A1 ; ALetter # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO +03A3..03F5 ; ALetter # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL +03F7..0481 ; ALetter # L& [139] GREEK CAPITAL LETTER SHO..CYRILLIC SMALL LETTER KOPPA +048A..052F ; ALetter # L& [166] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER EL WITH DESCENDER +0531..0556 ; ALetter # L& [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +0559 ; ALetter # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING +055A..055C ; ALetter # Po [3] ARMENIAN APOSTROPHE..ARMENIAN EXCLAMATION MARK +055E ; ALetter # Po ARMENIAN QUESTION MARK +0560..0588 ; ALetter # L& [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +058A ; ALetter # Pd ARMENIAN HYPHEN +05F3 ; ALetter # Po HEBREW PUNCTUATION GERESH +0620..063F ; ALetter # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0640 ; ALetter # Lm ARABIC TATWEEL +0641..064A ; ALetter # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH +066E..066F ; ALetter # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0671..06D3 ; ALetter # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D5 ; ALetter # Lo ARABIC LETTER AE +06E5..06E6 ; ALetter # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH +06EE..06EF ; ALetter # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06FA..06FC ; ALetter # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FF ; ALetter # Lo ARABIC LETTER HEH WITH INVERTED V +070F ; ALetter # Cf SYRIAC ABBREVIATION MARK +0710 ; ALetter # Lo SYRIAC LETTER ALAPH +0712..072F ; ALetter # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +074D..07A5 ; ALetter # Lo [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU +07B1 ; ALetter # Lo THAANA LETTER NAA +07CA..07EA ; ALetter # Lo [33] NKO LETTER A..NKO LETTER JONA RA +07F4..07F5 ; ALetter # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07FA ; ALetter # Lm NKO LAJANYALAN +0800..0815 ; ALetter # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +081A ; ALetter # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT +0824 ; ALetter # Lm SAMARITAN MODIFIER LETTER SHORT A +0828 ; ALetter # Lm SAMARITAN MODIFIER LETTER I +0840..0858 ; ALetter # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0860..086A ; ALetter # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; ALetter # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0889..088E ; ALetter # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +08A0..08C8 ; ALetter # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +08C9 ; ALetter # Lm ARABIC SMALL FARSI YEH +0904..0939 ; ALetter # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093D ; ALetter # Lo DEVANAGARI SIGN AVAGRAHA +0950 ; ALetter # Lo DEVANAGARI OM +0958..0961 ; ALetter # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0971 ; ALetter # Lm DEVANAGARI SIGN HIGH SPACING DOT +0972..0980 ; ALetter # Lo [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI +0985..098C ; ALetter # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; ALetter # Lo [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; ALetter # Lo [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; ALetter # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; ALetter # Lo BENGALI LETTER LA +09B6..09B9 ; ALetter # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BD ; ALetter # Lo BENGALI SIGN AVAGRAHA +09CE ; ALetter # Lo BENGALI LETTER KHANDA TA +09DC..09DD ; ALetter # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; ALetter # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09F0..09F1 ; ALetter # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09FC ; ALetter # Lo BENGALI LETTER VEDIC ANUSVARA +0A05..0A0A ; ALetter # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; ALetter # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; ALetter # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; ALetter # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; ALetter # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; ALetter # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; ALetter # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A59..0A5C ; ALetter # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; ALetter # Lo GURMUKHI LETTER FA +0A72..0A74 ; ALetter # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A85..0A8D ; ALetter # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; ALetter # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; ALetter # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; ALetter # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; ALetter # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; ALetter # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABD ; ALetter # Lo GUJARATI SIGN AVAGRAHA +0AD0 ; ALetter # Lo GUJARATI OM +0AE0..0AE1 ; ALetter # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AF9 ; ALetter # Lo GUJARATI LETTER ZHA +0B05..0B0C ; ALetter # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; ALetter # Lo [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; ALetter # Lo [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; ALetter # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; ALetter # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; ALetter # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3D ; ALetter # Lo ORIYA SIGN AVAGRAHA +0B5C..0B5D ; ALetter # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; ALetter # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B71 ; ALetter # Lo ORIYA LETTER WA +0B83 ; ALetter # Lo TAMIL SIGN VISARGA +0B85..0B8A ; ALetter # Lo [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; ALetter # Lo [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; ALetter # Lo [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; ALetter # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; ALetter # Lo TAMIL LETTER JA +0B9E..0B9F ; ALetter # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; ALetter # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; ALetter # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; ALetter # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA +0BD0 ; ALetter # Lo TAMIL OM +0C05..0C0C ; ALetter # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; ALetter # Lo [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; ALetter # Lo [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; ALetter # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3D ; ALetter # Lo TELUGU SIGN AVAGRAHA +0C58..0C5A ; ALetter # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; ALetter # Lo TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; ALetter # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C80 ; ALetter # Lo KANNADA SIGN SPACING CANDRABINDU +0C85..0C8C ; ALetter # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; ALetter # Lo [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; ALetter # Lo [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; ALetter # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; ALetter # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBD ; ALetter # Lo KANNADA SIGN AVAGRAHA +0CDD..0CDE ; ALetter # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; ALetter # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CF1..0CF2 ; ALetter # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0D04..0D0C ; ALetter # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; ALetter # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; ALetter # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3D ; ALetter # Lo MALAYALAM SIGN AVAGRAHA +0D4E ; ALetter # Lo MALAYALAM LETTER DOT REPH +0D54..0D56 ; ALetter # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D5F..0D61 ; ALetter # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D7A..0D7F ; ALetter # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D85..0D96 ; ALetter # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; ALetter # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; ALetter # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; ALetter # Lo SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; ALetter # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0F00 ; ALetter # Lo TIBETAN SYLLABLE OM +0F40..0F47 ; ALetter # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; ALetter # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F88..0F8C ; ALetter # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +10A0..10C5 ; ALetter # L& [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; ALetter # L& GEORGIAN CAPITAL LETTER YN +10CD ; ALetter # L& GEORGIAN CAPITAL LETTER AEN +10D0..10FA ; ALetter # L& [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FC ; ALetter # Lm MODIFIER LETTER GEORGIAN NAR +10FD..10FF ; ALetter # L& [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1100..1248 ; ALetter # Lo [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA +124A..124D ; ALetter # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; ALetter # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; ALetter # Lo ETHIOPIC SYLLABLE QHWA +125A..125D ; ALetter # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; ALetter # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; ALetter # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; ALetter # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; ALetter # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; ALetter # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; ALetter # Lo ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; ALetter # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; ALetter # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; ALetter # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; ALetter # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; ALetter # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +1380..138F ; ALetter # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +13A0..13F5 ; ALetter # L& [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +13F8..13FD ; ALetter # L& [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1401..166C ; ALetter # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166F..167F ; ALetter # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1681..169A ; ALetter # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +16A0..16EA ; ALetter # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EE..16F0 ; ALetter # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +16F1..16F8 ; ALetter # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; ALetter # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA +171F..1731 ; ALetter # Lo [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA +1740..1751 ; ALetter # Lo [18] BUHID LETTER A..BUHID LETTER HA +1760..176C ; ALetter # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; ALetter # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1820..1842 ; ALetter # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1843 ; ALetter # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN +1844..1878 ; ALetter # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; ALetter # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1887..18A8 ; ALetter # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18AA ; ALetter # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; ALetter # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; ALetter # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1A00..1A16 ; ALetter # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1B05..1B33 ; ALetter # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B45..1B4C ; ALetter # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B83..1BA0 ; ALetter # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BAE..1BAF ; ALetter # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BBA..1BE5 ; ALetter # Lo [44] SUNDANESE AVAGRAHA..BATAK LETTER U +1C00..1C23 ; ALetter # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C4D..1C4F ; ALetter # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C5A..1C77 ; ALetter # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1C78..1C7D ; ALetter # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1C80..1C88 ; ALetter # L& [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1C90..1CBA ; ALetter # L& [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; ALetter # L& [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1CE9..1CEC ; ALetter # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CEE..1CF3 ; ALetter # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF5..1CF6 ; ALetter # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CFA ; ALetter # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +1D00..1D2B ; ALetter # L& [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D2C..1D6A ; ALetter # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D6B..1D77 ; ALetter # L& [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D78 ; ALetter # Lm MODIFIER LETTER CYRILLIC EN +1D79..1D9A ; ALetter # L& [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1D9B..1DBF ; ALetter # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +1E00..1F15 ; ALetter # L& [278] LATIN CAPITAL LETTER A WITH RING BELOW..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F18..1F1D ; ALetter # L& [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F45 ; ALetter # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F48..1F4D ; ALetter # L& [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; ALetter # L& [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F59 ; ALetter # L& GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; ALetter # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; ALetter # L& GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F..1F7D ; ALetter # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1FB4 ; ALetter # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FBC ; ALetter # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FBE ; ALetter # L& GREEK PROSGEGRAMMENI +1FC2..1FC4 ; ALetter # L& [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FCC ; ALetter # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FD0..1FD3 ; ALetter # L& [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FDB ; ALetter # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA +1FE0..1FEC ; ALetter # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FF2..1FF4 ; ALetter # L& [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FFC ; ALetter # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI +2071 ; ALetter # Lm SUPERSCRIPT LATIN SMALL LETTER I +207F ; ALetter # Lm SUPERSCRIPT LATIN SMALL LETTER N +2090..209C ; ALetter # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +2102 ; ALetter # L& DOUBLE-STRUCK CAPITAL C +2107 ; ALetter # L& EULER CONSTANT +210A..2113 ; ALetter # L& [10] SCRIPT SMALL G..SCRIPT SMALL L +2115 ; ALetter # L& DOUBLE-STRUCK CAPITAL N +2119..211D ; ALetter # L& [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +2124 ; ALetter # L& DOUBLE-STRUCK CAPITAL Z +2126 ; ALetter # L& OHM SIGN +2128 ; ALetter # L& BLACK-LETTER CAPITAL Z +212A..212D ; ALetter # L& [4] KELVIN SIGN..BLACK-LETTER CAPITAL C +212F..2134 ; ALetter # L& [6] SCRIPT SMALL E..SCRIPT SMALL O +2135..2138 ; ALetter # Lo [4] ALEF SYMBOL..DALET SYMBOL +2139 ; ALetter # L& INFORMATION SOURCE +213C..213F ; ALetter # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI +2145..2149 ; ALetter # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J +214E ; ALetter # L& TURNED SMALL F +2160..2182 ; ALetter # Nl [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND +2183..2184 ; ALetter # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C +2185..2188 ; ALetter # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +24B6..24E9 ; ALetter # So [52] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN SMALL LETTER Z +2C00..2C7B ; ALetter # L& [124] GLAGOLITIC CAPITAL LETTER AZU..LATIN LETTER SMALL CAPITAL TURNED E +2C7C..2C7D ; ALetter # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2C7E..2CE4 ; ALetter # L& [103] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC SYMBOL KAI +2CEB..2CEE ; ALetter # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CF2..2CF3 ; ALetter # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI +2D00..2D25 ; ALetter # L& [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; ALetter # L& GEORGIAN SMALL LETTER YN +2D2D ; ALetter # L& GEORGIAN SMALL LETTER AEN +2D30..2D67 ; ALetter # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D6F ; ALetter # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2D80..2D96 ; ALetter # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; ALetter # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; ALetter # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; ALetter # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; ALetter # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2E2F ; ALetter # Lm VERTICAL TILDE +3005 ; ALetter # Lm IDEOGRAPHIC ITERATION MARK +303B ; ALetter # Lm VERTICAL IDEOGRAPHIC ITERATION MARK +303C ; ALetter # Lo MASU MARK +3105..312F ; ALetter # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; ALetter # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +31A0..31BF ; ALetter # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +A000..A014 ; ALetter # Lo [21] YI SYLLABLE IT..YI SYLLABLE E +A015 ; ALetter # Lm YI SYLLABLE WU +A016..A48C ; ALetter # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A4D0..A4F7 ; ALetter # Lo [40] LISU LETTER BA..LISU LETTER OE +A4F8..A4FD ; ALetter # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A500..A60B ; ALetter # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A60C ; ALetter # Lm VAI SYLLABLE LENGTHENER +A610..A61F ; ALetter # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A62A..A62B ; ALetter # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A640..A66D ; ALetter # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A66E ; ALetter # Lo CYRILLIC LETTER MULTIOCULAR O +A67F ; ALetter # Lm CYRILLIC PAYEROK +A680..A69B ; ALetter # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O +A69C..A69D ; ALetter # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A6A0..A6E5 ; ALetter # Lo [70] BAMUM LETTER A..BAMUM LETTER KI +A6E6..A6EF ; ALetter # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +A708..A716 ; ALetter # Sk [15] MODIFIER LETTER EXTRA-HIGH DOTTED TONE BAR..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A717..A71F ; ALetter # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A720..A721 ; ALetter # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A722..A76F ; ALetter # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON +A770 ; ALetter # Lm MODIFIER LETTER US +A771..A787 ; ALetter # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T +A788 ; ALetter # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A789..A78A ; ALetter # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +A78B..A78E ; ALetter # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A78F ; ALetter # Lo LATIN LETTER SINOLOGICAL DOT +A790..A7CA ; ALetter # L& [59] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D0..A7D1 ; ALetter # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; ALetter # L& LATIN SMALL LETTER DOUBLE THORN +A7D5..A7D9 ; ALetter # L& [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S +A7F2..A7F4 ; ALetter # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F5..A7F6 ; ALetter # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H +A7F7 ; ALetter # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7F8..A7F9 ; ALetter # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A7FA ; ALetter # L& LATIN LETTER SMALL CAPITAL TURNED M +A7FB..A801 ; ALetter # Lo [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I +A803..A805 ; ALetter # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A807..A80A ; ALetter # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80C..A822 ; ALetter # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A840..A873 ; ALetter # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A882..A8B3 ; ALetter # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8F2..A8F7 ; ALetter # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8FB ; ALetter # Lo DEVANAGARI HEADSTROKE +A8FD..A8FE ; ALetter # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A90A..A925 ; ALetter # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A930..A946 ; ALetter # Lo [23] REJANG LETTER KA..REJANG LETTER A +A960..A97C ; ALetter # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A984..A9B2 ; ALetter # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9CF ; ALetter # Lm JAVANESE PANGRANGKEP +AA00..AA28 ; ALetter # Lo [41] CHAM LETTER A..CHAM LETTER HA +AA40..AA42 ; ALetter # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA44..AA4B ; ALetter # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AAE0..AAEA ; ALetter # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAF2 ; ALetter # Lo MEETEI MAYEK ANJI +AAF3..AAF4 ; ALetter # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AB01..AB06 ; ALetter # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; ALetter # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; ALetter # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; ALetter # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; ALetter # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +AB30..AB5A ; ALetter # L& [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB5B ; ALetter # Sk MODIFIER BREVE WITH INVERTED BREVE +AB5C..AB5F ; ALetter # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB60..AB68 ; ALetter # L& [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB69 ; ALetter # Lm MODIFIER LETTER SMALL TURNED W +AB70..ABBF ; ALetter # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +ABC0..ABE2 ; ALetter # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +AC00..D7A3 ; ALetter # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH +D7B0..D7C6 ; ALetter # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; ALetter # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +FB00..FB06 ; ALetter # L& [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; ALetter # L& [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FB50..FBB1 ; ALetter # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBD3..FD3D ; ALetter # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD50..FD8F ; ALetter # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; ALetter # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDF0..FDFB ; ALetter # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FE70..FE74 ; ALetter # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; ALetter # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FF21..FF3A ; ALetter # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +FF41..FF5A ; ALetter # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +FFA0..FFBE ; ALetter # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; ALetter # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; ALetter # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; ALetter # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +10000..1000B ; ALetter # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; ALetter # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; ALetter # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; ALetter # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; ALetter # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; ALetter # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; ALetter # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10140..10174 ; ALetter # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10280..1029C ; ALetter # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; ALetter # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3 +10300..1031F ; ALetter # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +1032D..10340 ; ALetter # Lo [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA +10341 ; ALetter # Nl GOTHIC LETTER NINETY +10342..10349 ; ALetter # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A ; ALetter # Nl GOTHIC LETTER NINE HUNDRED +10350..10375 ; ALetter # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10380..1039D ; ALetter # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +103A0..103C3 ; ALetter # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; ALetter # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +103D1..103D5 ; ALetter # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +10400..1044F ; ALetter # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW +10450..1049D ; ALetter # Lo [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO +104B0..104D3 ; ALetter # L& [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +104D8..104FB ; ALetter # L& [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10500..10527 ; ALetter # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; ALetter # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +10570..1057A ; ALetter # L& [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; ALetter # L& [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; ALetter # L& [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; ALetter # L& [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10597..105A1 ; ALetter # L& [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; ALetter # L& [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; ALetter # L& [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; ALetter # L& [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10600..10736 ; ALetter # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; ALetter # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; ALetter # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10780..10785 ; ALetter # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; ALetter # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; ALetter # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +10800..10805 ; ALetter # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; ALetter # Lo CYPRIOT SYLLABLE JO +1080A..10835 ; ALetter # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; ALetter # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; ALetter # Lo CYPRIOT SYLLABLE ZA +1083F..10855 ; ALetter # Lo [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW +10860..10876 ; ALetter # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10880..1089E ; ALetter # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108E0..108F2 ; ALetter # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; ALetter # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +10900..10915 ; ALetter # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10920..10939 ; ALetter # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10980..109B7 ; ALetter # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA +109BE..109BF ; ALetter # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +10A00 ; ALetter # Lo KHAROSHTHI LETTER A +10A10..10A13 ; ALetter # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; ALetter # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; ALetter # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A60..10A7C ; ALetter # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A80..10A9C ; ALetter # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10AC0..10AC7 ; ALetter # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC9..10AE4 ; ALetter # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10B00..10B35 ; ALetter # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B40..10B55 ; ALetter # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B60..10B72 ; ALetter # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B80..10B91 ; ALetter # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10C00..10C48 ; ALetter # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10C80..10CB2 ; ALetter # L& [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +10CC0..10CF2 ; ALetter # L& [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +10D00..10D23 ; ALetter # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10E80..10EA9 ; ALetter # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EB0..10EB1 ; ALetter # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10F00..10F1C ; ALetter # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F27 ; ALetter # Lo OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; ALetter # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F70..10F81 ; ALetter # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10FB0..10FC4 ; ALetter # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FE0..10FF6 ; ALetter # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11003..11037 ; ALetter # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA +11071..11072 ; ALetter # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11075 ; ALetter # Lo BRAHMI LETTER OLD TAMIL LLA +11083..110AF ; ALetter # Lo [45] KAITHI LETTER A..KAITHI LETTER HA +110D0..110E8 ; ALetter # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +11103..11126 ; ALetter # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11144 ; ALetter # Lo CHAKMA LETTER LHAA +11147 ; ALetter # Lo CHAKMA LETTER VAA +11150..11172 ; ALetter # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11176 ; ALetter # Lo MAHAJANI LIGATURE SHRI +11183..111B2 ; ALetter # Lo [48] SHARADA LETTER A..SHARADA LETTER HA +111C1..111C4 ; ALetter # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111DA ; ALetter # Lo SHARADA EKAM +111DC ; ALetter # Lo SHARADA HEADSTROKE +11200..11211 ; ALetter # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; ALetter # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1123F..11240 ; ALetter # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11280..11286 ; ALetter # Lo [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; ALetter # Lo MULTANI LETTER GHA +1128A..1128D ; ALetter # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; ALetter # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; ALetter # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112B0..112DE ; ALetter # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +11305..1130C ; ALetter # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; ALetter # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; ALetter # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; ALetter # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; ALetter # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; ALetter # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133D ; ALetter # Lo GRANTHA SIGN AVAGRAHA +11350 ; ALetter # Lo GRANTHA OM +1135D..11361 ; ALetter # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL +11400..11434 ; ALetter # Lo [53] NEWA LETTER A..NEWA LETTER HA +11447..1144A ; ALetter # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1145F..11461 ; ALetter # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; ALetter # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114C4..114C5 ; ALetter # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C7 ; ALetter # Lo TIRHUTA OM +11580..115AE ; ALetter # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115D8..115DB ; ALetter # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +11600..1162F ; ALetter # Lo [48] MODI LETTER A..MODI LETTER LLA +11644 ; ALetter # Lo MODI SIGN HUVA +11680..116AA ; ALetter # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA +116B8 ; ALetter # Lo TAKRI LETTER ARCHAIC KHA +11800..1182B ; ALetter # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA +118A0..118DF ; ALetter # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +118FF..11906 ; ALetter # Lo [8] WARANG CITI OM..DIVES AKURU LETTER E +11909 ; ALetter # Lo DIVES AKURU LETTER O +1190C..11913 ; ALetter # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; ALetter # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; ALetter # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +1193F ; ALetter # Lo DIVES AKURU PREFIXED NASAL SIGN +11941 ; ALetter # Lo DIVES AKURU INITIAL RA +119A0..119A7 ; ALetter # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; ALetter # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119E1 ; ALetter # Lo NANDINAGARI SIGN AVAGRAHA +119E3 ; ALetter # Lo NANDINAGARI HEADSTROKE +11A00 ; ALetter # Lo ZANABAZAR SQUARE LETTER A +11A0B..11A32 ; ALetter # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A3A ; ALetter # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A50 ; ALetter # Lo SOYOMBO LETTER A +11A5C..11A89 ; ALetter # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A9D ; ALetter # Lo SOYOMBO MARK PLUTA +11AB0..11AF8 ; ALetter # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11C00..11C08 ; ALetter # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; ALetter # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C40 ; ALetter # Lo BHAIKSUKI SIGN AVAGRAHA +11C72..11C8F ; ALetter # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A +11D00..11D06 ; ALetter # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; ALetter # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; ALetter # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D46 ; ALetter # Lo MASARAM GONDI REPHA +11D60..11D65 ; ALetter # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; ALetter # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; ALetter # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D98 ; ALetter # Lo GUNJALA GONDI OM +11EE0..11EF2 ; ALetter # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA +11F02 ; ALetter # Lo KAWI SIGN REPHA +11F04..11F10 ; ALetter # Lo [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; ALetter # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA +11FB0 ; ALetter # Lo LISU LETTER YHA +12000..12399 ; ALetter # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12400..1246E ; ALetter # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM +12480..12543 ; ALetter # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; ALetter # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +13000..1342F ; ALetter # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D +13441..13446 ; ALetter # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +14400..14646 ; ALetter # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; ALetter # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; ALetter # Lo [31] MRO LETTER TA..MRO LETTER TEK +16A70..16ABE ; ALetter # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AD0..16AED ; ALetter # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16B00..16B2F ; ALetter # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B40..16B43 ; ALetter # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16B63..16B77 ; ALetter # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; ALetter # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16E40..16E7F ; ALetter # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16F00..16F4A ; ALetter # Lo [75] MIAO LETTER PA..MIAO LETTER RTE +16F50 ; ALetter # Lo MIAO LETTER NASALIZATION +16F93..16F9F ; ALetter # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; ALetter # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE3 ; ALetter # Lm OLD CHINESE ITERATION MARK +1BC00..1BC6A ; ALetter # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; ALetter # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; ALetter # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; ALetter # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1D400..1D454 ; ALetter # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G +1D456..1D49C ; ALetter # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; ALetter # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; ALetter # L& MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; ALetter # L& [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; ALetter # L& [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B9 ; ALetter # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D +1D4BB ; ALetter # L& MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; ALetter # L& [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D505 ; ALetter # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; ALetter # L& [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; ALetter # L& [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; ALetter # L& [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D51E..1D539 ; ALetter # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; ALetter # L& [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; ALetter # L& [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; ALetter # L& MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; ALetter # L& [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D552..1D6A5 ; ALetter # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6A8..1D6C0 ; ALetter # L& [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6C2..1D6DA ; ALetter # L& [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DC..1D6FA ; ALetter # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA +1D6FC..1D714 ; ALetter # L& [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D716..1D734 ; ALetter # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D736..1D74E ; ALetter # L& [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D750..1D76E ; ALetter # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D770..1D788 ; ALetter # L& [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D78A..1D7A8 ; ALetter # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7AA..1D7C2 ; ALetter # L& [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C4..1D7CB ; ALetter # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA +1DF00..1DF09 ; ALetter # L& [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0A ; ALetter # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1DF0B..1DF1E ; ALetter # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; ALetter # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E030..1E06D ; ALetter # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E100..1E12C ; ALetter # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E137..1E13D ; ALetter # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E14E ; ALetter # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E290..1E2AD ; ALetter # Lo [30] TOTO LETTER PA..TOTO LETTER A +1E2C0..1E2EB ; ALetter # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E4D0..1E4EA ; ALetter # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E4EB ; ALetter # Lm NAG MUNDARI SIGN OJOD +1E7E0..1E7E6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; ALetter # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; ALetter # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; ALetter # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; ALetter # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1E900..1E943 ; ALetter # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA +1E94B ; ALetter # Lm ADLAM NASALIZATION MARK +1EE00..1EE03 ; ALetter # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; ALetter # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; ALetter # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; ALetter # Lo ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; ALetter # Lo ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; ALetter # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; ALetter # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; ALetter # Lo ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; ALetter # Lo ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; ALetter # Lo ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; ALetter # Lo ARABIC MATHEMATICAL TAILED HAH +1EE49 ; ALetter # Lo ARABIC MATHEMATICAL TAILED YEH +1EE4B ; ALetter # Lo ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; ALetter # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; ALetter # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; ALetter # Lo ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; ALetter # Lo ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; ALetter # Lo ARABIC MATHEMATICAL TAILED DAD +1EE5B ; ALetter # Lo ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; ALetter # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; ALetter # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; ALetter # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; ALetter # Lo ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; ALetter # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; ALetter # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; ALetter # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; ALetter # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; ALetter # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; ALetter # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; ALetter # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; ALetter # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; ALetter # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; ALetter # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +1F130..1F149 ; ALetter # So [26] SQUARED LATIN CAPITAL LETTER A..SQUARED LATIN CAPITAL LETTER Z +1F150..1F169 ; ALetter # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z +1F170..1F189 ; ALetter # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z + +# Total code points: 29490 + +# ================================================ + +003A ; MidLetter # Po COLON +00B7 ; MidLetter # Po MIDDLE DOT +0387 ; MidLetter # Po GREEK ANO TELEIA +055F ; MidLetter # Po ARMENIAN ABBREVIATION MARK +05F4 ; MidLetter # Po HEBREW PUNCTUATION GERSHAYIM +2027 ; MidLetter # Po HYPHENATION POINT +FE13 ; MidLetter # Po PRESENTATION FORM FOR VERTICAL COLON +FE55 ; MidLetter # Po SMALL COLON +FF1A ; MidLetter # Po FULLWIDTH COLON + +# Total code points: 9 + +# ================================================ + +002C ; MidNum # Po COMMA +003B ; MidNum # Po SEMICOLON +037E ; MidNum # Po GREEK QUESTION MARK +0589 ; MidNum # Po ARMENIAN FULL STOP +060C..060D ; MidNum # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR +066C ; MidNum # Po ARABIC THOUSANDS SEPARATOR +07F8 ; MidNum # Po NKO COMMA +2044 ; MidNum # Sm FRACTION SLASH +FE10 ; MidNum # Po PRESENTATION FORM FOR VERTICAL COMMA +FE14 ; MidNum # Po PRESENTATION FORM FOR VERTICAL SEMICOLON +FE50 ; MidNum # Po SMALL COMMA +FE54 ; MidNum # Po SMALL SEMICOLON +FF0C ; MidNum # Po FULLWIDTH COMMA +FF1B ; MidNum # Po FULLWIDTH SEMICOLON + +# Total code points: 15 + +# ================================================ + +002E ; MidNumLet # Po FULL STOP +2018 ; MidNumLet # Pi LEFT SINGLE QUOTATION MARK +2019 ; MidNumLet # Pf RIGHT SINGLE QUOTATION MARK +2024 ; MidNumLet # Po ONE DOT LEADER +FE52 ; MidNumLet # Po SMALL FULL STOP +FF07 ; MidNumLet # Po FULLWIDTH APOSTROPHE +FF0E ; MidNumLet # Po FULLWIDTH FULL STOP + +# Total code points: 7 + +# ================================================ + +0030..0039 ; Numeric # Nd [10] DIGIT ZERO..DIGIT NINE +0600..0605 ; Numeric # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +0660..0669 ; Numeric # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +066B ; Numeric # Po ARABIC DECIMAL SEPARATOR +06DD ; Numeric # Cf ARABIC END OF AYAH +06F0..06F9 ; Numeric # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +07C0..07C9 ; Numeric # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE +0890..0891 ; Numeric # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +08E2 ; Numeric # Cf ARABIC DISPUTED END OF AYAH +0966..096F ; Numeric # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +09E6..09EF ; Numeric # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +0A66..0A6F ; Numeric # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0AE6..0AEF ; Numeric # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0B66..0B6F ; Numeric # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0BE6..0BEF ; Numeric # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0C66..0C6F ; Numeric # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0CE6..0CEF ; Numeric # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0D66..0D6F ; Numeric # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0DE6..0DEF ; Numeric # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0E50..0E59 ; Numeric # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE +0ED0..0ED9 ; Numeric # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE +0F20..0F29 ; Numeric # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +1040..1049 ; Numeric # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +1090..1099 ; Numeric # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +17E0..17E9 ; Numeric # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +1810..1819 ; Numeric # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1946..194F ; Numeric # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +19D0..19D9 ; Numeric # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +1A80..1A89 ; Numeric # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; Numeric # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1B50..1B59 ; Numeric # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1BB0..1BB9 ; Numeric # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1C40..1C49 ; Numeric # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C50..1C59 ; Numeric # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +A620..A629 ; Numeric # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE +A8D0..A8D9 ; Numeric # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A900..A909 ; Numeric # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A9D0..A9D9 ; Numeric # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9F0..A9F9 ; Numeric # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +AA50..AA59 ; Numeric # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +ABF0..ABF9 ; Numeric # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +FF10..FF19 ; Numeric # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +104A0..104A9 ; Numeric # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +10D30..10D39 ; Numeric # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +11066..1106F ; Numeric # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +110BD ; Numeric # Cf KAITHI NUMBER SIGN +110CD ; Numeric # Cf KAITHI NUMBER SIGN ABOVE +110F0..110F9 ; Numeric # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11136..1113F ; Numeric # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +111D0..111D9 ; Numeric # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +112F0..112F9 ; Numeric # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11450..11459 ; Numeric # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +114D0..114D9 ; Numeric # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11650..11659 ; Numeric # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE +116C0..116C9 ; Numeric # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11730..11739 ; Numeric # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +118E0..118E9 ; Numeric # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +11950..11959 ; Numeric # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +11C50..11C59 ; Numeric # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11D50..11D59 ; Numeric # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11DA0..11DA9 ; Numeric # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11F50..11F59 ; Numeric # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +16A60..16A69 ; Numeric # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE +16AC0..16AC9 ; Numeric # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16B50..16B59 ; Numeric # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +1D7CE..1D7FF ; Numeric # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1E140..1E149 ; Numeric # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E2F0..1E2F9 ; Numeric # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E4F0..1E4F9 ; Numeric # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E950..1E959 ; Numeric # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1FBF0..1FBF9 ; Numeric # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE + +# Total code points: 693 + +# ================================================ + +005F ; ExtendNumLet # Pc LOW LINE +202F ; ExtendNumLet # Zs NARROW NO-BREAK SPACE +203F..2040 ; ExtendNumLet # Pc [2] UNDERTIE..CHARACTER TIE +2054 ; ExtendNumLet # Pc INVERTED UNDERTIE +FE33..FE34 ; ExtendNumLet # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE4D..FE4F ; ExtendNumLet # Pc [3] DASHED LOW LINE..WAVY LOW LINE +FF3F ; ExtendNumLet # Pc FULLWIDTH LOW LINE + +# Total code points: 11 + +# ================================================ + +200D ; ZWJ # Cf ZERO WIDTH JOINER + +# Total code points: 1 + +# ================================================ + +0020 ; WSegSpace # Zs SPACE +1680 ; WSegSpace # Zs OGHAM SPACE MARK +2000..2006 ; WSegSpace # Zs [7] EN QUAD..SIX-PER-EM SPACE +2008..200A ; WSegSpace # Zs [3] PUNCTUATION SPACE..HAIR SPACE +205F ; WSegSpace # Zs MEDIUM MATHEMATICAL SPACE +3000 ; WSegSpace # Zs IDEOGRAPHIC SPACE + +# Total code points: 14 + +# EOF diff --git a/resources/standards-data/unicode-character-database/build.sh b/resources/standards-data/unicode-character-database/build.sh new file mode 100755 index 00000000000..a1be097be61 --- /dev/null +++ b/resources/standards-data/unicode-character-database/build.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# +# Compile our sourcemap-path remapping module for use by Web builds, releases, etc. +# +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "$(dirname "$THIS_SCRIPT")/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/build/minimum-versions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Downloads Unicode data consumed by various Keyman platforms." \ + clean configure build + +builder_describe_outputs \ + configure /resources/standards-data/unicode-character-database/UnicodeData.txt \ + build /resources/standards-data/unicode-character-database/UnicodeData.txt + +builder_parse "$@" + +# Used by Developer +BLOCKS_SRC_HREF="https://www.unicode.org/Public/$KEYMAN_VERSION_UNICODE/ucd/Blocks.txt" +BLOCKS_SRC_LOCAL="./Blocks.txt" + +UNICODE_DATA_SRC_HREF="https://www.unicode.org/Public/$KEYMAN_VERSION_UNICODE/ucd/UnicodeData.txt" +UNICODE_DATA_SRC_LOCAL="./UnicodeData.txt" + +# Used by common/models/wordbreakers for the default Unicode wordbreaker. +WORDBREAK_PROP_SRC_HREF="https://www.unicode.org/Public/$KEYMAN_VERSION_UNICODE/ucd/auxiliary/WordBreakProperty.txt" +WORDBREAK_PROP_SRC_LOCAL="./WordBreakProperty.txt" + +EMOJI_DATA_SRC_HREF="https://www.unicode.org/Public/$KEYMAN_VERSION_UNICODE/ucd/emoji/emoji-data.txt" +EMOJI_DATA_SRC_LOCAL="./emoji-data.txt" + +function downloadPropertyFile() { + local SRC="$1" + local DEST="$2" + + local RETRY=5 # Curl retries this number of times before giving up + local RETRY_DELAY=5 # Make curl sleep this amount of time before each retry when a transfer has failed + + echo "Downloading ${SRC} - ${RETRY} attempts" + # local URL_DOWNLOAD_FILE=`curl --retry "$RETRY" --retry-delay "$RETRY_DELAY" --silent "${SRC}" | "$JQ" -r .txt` + curl --fail --retry "$RETRY" --retry-delay "$RETRY_DELAY" --silent "$SRC" --output "$DEST" || { + builder_die "Downloading $SRC failed with error $?" + } +} + +do_clean() { + rm -rf **/*.txt +} + +do_configure() { + downloadPropertyFile "${BLOCKS_SRC_HREF}" "${BLOCKS_SRC_LOCAL}" + downloadPropertyFile "${UNICODE_DATA_SRC_HREF}" "${UNICODE_DATA_SRC_LOCAL}" + + downloadPropertyFile "${WORDBREAK_PROP_SRC_HREF}" "${WORDBREAK_PROP_SRC_LOCAL}" + downloadPropertyFile "${EMOJI_DATA_SRC_HREF}" "${EMOJI_DATA_SRC_LOCAL}" +} + +builder_run_action clean do_clean +builder_run_action configure do_configure \ No newline at end of file diff --git a/resources/standards-data/unicode-character-database/emoji-data.txt b/resources/standards-data/unicode-character-database/emoji-data.txt new file mode 100644 index 00000000000..0ba10e9ce4c --- /dev/null +++ b/resources/standards-data/unicode-character-database/emoji-data.txt @@ -0,0 +1,1320 @@ +# emoji-data.txt +# Date: 2023-02-01, 02:22:54 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Emoji Data for UTS #51 +# Used with Emoji Version 15.1 and subsequent minor revisions (if any) +# +# For documentation and usage, see https://www.unicode.org/reports/tr51 +# +# Format: +# ; # +# Note: there is no guarantee as to the structure of whitespace or comments +# +# Characters and sequences are listed in code point order. Users should be shown a more natural order. +# See the CLDR collation order for Emoji. + + +# ================================================ + +# All omitted code points have Emoji=No + +0023 ; Emoji # E0.0 [1] (#️) hash sign +002A ; Emoji # E0.0 [1] (*️) asterisk +0030..0039 ; Emoji # E0.0 [10] (0️..9️) digit zero..digit nine +00A9 ; Emoji # E0.6 [1] (©️) copyright +00AE ; Emoji # E0.6 [1] (®️) registered +203C ; Emoji # E0.6 [1] (‼️) double exclamation mark +2049 ; Emoji # E0.6 [1] (⁉️) exclamation question mark +2122 ; Emoji # E0.6 [1] (™️) trade mark +2139 ; Emoji # E0.6 [1] (ℹ️) information +2194..2199 ; Emoji # E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Emoji # E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Emoji # E0.6 [2] (⌚..⌛) watch..hourglass done +2328 ; Emoji # E1.0 [1] (⌨️) keyboard +23CF ; Emoji # E1.0 [1] (⏏️) eject button +23E9..23EC ; Emoji # E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23ED..23EE ; Emoji # E0.7 [2] (⏭️..⏮️) next track button..last track button +23EF ; Emoji # E1.0 [1] (⏯️) play or pause button +23F0 ; Emoji # E0.6 [1] (⏰) alarm clock +23F1..23F2 ; Emoji # E1.0 [2] (⏱️..⏲️) stopwatch..timer clock +23F3 ; Emoji # E0.6 [1] (⏳) hourglass not done +23F8..23FA ; Emoji # E0.7 [3] (⏸️..⏺️) pause button..record button +24C2 ; Emoji # E0.6 [1] (Ⓜ️) circled M +25AA..25AB ; Emoji # E0.6 [2] (▪️..▫️) black small square..white small square +25B6 ; Emoji # E0.6 [1] (▶️) play button +25C0 ; Emoji # E0.6 [1] (◀️) reverse button +25FB..25FE ; Emoji # E0.6 [4] (◻️..◾) white medium square..black medium-small square +2600..2601 ; Emoji # E0.6 [2] (☀️..☁️) sun..cloud +2602..2603 ; Emoji # E0.7 [2] (☂️..☃️) umbrella..snowman +2604 ; Emoji # E1.0 [1] (☄️) comet +260E ; Emoji # E0.6 [1] (☎️) telephone +2611 ; Emoji # E0.6 [1] (☑️) check box with check +2614..2615 ; Emoji # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2618 ; Emoji # E1.0 [1] (☘️) shamrock +261D ; Emoji # E0.6 [1] (☝️) index pointing up +2620 ; Emoji # E1.0 [1] (☠️) skull and crossbones +2622..2623 ; Emoji # E1.0 [2] (☢️..☣️) radioactive..biohazard +2626 ; Emoji # E1.0 [1] (☦️) orthodox cross +262A ; Emoji # E0.7 [1] (☪️) star and crescent +262E ; Emoji # E1.0 [1] (☮️) peace symbol +262F ; Emoji # E0.7 [1] (☯️) yin yang +2638..2639 ; Emoji # E0.7 [2] (☸️..☹️) wheel of dharma..frowning face +263A ; Emoji # E0.6 [1] (☺️) smiling face +2640 ; Emoji # E4.0 [1] (♀️) female sign +2642 ; Emoji # E4.0 [1] (♂️) male sign +2648..2653 ; Emoji # E0.6 [12] (♈..♓) Aries..Pisces +265F ; Emoji # E11.0 [1] (♟️) chess pawn +2660 ; Emoji # E0.6 [1] (♠️) spade suit +2663 ; Emoji # E0.6 [1] (♣️) club suit +2665..2666 ; Emoji # E0.6 [2] (♥️..♦️) heart suit..diamond suit +2668 ; Emoji # E0.6 [1] (♨️) hot springs +267B ; Emoji # E0.6 [1] (♻️) recycling symbol +267E ; Emoji # E11.0 [1] (♾️) infinity +267F ; Emoji # E0.6 [1] (♿) wheelchair symbol +2692 ; Emoji # E1.0 [1] (⚒️) hammer and pick +2693 ; Emoji # E0.6 [1] (⚓) anchor +2694 ; Emoji # E1.0 [1] (⚔️) crossed swords +2695 ; Emoji # E4.0 [1] (⚕️) medical symbol +2696..2697 ; Emoji # E1.0 [2] (⚖️..⚗️) balance scale..alembic +2699 ; Emoji # E1.0 [1] (⚙️) gear +269B..269C ; Emoji # E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +26A0..26A1 ; Emoji # E0.6 [2] (⚠️..⚡) warning..high voltage +26A7 ; Emoji # E13.0 [1] (⚧️) transgender symbol +26AA..26AB ; Emoji # E0.6 [2] (⚪..⚫) white circle..black circle +26B0..26B1 ; Emoji # E1.0 [2] (⚰️..⚱️) coffin..funeral urn +26BD..26BE ; Emoji # E0.6 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C8 ; Emoji # E0.7 [1] (⛈️) cloud with lightning and rain +26CE ; Emoji # E0.6 [1] (⛎) Ophiuchus +26CF ; Emoji # E0.7 [1] (⛏️) pick +26D1 ; Emoji # E0.7 [1] (⛑️) rescue worker’s helmet +26D3 ; Emoji # E0.7 [1] (⛓️) chains +26D4 ; Emoji # E0.6 [1] (⛔) no entry +26E9 ; Emoji # E0.7 [1] (⛩️) shinto shrine +26EA ; Emoji # E0.6 [1] (⛪) church +26F0..26F1 ; Emoji # E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground +26F2..26F3 ; Emoji # E0.6 [2] (⛲..⛳) fountain..flag in hole +26F4 ; Emoji # E0.7 [1] (⛴️) ferry +26F5 ; Emoji # E0.6 [1] (⛵) sailboat +26F7..26F9 ; Emoji # E0.7 [3] (⛷️..⛹️) skier..person bouncing ball +26FA ; Emoji # E0.6 [1] (⛺) tent +26FD ; Emoji # E0.6 [1] (⛽) fuel pump +2702 ; Emoji # E0.6 [1] (✂️) scissors +2705 ; Emoji # E0.6 [1] (✅) check mark button +2708..270C ; Emoji # E0.6 [5] (✈️..✌️) airplane..victory hand +270D ; Emoji # E0.7 [1] (✍️) writing hand +270F ; Emoji # E0.6 [1] (✏️) pencil +2712 ; Emoji # E0.6 [1] (✒️) black nib +2714 ; Emoji # E0.6 [1] (✔️) check mark +2716 ; Emoji # E0.6 [1] (✖️) multiply +271D ; Emoji # E0.7 [1] (✝️) latin cross +2721 ; Emoji # E0.7 [1] (✡️) star of David +2728 ; Emoji # E0.6 [1] (✨) sparkles +2733..2734 ; Emoji # E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Emoji # E0.6 [1] (❄️) snowflake +2747 ; Emoji # E0.6 [1] (❇️) sparkle +274C ; Emoji # E0.6 [1] (❌) cross mark +274E ; Emoji # E0.6 [1] (❎) cross mark button +2753..2755 ; Emoji # E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Emoji # E0.6 [1] (❗) red exclamation mark +2763 ; Emoji # E1.0 [1] (❣️) heart exclamation +2764 ; Emoji # E0.6 [1] (❤️) red heart +2795..2797 ; Emoji # E0.6 [3] (➕..➗) plus..divide +27A1 ; Emoji # E0.6 [1] (➡️) right arrow +27B0 ; Emoji # E0.6 [1] (➰) curly loop +27BF ; Emoji # E1.0 [1] (➿) double curly loop +2934..2935 ; Emoji # E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Emoji # E0.6 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Emoji # E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji # E0.6 [1] (⭐) star +2B55 ; Emoji # E0.6 [1] (⭕) hollow red circle +3030 ; Emoji # E0.6 [1] (〰️) wavy dash +303D ; Emoji # E0.6 [1] (〽️) part alternation mark +3297 ; Emoji # E0.6 [1] (㊗️) Japanese “congratulations” button +3299 ; Emoji # E0.6 [1] (㊙️) Japanese “secret” button +1F004 ; Emoji # E0.6 [1] (🀄) mahjong red dragon +1F0CF ; Emoji # E0.6 [1] (🃏) joker +1F170..1F171 ; Emoji # E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E..1F17F ; Emoji # E0.6 [2] (🅾️..🅿️) O button (blood type)..P button +1F18E ; Emoji # E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji # E0.6 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201..1F202 ; Emoji # E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F21A ; Emoji # E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji # E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Emoji # E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F250..1F251 ; Emoji # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F30C ; Emoji # E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Emoji # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji # E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Emoji # E1.0 [1] (🌐) globe with meridians +1F311 ; Emoji # E0.6 [1] (🌑) new moon +1F312 ; Emoji # E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Emoji # E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Emoji # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Emoji # E0.6 [1] (🌙) crescent moon +1F31A ; Emoji # E1.0 [1] (🌚) new moon face +1F31B ; Emoji # E0.6 [1] (🌛) first quarter moon face +1F31C ; Emoji # E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Emoji # E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Emoji # E0.6 [2] (🌟..🌠) glowing star..shooting star +1F321 ; Emoji # E0.7 [1] (🌡️) thermometer +1F324..1F32C ; Emoji # E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Emoji # E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Emoji # E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Emoji # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Emoji # E0.6 [2] (🌴..🌵) palm tree..cactus +1F336 ; Emoji # E0.7 [1] (🌶️) hot pepper +1F337..1F34A ; Emoji # E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Emoji # E1.0 [1] (🍋) lemon +1F34C..1F34F ; Emoji # E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Emoji # E1.0 [1] (🍐) pear +1F351..1F37B ; Emoji # E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Emoji # E1.0 [1] (🍼) baby bottle +1F37D ; Emoji # E0.7 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Emoji # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji # E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F396..1F397 ; Emoji # E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon +1F399..1F39B ; Emoji # E0.7 [3] (🎙️..🎛️) studio microphone..control knobs +1F39E..1F39F ; Emoji # E0.7 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Emoji # E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji # E1.0 [1] (🏅) sports medal +1F3C6 ; Emoji # E0.6 [1] (🏆) trophy +1F3C7 ; Emoji # E1.0 [1] (🏇) horse racing +1F3C8 ; Emoji # E0.6 [1] (🏈) american football +1F3C9 ; Emoji # E1.0 [1] (🏉) rugby football +1F3CA ; Emoji # E0.6 [1] (🏊) person swimming +1F3CB..1F3CE ; Emoji # E0.7 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Emoji # E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Emoji # E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3E3 ; Emoji # E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Emoji # E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Emoji # E0.6 [12] (🏥..🏰) hospital..castle +1F3F3 ; Emoji # E0.7 [1] (🏳️) white flag +1F3F4 ; Emoji # E1.0 [1] (🏴) black flag +1F3F5 ; Emoji # E0.7 [1] (🏵️) rosette +1F3F7 ; Emoji # E0.7 [1] (🏷️) label +1F3F8..1F407 ; Emoji # E1.0 [16] (🏸..🐇) badminton..rabbit +1F408 ; Emoji # E0.7 [1] (🐈) cat +1F409..1F40B ; Emoji # E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Emoji # E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Emoji # E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Emoji # E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Emoji # E1.0 [1] (🐓) rooster +1F414 ; Emoji # E0.6 [1] (🐔) chicken +1F415 ; Emoji # E0.7 [1] (🐕) dog +1F416 ; Emoji # E1.0 [1] (🐖) pig +1F417..1F429 ; Emoji # E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Emoji # E1.0 [1] (🐪) camel +1F42B..1F43E ; Emoji # E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F43F ; Emoji # E0.7 [1] (🐿️) chipmunk +1F440 ; Emoji # E0.6 [1] (👀) eyes +1F441 ; Emoji # E0.7 [1] (👁️) eye +1F442..1F464 ; Emoji # E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Emoji # E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Emoji # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Emoji # E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Emoji # E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Emoji # E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji # E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji # E0.6 [1] (📮) postbox +1F4EF ; Emoji # E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Emoji # E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Emoji # E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Emoji # E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Emoji # E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji # E0.6 [4] (📹..📼) video camera..videocassette +1F4FD ; Emoji # E0.7 [1] (📽️) film projector +1F4FF..1F502 ; Emoji # E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Emoji # E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Emoji # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Emoji # E0.7 [1] (🔈) speaker low volume +1F509 ; Emoji # E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Emoji # E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Emoji # E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Emoji # E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Emoji # E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Emoji # E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F549..1F54A ; Emoji # E0.7 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Emoji # E1.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F55B ; Emoji # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Emoji # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F56F..1F570 ; Emoji # E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock +1F573..1F579 ; Emoji # E0.7 [7] (🕳️..🕹️) hole..joystick +1F57A ; Emoji # E3.0 [1] (🕺) man dancing +1F587 ; Emoji # E0.7 [1] (🖇️) linked paperclips +1F58A..1F58D ; Emoji # E0.7 [4] (🖊️..🖍️) pen..crayon +1F590 ; Emoji # E0.7 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji # E3.0 [1] (🖤) black heart +1F5A5 ; Emoji # E0.7 [1] (🖥️) desktop computer +1F5A8 ; Emoji # E0.7 [1] (🖨️) printer +1F5B1..1F5B2 ; Emoji # E0.7 [2] (🖱️..🖲️) computer mouse..trackball +1F5BC ; Emoji # E0.7 [1] (🖼️) framed picture +1F5C2..1F5C4 ; Emoji # E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5D1..1F5D3 ; Emoji # E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5DC..1F5DE ; Emoji # E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5E1 ; Emoji # E0.7 [1] (🗡️) dagger +1F5E3 ; Emoji # E0.7 [1] (🗣️) speaking head +1F5E8 ; Emoji # E2.0 [1] (🗨️) left speech bubble +1F5EF ; Emoji # E0.7 [1] (🗯️) right anger bubble +1F5F3 ; Emoji # E0.7 [1] (🗳️) ballot box with ballot +1F5FA ; Emoji # E0.7 [1] (🗺️) world map +1F5FB..1F5FF ; Emoji # E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji # E1.0 [1] (😀) grinning face +1F601..1F606 ; Emoji # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Emoji # E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Emoji # E0.6 [1] (😏) smirking face +1F610 ; Emoji # E0.7 [1] (😐) neutral face +1F611 ; Emoji # E1.0 [1] (😑) expressionless face +1F612..1F614 ; Emoji # E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji # E1.0 [1] (😕) confused face +1F616 ; Emoji # E0.6 [1] (😖) confounded face +1F617 ; Emoji # E1.0 [1] (😗) kissing face +1F618 ; Emoji # E0.6 [1] (😘) face blowing a kiss +1F619 ; Emoji # E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji # E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Emoji # E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Emoji # E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji # E1.0 [1] (😟) worried face +1F620..1F625 ; Emoji # E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji # E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji # E1.0 [1] (😬) grimacing face +1F62D ; Emoji # E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji # E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji # E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji # E1.0 [1] (😴) sleeping face +1F635 ; Emoji # E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Emoji # E1.0 [1] (😶) face without mouth +1F637..1F640 ; Emoji # E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Emoji # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Emoji # E0.6 [1] (🚀) rocket +1F681..1F682 ; Emoji # E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Emoji # E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Emoji # E1.0 [1] (🚆) train +1F687 ; Emoji # E0.6 [1] (🚇) metro +1F688 ; Emoji # E1.0 [1] (🚈) light rail +1F689 ; Emoji # E0.6 [1] (🚉) station +1F68A..1F68B ; Emoji # E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Emoji # E0.6 [1] (🚌) bus +1F68D ; Emoji # E0.7 [1] (🚍) oncoming bus +1F68E ; Emoji # E1.0 [1] (🚎) trolleybus +1F68F ; Emoji # E0.6 [1] (🚏) bus stop +1F690 ; Emoji # E1.0 [1] (🚐) minibus +1F691..1F693 ; Emoji # E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Emoji # E0.7 [1] (🚔) oncoming police car +1F695 ; Emoji # E0.6 [1] (🚕) taxi +1F696 ; Emoji # E1.0 [1] (🚖) oncoming taxi +1F697 ; Emoji # E0.6 [1] (🚗) automobile +1F698 ; Emoji # E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Emoji # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Emoji # E0.6 [1] (🚢) ship +1F6A3 ; Emoji # E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Emoji # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Emoji # E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Emoji # E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Emoji # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Emoji # E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Emoji # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Emoji # E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Emoji # E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji # E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Emoji # E1.0 [1] (🚿) shower +1F6C0 ; Emoji # E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Emoji # E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6CB ; Emoji # E0.7 [1] (🛋️) couch and lamp +1F6CC ; Emoji # E1.0 [1] (🛌) person in bed +1F6CD..1F6CF ; Emoji # E0.7 [3] (🛍️..🛏️) shopping bags..bed +1F6D0 ; Emoji # E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji # E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E9 ; Emoji # E0.7 [1] (🛩️) small airplane +1F6EB..1F6EC ; Emoji # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F0 ; Emoji # E0.7 [1] (🛰️) satellite +1F6F3 ; Emoji # E0.7 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Emoji # E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji # E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Emoji # E11.0 [1] (🛹) skateboard +1F6FA ; Emoji # E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Emoji # E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F7E0..1F7EB ; Emoji # E12.0 [12] (🟠..🟫) orange circle..brown square +1F7F0 ; Emoji # E14.0 [1] (🟰) heavy equals sign +1F90C ; Emoji # E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Emoji # E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Emoji # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji # E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji # E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Emoji # E12.0 [1] (🤿) diving mask +1F940..1F945 ; Emoji # E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji # E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Emoji # E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Emoji # E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji # E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Emoji # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Emoji # E12.0 [1] (🥱) yawning face +1F972 ; Emoji # E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Emoji # E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Emoji # E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Emoji # E14.0 [1] (🥹) face holding back tears +1F97A ; Emoji # E11.0 [1] (🥺) pleading face +1F97B ; Emoji # E12.0 [1] (🥻) sari +1F97C..1F97F ; Emoji # E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Emoji # E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Emoji # E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji # E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Emoji # E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Emoji # E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Emoji # E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Emoji # E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Emoji # E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Emoji # E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Emoji # E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Emoji # E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Emoji # E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Emoji # E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Emoji # E13.0 [1] (🧋) bubble tea +1F9CC ; Emoji # E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Emoji # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Emoji # E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Emoji # E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA70..1FA73 ; Emoji # E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Emoji # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji # E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Emoji # E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Emoji # E14.0 [2] (🩻..🩼) x-ray..crutch +1FA80..1FA82 ; Emoji # E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute +1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Emoji # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji # E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Emoji # E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Emoji # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji # E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji # E15.0 [2] (🫚..🫛) ginger root..pea pod +1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face +1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 1424 + +# ================================================ + +# All omitted code points have Emoji_Presentation=No + +231A..231B ; Emoji_Presentation # E0.6 [2] (⌚..⌛) watch..hourglass done +23E9..23EC ; Emoji_Presentation # E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23F0 ; Emoji_Presentation # E0.6 [1] (⏰) alarm clock +23F3 ; Emoji_Presentation # E0.6 [1] (⏳) hourglass not done +25FD..25FE ; Emoji_Presentation # E0.6 [2] (◽..◾) white medium-small square..black medium-small square +2614..2615 ; Emoji_Presentation # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2648..2653 ; Emoji_Presentation # E0.6 [12] (♈..♓) Aries..Pisces +267F ; Emoji_Presentation # E0.6 [1] (♿) wheelchair symbol +2693 ; Emoji_Presentation # E0.6 [1] (⚓) anchor +26A1 ; Emoji_Presentation # E0.6 [1] (⚡) high voltage +26AA..26AB ; Emoji_Presentation # E0.6 [2] (⚪..⚫) white circle..black circle +26BD..26BE ; Emoji_Presentation # E0.6 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji_Presentation # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26CE ; Emoji_Presentation # E0.6 [1] (⛎) Ophiuchus +26D4 ; Emoji_Presentation # E0.6 [1] (⛔) no entry +26EA ; Emoji_Presentation # E0.6 [1] (⛪) church +26F2..26F3 ; Emoji_Presentation # E0.6 [2] (⛲..⛳) fountain..flag in hole +26F5 ; Emoji_Presentation # E0.6 [1] (⛵) sailboat +26FA ; Emoji_Presentation # E0.6 [1] (⛺) tent +26FD ; Emoji_Presentation # E0.6 [1] (⛽) fuel pump +2705 ; Emoji_Presentation # E0.6 [1] (✅) check mark button +270A..270B ; Emoji_Presentation # E0.6 [2] (✊..✋) raised fist..raised hand +2728 ; Emoji_Presentation # E0.6 [1] (✨) sparkles +274C ; Emoji_Presentation # E0.6 [1] (❌) cross mark +274E ; Emoji_Presentation # E0.6 [1] (❎) cross mark button +2753..2755 ; Emoji_Presentation # E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Emoji_Presentation # E0.6 [1] (❗) red exclamation mark +2795..2797 ; Emoji_Presentation # E0.6 [3] (➕..➗) plus..divide +27B0 ; Emoji_Presentation # E0.6 [1] (➰) curly loop +27BF ; Emoji_Presentation # E1.0 [1] (➿) double curly loop +2B1B..2B1C ; Emoji_Presentation # E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji_Presentation # E0.6 [1] (⭐) star +2B55 ; Emoji_Presentation # E0.6 [1] (⭕) hollow red circle +1F004 ; Emoji_Presentation # E0.6 [1] (🀄) mahjong red dragon +1F0CF ; Emoji_Presentation # E0.6 [1] (🃏) joker +1F18E ; Emoji_Presentation # E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji_Presentation # E0.6 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji_Presentation # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201 ; Emoji_Presentation # E0.6 [1] (🈁) Japanese “here” button +1F21A ; Emoji_Presentation # E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji_Presentation # E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F236 ; Emoji_Presentation # E0.6 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button +1F238..1F23A ; Emoji_Presentation # E0.6 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button +1F250..1F251 ; Emoji_Presentation # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F30C ; Emoji_Presentation # E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Emoji_Presentation # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji_Presentation # E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Emoji_Presentation # E1.0 [1] (🌐) globe with meridians +1F311 ; Emoji_Presentation # E0.6 [1] (🌑) new moon +1F312 ; Emoji_Presentation # E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Emoji_Presentation # E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Emoji_Presentation # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Emoji_Presentation # E0.6 [1] (🌙) crescent moon +1F31A ; Emoji_Presentation # E1.0 [1] (🌚) new moon face +1F31B ; Emoji_Presentation # E0.6 [1] (🌛) first quarter moon face +1F31C ; Emoji_Presentation # E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Emoji_Presentation # E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Emoji_Presentation # E0.6 [2] (🌟..🌠) glowing star..shooting star +1F32D..1F32F ; Emoji_Presentation # E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Emoji_Presentation # E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Emoji_Presentation # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Emoji_Presentation # E0.6 [2] (🌴..🌵) palm tree..cactus +1F337..1F34A ; Emoji_Presentation # E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Emoji_Presentation # E1.0 [1] (🍋) lemon +1F34C..1F34F ; Emoji_Presentation # E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Emoji_Presentation # E1.0 [1] (🍐) pear +1F351..1F37B ; Emoji_Presentation # E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Emoji_Presentation # E1.0 [1] (🍼) baby bottle +1F37E..1F37F ; Emoji_Presentation # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji_Presentation # E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F3A0..1F3C4 ; Emoji_Presentation # E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji_Presentation # E1.0 [1] (🏅) sports medal +1F3C6 ; Emoji_Presentation # E0.6 [1] (🏆) trophy +1F3C7 ; Emoji_Presentation # E1.0 [1] (🏇) horse racing +1F3C8 ; Emoji_Presentation # E0.6 [1] (🏈) american football +1F3C9 ; Emoji_Presentation # E1.0 [1] (🏉) rugby football +1F3CA ; Emoji_Presentation # E0.6 [1] (🏊) person swimming +1F3CF..1F3D3 ; Emoji_Presentation # E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3E0..1F3E3 ; Emoji_Presentation # E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Emoji_Presentation # E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Emoji_Presentation # E0.6 [12] (🏥..🏰) hospital..castle +1F3F4 ; Emoji_Presentation # E1.0 [1] (🏴) black flag +1F3F8..1F407 ; Emoji_Presentation # E1.0 [16] (🏸..🐇) badminton..rabbit +1F408 ; Emoji_Presentation # E0.7 [1] (🐈) cat +1F409..1F40B ; Emoji_Presentation # E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Emoji_Presentation # E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Emoji_Presentation # E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Emoji_Presentation # E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Emoji_Presentation # E1.0 [1] (🐓) rooster +1F414 ; Emoji_Presentation # E0.6 [1] (🐔) chicken +1F415 ; Emoji_Presentation # E0.7 [1] (🐕) dog +1F416 ; Emoji_Presentation # E1.0 [1] (🐖) pig +1F417..1F429 ; Emoji_Presentation # E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Emoji_Presentation # E1.0 [1] (🐪) camel +1F42B..1F43E ; Emoji_Presentation # E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F440 ; Emoji_Presentation # E0.6 [1] (👀) eyes +1F442..1F464 ; Emoji_Presentation # E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Emoji_Presentation # E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Emoji_Presentation # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Presentation # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Emoji_Presentation # E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Emoji_Presentation # E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Emoji_Presentation # E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji_Presentation # E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji_Presentation # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji_Presentation # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji_Presentation # E0.6 [1] (📮) postbox +1F4EF ; Emoji_Presentation # E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Emoji_Presentation # E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Emoji_Presentation # E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Emoji_Presentation # E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Emoji_Presentation # E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji_Presentation # E0.6 [4] (📹..📼) video camera..videocassette +1F4FF..1F502 ; Emoji_Presentation # E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Emoji_Presentation # E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Emoji_Presentation # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Emoji_Presentation # E0.7 [1] (🔈) speaker low volume +1F509 ; Emoji_Presentation # E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Emoji_Presentation # E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Emoji_Presentation # E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Emoji_Presentation # E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Emoji_Presentation # E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Emoji_Presentation # E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F54B..1F54E ; Emoji_Presentation # E1.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F55B ; Emoji_Presentation # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Emoji_Presentation # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F57A ; Emoji_Presentation # E3.0 [1] (🕺) man dancing +1F595..1F596 ; Emoji_Presentation # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji_Presentation # E3.0 [1] (🖤) black heart +1F5FB..1F5FF ; Emoji_Presentation # E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji_Presentation # E1.0 [1] (😀) grinning face +1F601..1F606 ; Emoji_Presentation # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji_Presentation # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji_Presentation # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Emoji_Presentation # E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Emoji_Presentation # E0.6 [1] (😏) smirking face +1F610 ; Emoji_Presentation # E0.7 [1] (😐) neutral face +1F611 ; Emoji_Presentation # E1.0 [1] (😑) expressionless face +1F612..1F614 ; Emoji_Presentation # E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji_Presentation # E1.0 [1] (😕) confused face +1F616 ; Emoji_Presentation # E0.6 [1] (😖) confounded face +1F617 ; Emoji_Presentation # E1.0 [1] (😗) kissing face +1F618 ; Emoji_Presentation # E0.6 [1] (😘) face blowing a kiss +1F619 ; Emoji_Presentation # E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji_Presentation # E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Emoji_Presentation # E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Emoji_Presentation # E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji_Presentation # E1.0 [1] (😟) worried face +1F620..1F625 ; Emoji_Presentation # E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji_Presentation # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji_Presentation # E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji_Presentation # E1.0 [1] (😬) grimacing face +1F62D ; Emoji_Presentation # E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji_Presentation # E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji_Presentation # E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji_Presentation # E1.0 [1] (😴) sleeping face +1F635 ; Emoji_Presentation # E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Emoji_Presentation # E1.0 [1] (😶) face without mouth +1F637..1F640 ; Emoji_Presentation # E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Emoji_Presentation # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji_Presentation # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Emoji_Presentation # E0.6 [1] (🚀) rocket +1F681..1F682 ; Emoji_Presentation # E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Emoji_Presentation # E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Emoji_Presentation # E1.0 [1] (🚆) train +1F687 ; Emoji_Presentation # E0.6 [1] (🚇) metro +1F688 ; Emoji_Presentation # E1.0 [1] (🚈) light rail +1F689 ; Emoji_Presentation # E0.6 [1] (🚉) station +1F68A..1F68B ; Emoji_Presentation # E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Emoji_Presentation # E0.6 [1] (🚌) bus +1F68D ; Emoji_Presentation # E0.7 [1] (🚍) oncoming bus +1F68E ; Emoji_Presentation # E1.0 [1] (🚎) trolleybus +1F68F ; Emoji_Presentation # E0.6 [1] (🚏) bus stop +1F690 ; Emoji_Presentation # E1.0 [1] (🚐) minibus +1F691..1F693 ; Emoji_Presentation # E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Emoji_Presentation # E0.7 [1] (🚔) oncoming police car +1F695 ; Emoji_Presentation # E0.6 [1] (🚕) taxi +1F696 ; Emoji_Presentation # E1.0 [1] (🚖) oncoming taxi +1F697 ; Emoji_Presentation # E0.6 [1] (🚗) automobile +1F698 ; Emoji_Presentation # E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Emoji_Presentation # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji_Presentation # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Emoji_Presentation # E0.6 [1] (🚢) ship +1F6A3 ; Emoji_Presentation # E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Emoji_Presentation # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Emoji_Presentation # E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Emoji_Presentation # E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Emoji_Presentation # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Emoji_Presentation # E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Emoji_Presentation # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Emoji_Presentation # E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Emoji_Presentation # E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji_Presentation # E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Emoji_Presentation # E1.0 [1] (🚿) shower +1F6C0 ; Emoji_Presentation # E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Emoji_Presentation # E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6CC ; Emoji_Presentation # E1.0 [1] (🛌) person in bed +1F6D0 ; Emoji_Presentation # E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji_Presentation # E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Emoji_Presentation # E11.0 [1] (🛹) skateboard +1F6FA ; Emoji_Presentation # E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Emoji_Presentation # E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F7E0..1F7EB ; Emoji_Presentation # E12.0 [12] (🟠..🟫) orange circle..brown square +1F7F0 ; Emoji_Presentation # E14.0 [1] (🟰) heavy equals sign +1F90C ; Emoji_Presentation # E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Emoji_Presentation # E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Emoji_Presentation # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji_Presentation # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Presentation # E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji_Presentation # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji_Presentation # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji_Presentation # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Presentation # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji_Presentation # E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji_Presentation # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Emoji_Presentation # E12.0 [1] (🤿) diving mask +1F940..1F945 ; Emoji_Presentation # E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji_Presentation # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji_Presentation # E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Emoji_Presentation # E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Emoji_Presentation # E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji_Presentation # E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Emoji_Presentation # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Emoji_Presentation # E12.0 [1] (🥱) yawning face +1F972 ; Emoji_Presentation # E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Emoji_Presentation # E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Emoji_Presentation # E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Emoji_Presentation # E14.0 [1] (🥹) face holding back tears +1F97A ; Emoji_Presentation # E11.0 [1] (🥺) pleading face +1F97B ; Emoji_Presentation # E12.0 [1] (🥻) sari +1F97C..1F97F ; Emoji_Presentation # E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Emoji_Presentation # E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Emoji_Presentation # E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji_Presentation # E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Emoji_Presentation # E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Emoji_Presentation # E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Emoji_Presentation # E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Emoji_Presentation # E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Emoji_Presentation # E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Emoji_Presentation # E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Emoji_Presentation # E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Emoji_Presentation # E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Emoji_Presentation # E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Emoji_Presentation # E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Emoji_Presentation # E13.0 [1] (🧋) bubble tea +1F9CC ; Emoji_Presentation # E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Emoji_Presentation # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Emoji_Presentation # E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Emoji_Presentation # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji_Presentation # E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Emoji_Presentation # E14.0 [2] (🩻..🩼) x-ray..crutch +1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute +1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Emoji_Presentation # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji_Presentation # E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Emoji_Presentation # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji_Presentation # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji_Presentation # E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji_Presentation # E15.0 [2] (🫚..🫛) ginger root..pea pod +1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face +1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 1205 + +# ================================================ + +# All omitted code points have Emoji_Modifier=No + +1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone + +# Total elements: 5 + +# ================================================ + +# All omitted code points have Emoji_Modifier_Base=No + +261D ; Emoji_Modifier_Base # E0.6 [1] (☝️) index pointing up +26F9 ; Emoji_Modifier_Base # E0.7 [1] (⛹️) person bouncing ball +270A..270C ; Emoji_Modifier_Base # E0.6 [3] (✊..✌️) raised fist..victory hand +270D ; Emoji_Modifier_Base # E0.7 [1] (✍️) writing hand +1F385 ; Emoji_Modifier_Base # E0.6 [1] (🎅) Santa Claus +1F3C2..1F3C4 ; Emoji_Modifier_Base # E0.6 [3] (🏂..🏄) snowboarder..person surfing +1F3C7 ; Emoji_Modifier_Base # E1.0 [1] (🏇) horse racing +1F3CA ; Emoji_Modifier_Base # E0.6 [1] (🏊) person swimming +1F3CB..1F3CC ; Emoji_Modifier_Base # E0.7 [2] (🏋️..🏌️) person lifting weights..person golfing +1F442..1F443 ; Emoji_Modifier_Base # E0.6 [2] (👂..👃) ear..nose +1F446..1F450 ; Emoji_Modifier_Base # E0.6 [11] (👆..👐) backhand index pointing up..open hands +1F466..1F46B ; Emoji_Modifier_Base # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Modifier_Base # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F478 ; Emoji_Modifier_Base # E0.6 [11] (👮..👸) police officer..princess +1F47C ; Emoji_Modifier_Base # E0.6 [1] (👼) baby angel +1F481..1F483 ; Emoji_Modifier_Base # E0.6 [3] (💁..💃) person tipping hand..woman dancing +1F485..1F487 ; Emoji_Modifier_Base # E0.6 [3] (💅..💇) nail polish..person getting haircut +1F48F ; Emoji_Modifier_Base # E0.6 [1] (💏) kiss +1F491 ; Emoji_Modifier_Base # E0.6 [1] (💑) couple with heart +1F4AA ; Emoji_Modifier_Base # E0.6 [1] (💪) flexed biceps +1F574..1F575 ; Emoji_Modifier_Base # E0.7 [2] (🕴️..🕵️) person in suit levitating..detective +1F57A ; Emoji_Modifier_Base # E3.0 [1] (🕺) man dancing +1F590 ; Emoji_Modifier_Base # E0.7 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji_Modifier_Base # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F645..1F647 ; Emoji_Modifier_Base # E0.6 [3] (🙅..🙇) person gesturing NO..person bowing +1F64B..1F64F ; Emoji_Modifier_Base # E0.6 [5] (🙋..🙏) person raising hand..folded hands +1F6A3 ; Emoji_Modifier_Base # E1.0 [1] (🚣) person rowing boat +1F6B4..1F6B5 ; Emoji_Modifier_Base # E1.0 [2] (🚴..🚵) person biking..person mountain biking +1F6B6 ; Emoji_Modifier_Base # E0.6 [1] (🚶) person walking +1F6C0 ; Emoji_Modifier_Base # E0.6 [1] (🛀) person taking bath +1F6CC ; Emoji_Modifier_Base # E1.0 [1] (🛌) person in bed +1F90C ; Emoji_Modifier_Base # E13.0 [1] (🤌) pinched fingers +1F90F ; Emoji_Modifier_Base # E12.0 [1] (🤏) pinching hand +1F918 ; Emoji_Modifier_Base # E1.0 [1] (🤘) sign of the horns +1F919..1F91E ; Emoji_Modifier_Base # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Modifier_Base # E5.0 [1] (🤟) love-you gesture +1F926 ; Emoji_Modifier_Base # E3.0 [1] (🤦) person facepalming +1F930 ; Emoji_Modifier_Base # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Modifier_Base # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F939 ; Emoji_Modifier_Base # E3.0 [7] (🤳..🤹) selfie..person juggling +1F93C..1F93E ; Emoji_Modifier_Base # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F977 ; Emoji_Modifier_Base # E13.0 [1] (🥷) ninja +1F9B5..1F9B6 ; Emoji_Modifier_Base # E11.0 [2] (🦵..🦶) leg..foot +1F9B8..1F9B9 ; Emoji_Modifier_Base # E11.0 [2] (🦸..🦹) superhero..supervillain +1F9BB ; Emoji_Modifier_Base # E12.0 [1] (🦻) ear with hearing aid +1F9CD..1F9CF ; Emoji_Modifier_Base # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (🧑..🧝) person..elf +1FAC3..1FAC5 ; Emoji_Modifier_Base # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FAF0..1FAF6 ; Emoji_Modifier_Base # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Modifier_Base # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 134 + +# ================================================ + +# All omitted code points have Emoji_Component=No + +0023 ; Emoji_Component # E0.0 [1] (#️) hash sign +002A ; Emoji_Component # E0.0 [1] (*️) asterisk +0030..0039 ; Emoji_Component # E0.0 [10] (0️..9️) digit zero..digit nine +200D ; Emoji_Component # E0.0 [1] (‍) zero width joiner +20E3 ; Emoji_Component # E0.0 [1] (⃣) combining enclosing keycap +FE0F ; Emoji_Component # E0.0 [1] () VARIATION SELECTOR-16 +1F1E6..1F1FF ; Emoji_Component # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F3FB..1F3FF ; Emoji_Component # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone +1F9B0..1F9B3 ; Emoji_Component # E11.0 [4] (🦰..🦳) red hair..white hair +E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..cancel tag + +# Total elements: 146 + +# ================================================ + +# All omitted code points have Extended_Pictographic=No + +00A9 ; Extended_Pictographic# E0.6 [1] (©️) copyright +00AE ; Extended_Pictographic# E0.6 [1] (®️) registered +203C ; Extended_Pictographic# E0.6 [1] (‼️) double exclamation mark +2049 ; Extended_Pictographic# E0.6 [1] (⁉️) exclamation question mark +2122 ; Extended_Pictographic# E0.6 [1] (™️) trade mark +2139 ; Extended_Pictographic# E0.6 [1] (ℹ️) information +2194..2199 ; Extended_Pictographic# E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done +2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard +2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL +23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button +23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button +23EF ; Extended_Pictographic# E1.0 [1] (⏯️) play or pause button +23F0 ; Extended_Pictographic# E0.6 [1] (⏰) alarm clock +23F1..23F2 ; Extended_Pictographic# E1.0 [2] (⏱️..⏲️) stopwatch..timer clock +23F3 ; Extended_Pictographic# E0.6 [1] (⏳) hourglass not done +23F8..23FA ; Extended_Pictographic# E0.7 [3] (⏸️..⏺️) pause button..record button +24C2 ; Extended_Pictographic# E0.6 [1] (Ⓜ️) circled M +25AA..25AB ; Extended_Pictographic# E0.6 [2] (▪️..▫️) black small square..white small square +25B6 ; Extended_Pictographic# E0.6 [1] (▶️) play button +25C0 ; Extended_Pictographic# E0.6 [1] (◀️) reverse button +25FB..25FE ; Extended_Pictographic# E0.6 [4] (◻️..◾) white medium square..black medium-small square +2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud +2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman +2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet +2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR +2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION +260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone +260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX +2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check +2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X +2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock +2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX +261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up +261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX +2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones +2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN +2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard +2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH +2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross +2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM +262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent +262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE +262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol +262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang +2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH +2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face +263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face +263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY +2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign +2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH +2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign +2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO +2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces +2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT +265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn +2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit +2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT +2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit +2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT +2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit +2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT +2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs +2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS +267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol +267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL +267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity +267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol +2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 +2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG +2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick +2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor +2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords +2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol +2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic +2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER +2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear +269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES +269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT +26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage +26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN +26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol +26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN +26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle +26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL +26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn +26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE +26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball +26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING +26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN +26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain +26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR +26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus +26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick +26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING +26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet +26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES +26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains +26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry +26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD +26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine +26EA ; Extended_Pictographic# E0.6 [1] (⛪) church +26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE +26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground +26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole +26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry +26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat +26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS +26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball +26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent +26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump +26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS +2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors +2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS +2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button +2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand +270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand +270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL +270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil +2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB +2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib +2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark +2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply +271D ; Extended_Pictographic# E0.7 [1] (✝️) latin cross +2721 ; Extended_Pictographic# E0.7 [1] (✡️) star of David +2728 ; Extended_Pictographic# E0.6 [1] (✨) sparkles +2733..2734 ; Extended_Pictographic# E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Extended_Pictographic# E0.6 [1] (❄️) snowflake +2747 ; Extended_Pictographic# E0.6 [1] (❇️) sparkle +274C ; Extended_Pictographic# E0.6 [1] (❌) cross mark +274E ; Extended_Pictographic# E0.6 [1] (❎) cross mark button +2753..2755 ; Extended_Pictographic# E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark +2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation +2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart +2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET +2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide +27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow +27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop +27BF ; Extended_Pictographic# E1.0 [1] (➿) double curly loop +2934..2935 ; Extended_Pictographic# E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Extended_Pictographic# E0.6 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Extended_Pictographic# E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Extended_Pictographic# E0.6 [1] (⭐) star +2B55 ; Extended_Pictographic# E0.6 [1] (⭕) hollow red circle +3030 ; Extended_Pictographic# E0.6 [1] (〰️) wavy dash +303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark +3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button +3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button +1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND +1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon +1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker +1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. +1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL +1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button +1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button +1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. +1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Extended_Pictographic# E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Extended_Pictographic# E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. +1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. +1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Extended_Pictographic# E1.0 [1] (🌐) globe with meridians +1F311 ; Extended_Pictographic# E0.6 [1] (🌑) new moon +1F312 ; Extended_Pictographic# E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Extended_Pictographic# E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Extended_Pictographic# E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Extended_Pictographic# E0.6 [1] (🌙) crescent moon +1F31A ; Extended_Pictographic# E1.0 [1] (🌚) new moon face +1F31B ; Extended_Pictographic# E0.6 [1] (🌛) first quarter moon face +1F31C ; Extended_Pictographic# E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star +1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer +1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN +1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Extended_Pictographic# E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Extended_Pictographic# E0.6 [2] (🌴..🌵) palm tree..cactus +1F336 ; Extended_Pictographic# E0.7 [1] (🌶️) hot pepper +1F337..1F34A ; Extended_Pictographic# E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Extended_Pictographic# E1.0 [1] (🍋) lemon +1F34C..1F34F ; Extended_Pictographic# E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Extended_Pictographic# E1.0 [1] (🍐) pear +1F351..1F37B ; Extended_Pictographic# E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Extended_Pictographic# E1.0 [1] (🍼) baby bottle +1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS +1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon +1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS +1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs +1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES +1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal +1F3C6 ; Extended_Pictographic# E0.6 [1] (🏆) trophy +1F3C7 ; Extended_Pictographic# E1.0 [1] (🏇) horse racing +1F3C8 ; Extended_Pictographic# E0.6 [1] (🏈) american football +1F3C9 ; Extended_Pictographic# E1.0 [1] (🏉) rugby football +1F3CA ; Extended_Pictographic# E0.6 [1] (🏊) person swimming +1F3CB..1F3CE ; Extended_Pictographic# E0.7 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Extended_Pictographic# E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Extended_Pictographic# E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle +1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT +1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag +1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag +1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette +1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE +1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label +1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora +1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit +1F408 ; Extended_Pictographic# E0.7 [1] (🐈) cat +1F409..1F40B ; Extended_Pictographic# E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Extended_Pictographic# E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Extended_Pictographic# E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Extended_Pictographic# E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Extended_Pictographic# E1.0 [1] (🐓) rooster +1F414 ; Extended_Pictographic# E0.6 [1] (🐔) chicken +1F415 ; Extended_Pictographic# E0.7 [1] (🐕) dog +1F416 ; Extended_Pictographic# E1.0 [1] (🐖) pig +1F417..1F429 ; Extended_Pictographic# E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Extended_Pictographic# E1.0 [1] (🐪) camel +1F42B..1F43E ; Extended_Pictographic# E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F43F ; Extended_Pictographic# E0.7 [1] (🐿️) chipmunk +1F440 ; Extended_Pictographic# E0.6 [1] (👀) eyes +1F441 ; Extended_Pictographic# E0.7 [1] (👁️) eye +1F442..1F464 ; Extended_Pictographic# E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Extended_Pictographic# E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Extended_Pictographic# E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Extended_Pictographic# E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Extended_Pictographic# E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Extended_Pictographic# E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Extended_Pictographic# E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Extended_Pictographic# E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Extended_Pictographic# E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Extended_Pictographic# E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Extended_Pictographic# E0.6 [1] (📮) postbox +1F4EF ; Extended_Pictographic# E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Extended_Pictographic# E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Extended_Pictographic# E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Extended_Pictographic# E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette +1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector +1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO +1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Extended_Pictographic# E0.7 [1] (🔈) speaker low volume +1F509 ; Extended_Pictographic# E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Extended_Pictographic# E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Extended_Pictographic# E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS +1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah +1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA +1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK +1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock +1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY +1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick +1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing +1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE +1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips +1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL +1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon +1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN +1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed +1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND +1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart +1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer +1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS +1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer +1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE +1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball +1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE +1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture +1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER +1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES +1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL +1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART +1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger +1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS +1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head +1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT +1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble +1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE +1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble +1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD +1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot +1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK +1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map +1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face +1F601..1F606 ; Extended_Pictographic# E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Extended_Pictographic# E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Extended_Pictographic# E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Extended_Pictographic# E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Extended_Pictographic# E0.6 [1] (😏) smirking face +1F610 ; Extended_Pictographic# E0.7 [1] (😐) neutral face +1F611 ; Extended_Pictographic# E1.0 [1] (😑) expressionless face +1F612..1F614 ; Extended_Pictographic# E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Extended_Pictographic# E1.0 [1] (😕) confused face +1F616 ; Extended_Pictographic# E0.6 [1] (😖) confounded face +1F617 ; Extended_Pictographic# E1.0 [1] (😗) kissing face +1F618 ; Extended_Pictographic# E0.6 [1] (😘) face blowing a kiss +1F619 ; Extended_Pictographic# E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Extended_Pictographic# E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Extended_Pictographic# E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Extended_Pictographic# E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Extended_Pictographic# E1.0 [1] (😟) worried face +1F620..1F625 ; Extended_Pictographic# E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Extended_Pictographic# E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Extended_Pictographic# E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Extended_Pictographic# E1.0 [1] (😬) grimacing face +1F62D ; Extended_Pictographic# E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Extended_Pictographic# E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Extended_Pictographic# E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Extended_Pictographic# E1.0 [1] (😴) sleeping face +1F635 ; Extended_Pictographic# E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Extended_Pictographic# E1.0 [1] (😶) face without mouth +1F637..1F640 ; Extended_Pictographic# E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Extended_Pictographic# E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Extended_Pictographic# E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Extended_Pictographic# E0.6 [1] (🚀) rocket +1F681..1F682 ; Extended_Pictographic# E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Extended_Pictographic# E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Extended_Pictographic# E1.0 [1] (🚆) train +1F687 ; Extended_Pictographic# E0.6 [1] (🚇) metro +1F688 ; Extended_Pictographic# E1.0 [1] (🚈) light rail +1F689 ; Extended_Pictographic# E0.6 [1] (🚉) station +1F68A..1F68B ; Extended_Pictographic# E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Extended_Pictographic# E0.6 [1] (🚌) bus +1F68D ; Extended_Pictographic# E0.7 [1] (🚍) oncoming bus +1F68E ; Extended_Pictographic# E1.0 [1] (🚎) trolleybus +1F68F ; Extended_Pictographic# E0.6 [1] (🚏) bus stop +1F690 ; Extended_Pictographic# E1.0 [1] (🚐) minibus +1F691..1F693 ; Extended_Pictographic# E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Extended_Pictographic# E0.7 [1] (🚔) oncoming police car +1F695 ; Extended_Pictographic# E0.6 [1] (🚕) taxi +1F696 ; Extended_Pictographic# E1.0 [1] (🚖) oncoming taxi +1F697 ; Extended_Pictographic# E0.6 [1] (🚗) automobile +1F698 ; Extended_Pictographic# E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Extended_Pictographic# E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Extended_Pictographic# E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Extended_Pictographic# E0.6 [1] (🚢) ship +1F6A3 ; Extended_Pictographic# E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Extended_Pictographic# E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Extended_Pictographic# E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Extended_Pictographic# E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Extended_Pictographic# E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Extended_Pictographic# E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Extended_Pictographic# E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Extended_Pictographic# E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Extended_Pictographic# E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Extended_Pictographic# E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower +1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL +1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp +1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed +1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed +1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA +1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator +1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE +1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane +1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE +1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. +1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite +1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE +1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Extended_Pictographic# E11.0 [1] (🛹) skateboard +1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. +1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS +1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square +1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. +1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign +1F7F1..1F7FF ; Extended_Pictographic# E0.0 [15] (🟱..🟿) .. +1F80C..1F80F ; Extended_Pictographic# E0.0 [4] (🠌..🠏) .. +1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. +1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. +1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. +1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Extended_Pictographic# E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Extended_Pictographic# E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Extended_Pictographic# E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Extended_Pictographic# E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Extended_Pictographic# E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Extended_Pictographic# E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Extended_Pictographic# E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Extended_Pictographic# E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Extended_Pictographic# E12.0 [1] (🤿) diving mask +1F940..1F945 ; Extended_Pictographic# E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Extended_Pictographic# E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Extended_Pictographic# E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Extended_Pictographic# E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Extended_Pictographic# E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Extended_Pictographic# E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Extended_Pictographic# E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Extended_Pictographic# E12.0 [1] (🥱) yawning face +1F972 ; Extended_Pictographic# E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Extended_Pictographic# E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Extended_Pictographic# E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Extended_Pictographic# E14.0 [1] (🥹) face holding back tears +1F97A ; Extended_Pictographic# E11.0 [1] (🥺) pleading face +1F97B ; Extended_Pictographic# E12.0 [1] (🥻) sari +1F97C..1F97F ; Extended_Pictographic# E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Extended_Pictographic# E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Extended_Pictographic# E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Extended_Pictographic# E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Extended_Pictographic# E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Extended_Pictographic# E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Extended_Pictographic# E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Extended_Pictographic# E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Extended_Pictographic# E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Extended_Pictographic# E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Extended_Pictographic# E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Extended_Pictographic# E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Extended_Pictographic# E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Extended_Pictographic# E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Extended_Pictographic# E13.0 [1] (🧋) bubble tea +1F9CC ; Extended_Pictographic# E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Extended_Pictographic# E14.0 [2] (🩻..🩼) x-ray..crutch +1FA7D..1FA7F ; Extended_Pictographic# E0.0 [3] (🩽..🩿) .. +1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute +1FA89..1FA8F ; Extended_Pictographic# E0.0 [7] (🪉..🪏) .. +1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Extended_Pictographic# E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Extended_Pictographic# E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Extended_Pictographic# E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Extended_Pictographic# E15.0 [3] (🪻..🪽) hyacinth..wing +1FABE ; Extended_Pictographic# E0.0 [1] (🪾) +1FABF ; Extended_Pictographic# E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FAC6..1FACD ; Extended_Pictographic# E0.0 [8] (🫆..🫍) .. +1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Extended_Pictographic# E15.0 [2] (🫚..🫛) ginger root..pea pod +1FADC..1FADF ; Extended_Pictographic# E0.0 [4] (🫜..🫟) .. +1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face +1FAE9..1FAEF ; Extended_Pictographic# E0.0 [7] (🫩..🫯) .. +1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand +1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. +1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. + +# Total elements: 3537 + +#EOF diff --git a/resources/standards-data/unicode-character-database/unicode-copyright.txt b/resources/standards-data/unicode-character-database/unicode-copyright.txt deleted file mode 100644 index 812c525e15f..00000000000 --- a/resources/standards-data/unicode-character-database/unicode-copyright.txt +++ /dev/null @@ -1,56 +0,0 @@ -Unicode Data Files ("DATA FILES") include all data files under the directories: -http://www.unicode.org/Public/ -http://www.unicode.org/reports/ -https://www.unicode.org/ivd/data/ - -Unicode Data Files do not include PDF online code charts under the -directory: -http://www.unicode.org/Public/ - -Unicode Software ("SOFTWARE") includes any source code published in the Unicode Standard -or any source code or compiled code under the directories: -https://www.unicode.org/Public/PROGRAMS/ -https://www.unicode.org/Public/cldr/ -https://site.icu-project.org/download/ - -NOTICE TO USER: Carefully read the following legal agreement. -BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S -DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), -YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE -TERMS AND CONDITIONS OF THIS AGREEMENT. -IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE -THE DATA FILES OR SOFTWARE. - -COPYRIGHT AND PERMISSION NOTICE - -Copyright © 1991-2023 Unicode, Inc. All rights reserved. -Distributed under the Terms of Use in https://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Unicode data files and any associated documentation -(the "Data Files") or Unicode software and any associated documentation -(the "Software") to deal in the Data Files or Software -without restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, and/or sell copies of -the Data Files or Software, and to permit persons to whom the Data Files -or Software are furnished to do so, provided that either -(a) this copyright and permission notice appear with all copies -of the Data Files or Software, or -(b) this copyright and permission notice appear in associated -Documentation. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS -NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL -DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, -DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THE DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in these Data Files or Software without prior -written authorization of the copyright holder. From 77b94bf559f1cae8ec84c2c257aa39db499942f2 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 6 Aug 2024 14:47:04 +0700 Subject: [PATCH 021/262] feat(common/models): integrates wordbreaking-data processor into wordbreaker toolchain --- common/models/wordbreakers/UNICODE_VERSION.md | 1 - common/models/wordbreakers/build.sh | 4 +++- .../wordbreakers/src/data-compiler/index.ts | 10 ++-------- .../src/imports/WordBreakProperty-13.0.0.txt.gz | Bin 23663 -> 0 bytes .../src/imports/emoji-data-13.0.0.txt.gz | Bin 23400 -> 0 bytes .../wordbreakers/src/main/default/data.ts | 11 ++++++++--- common/models/wordbreakers/tsconfig.json | 16 ---------------- 7 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 common/models/wordbreakers/UNICODE_VERSION.md delete mode 100644 common/models/wordbreakers/src/imports/WordBreakProperty-13.0.0.txt.gz delete mode 100644 common/models/wordbreakers/src/imports/emoji-data-13.0.0.txt.gz delete mode 100644 common/models/wordbreakers/tsconfig.json diff --git a/common/models/wordbreakers/UNICODE_VERSION.md b/common/models/wordbreakers/UNICODE_VERSION.md deleted file mode 100644 index 70045d3c9a6..00000000000 --- a/common/models/wordbreakers/UNICODE_VERSION.md +++ /dev/null @@ -1 +0,0 @@ -13.0.0 \ No newline at end of file diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index bd28c11acf9..561bc5a1152 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -13,6 +13,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe "Builds the predictive-text wordbreaker implementation module" \ + "@/resources/standards-data/unicode-character-database" \ "clean" \ "configure" \ "build" \ @@ -26,7 +27,8 @@ builder_describe_outputs \ builder_parse "$@" function do_build() { - tsc -b + tsc -b src/data-compiler/tsconfig.json + tsc -b src/main/tsconfig.json # Declaration bundling. tsc -p src/main/tsconfig.json --emitDeclarationOnly --outFile ./build/lib/index.d.ts diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts index 3ef1dc1fc79..4e3a6db337f 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -33,16 +33,10 @@ const MAX_CODE_POINT = 0x10FFFF; const projectDir = path.dirname(require.resolve("@keymanapp/models-wordbreakers/UNICODE_VERSION.md")); const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data.ts'); -// Ensure this package's major version number is in sync with the Unicode -// major version. - -const packageVersion = new String(fs.readFileSync(`${projectDir}/UNICODE_VERSION.md`)); -const UNICODE_VERSION = packageVersion.split('.')[0] + '.0.0'; - // The data files should be in this repository, with names matching the // Unicode version. -let wordBoundaryFilename = path.join(projectDir, `./src/imports/WordBreakProperty-${UNICODE_VERSION}.txt`); -let emojiDataFilename = path.join(projectDir, `./src/imports/emoji-data-${UNICODE_VERSION}.txt`); +const wordBoundaryFilename = path.join(projectDir, `./resources/standards-data/unicode-character-database/WordBreakProperty.txt`); +const emojiDataFilename = path.join(projectDir, `./resources/standards-data/unicode-character-database/emoji-data.txt`); ///////////////////////////// Word_Boundary file ///////////////////////////// diff --git a/common/models/wordbreakers/src/imports/WordBreakProperty-13.0.0.txt.gz b/common/models/wordbreakers/src/imports/WordBreakProperty-13.0.0.txt.gz deleted file mode 100644 index d3d188ae8249794dfe489b2dabaab286bf7f4725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23663 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 SwySxQKmGqYUQY-}lK}wuYX*M+ diff --git a/common/models/wordbreakers/src/imports/emoji-data-13.0.0.txt.gz b/common/models/wordbreakers/src/imports/emoji-data-13.0.0.txt.gz deleted file mode 100644 index bf2d917698e8109106f2ae2da8f778c222ce7f4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23400 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%0DIL-cBHi5~rE~}k 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 aQ%ik#j4iV%A}=wXX`((eecZ`JMEEab8)bz6 diff --git a/common/models/wordbreakers/src/main/default/data.ts b/common/models/wordbreakers/src/main/default/data.ts index abd128f020e..0b3bc14942a 100644 --- a/common/models/wordbreakers/src/main/default/data.ts +++ b/common/models/wordbreakers/src/main/default/data.ts @@ -2,8 +2,11 @@ /** * Valid values for a word break property. + * + * Is optimized away at compile-time; use `propertyMap` to find the mapped + * value at runtime for a property name if needed. */ -export const enum WordBreakProperty { // Scary bit: this does not exist as an object at run-time! +export const enum WordBreakProperty { Other, LF, Newline, @@ -27,8 +30,10 @@ export const enum WordBreakProperty { // Scary bit: this does not exist as an o eot }; -// Not currently built by the auto-generator tool, but it easily could be. -// If and when we import the data.ts rebuilder, we can add this in. +/** + * Contains property names per associated index, as this is compiled away + * by TypeScript for `const enum` cases like `WordBreakProperty`. + */ export const propertyMap = [ "Other", "LF", diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json deleted file mode 100644 index 1cebab27c4a..00000000000 --- a/common/models/wordbreakers/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../tsconfig.kmw-worker-base.json", - - "compilerOptions": { - "baseUrl": "./", - "outDir": "build/obj", - "tsBuildInfoFile": "build/tsconfig.tsbuildinfo", - "rootDir": "./", - "module": "Node16", - "moduleResolution": "Node16" - }, - "references": [ - { "path": "./src/main" }, - { "path": "./src/data-compiler" } - ] -} \ No newline at end of file From 242eb6c0ad38414a5af03e129a3acaa30b289a87 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 6 Aug 2024 14:55:30 +0700 Subject: [PATCH 022/262] fix(common): restore, preserve unicode-copyright.txt --- .../unicode-character-database/build.sh | 4 +- .../unicode-copyright.txt | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 resources/standards-data/unicode-character-database/unicode-copyright.txt diff --git a/resources/standards-data/unicode-character-database/build.sh b/resources/standards-data/unicode-character-database/build.sh index a1be097be61..edb3c1e6ba6 100755 --- a/resources/standards-data/unicode-character-database/build.sh +++ b/resources/standards-data/unicode-character-database/build.sh @@ -51,7 +51,9 @@ function downloadPropertyFile() { } do_clean() { - rm -rf **/*.txt + mv unicode-copyright.txt unicode-copyright.txt.bak + rm -rf *.txt + mv unicode-copyright.txt.bak unicode-copyright.txt } do_configure() { diff --git a/resources/standards-data/unicode-character-database/unicode-copyright.txt b/resources/standards-data/unicode-character-database/unicode-copyright.txt new file mode 100644 index 00000000000..812c525e15f --- /dev/null +++ b/resources/standards-data/unicode-character-database/unicode-copyright.txt @@ -0,0 +1,56 @@ +Unicode Data Files ("DATA FILES") include all data files under the directories: +http://www.unicode.org/Public/ +http://www.unicode.org/reports/ +https://www.unicode.org/ivd/data/ + +Unicode Data Files do not include PDF online code charts under the +directory: +http://www.unicode.org/Public/ + +Unicode Software ("SOFTWARE") includes any source code published in the Unicode Standard +or any source code or compiled code under the directories: +https://www.unicode.org/Public/PROGRAMS/ +https://www.unicode.org/Public/cldr/ +https://site.icu-project.org/download/ + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2023 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. From 3ee71158fea207d9936ffd72360fdc67d660b8f8 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 6 Aug 2024 16:03:19 +0700 Subject: [PATCH 023/262] fix(windows): Don't translate "Keyman" in some Windows strings --- windows/src/desktop/kmshell/xml/strings.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 1e1df09ce00..4cef9daa9ba 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -792,13 +792,18 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman Start Keyman + + + + Start %1$s + From 8b408730b7ba04f502f6f1c9c0c165d8cbec6527 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Tue, 6 Aug 2024 17:14:34 +0700 Subject: [PATCH 024/262] change(mac): uses Library directory instead of Documents Write new key KMDataModelVersion in UserDefaults with integer value that determines how data is expected to be stored. Migrates existing Keyman data to new location if it was written with an older (or no) version. Fixes: #2542 --- .../Keyman4MacIM/KMDataRepository.m | 36 +++--- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 10 +- .../Keyman4MacIM/KMSettingsRepository.h | 2 +- .../Keyman4MacIM/KMSettingsRepository.m | 112 ++++++++++-------- 4 files changed, 87 insertions(+), 73 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index ac2d678ce57..24495007bd4 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -55,9 +55,9 @@ - (NSURL *)documentsSubDirectory { NSURL *documentsUrl = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; if (directoryError) { - os_log_error([KMLogs startupLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); + os_log_error([KMLogs dataLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "Documents subdirectory: '%{public}@'", documentsUrl); + os_log_info([KMLogs dataLog], "Documents subdirectory: '%{public}@'", documentsUrl); _documentsSubDirectory = documentsUrl; } } @@ -72,9 +72,9 @@ - (NSURL *)applicationSupportSubDirectory { NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; if (directoryError) { - os_log_error([KMLogs startupLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); + os_log_error([KMLogs dataLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); + os_log_info([KMLogs dataLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); _applicationSupportSubDirectory = applicationSupportUrl; } } @@ -107,15 +107,15 @@ - (void)createDataDirectoryIfNecessary { if (!exists) { NSError *createError = nil; - os_log_info([KMLogs startupLog], "createDataDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanDataDirectory.path); + os_log_info([KMLogs dataLog], "createDataDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanDataDirectory.path); [fileManager createDirectoryAtPath:self.keymanDataDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; if (createError) { - os_log_error([KMLogs startupLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); + os_log_error([KMLogs dataLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); + os_log_info([KMLogs dataLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); } } else { - os_log_info([KMLogs startupLog], "Keyman data directory already exists: '%{public}@'", self.keymanDataDirectory.path); + os_log_info([KMLogs dataLog], "Keyman data directory already exists: '%{public}@'", self.keymanDataDirectory.path); } } @@ -130,15 +130,15 @@ - (void)createKeyboardsDirectoryIfNecessary { if (!exists) { NSError *createError = nil; - os_log_info([KMLogs startupLog], "createKeyboardsDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "createKeyboardsDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanKeyboardsDirectory.path); [fileManager createDirectoryAtPath:self.keymanKeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; if (createError) { - os_log_error([KMLogs startupLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); + os_log_error([KMLogs dataLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); } } else { - os_log_info([KMLogs startupLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); } } @@ -150,7 +150,7 @@ - (NSURL *)obsoleteKeymanKeyboardsDirectory { return _obsoleteKeymanKeyboardsDirectory; } -- (BOOL)keyboardsExistInDocumentsFolder { +- (BOOL)keyboardsExistInObsoleteDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; BOOL exists = ([fileManager fileExistsAtPath:self.obsoleteKeymanKeyboardsDirectory.path isDirectory:&isDir]); @@ -163,19 +163,19 @@ - (BOOL)keyboardsExistInDocumentsFolder { - (BOOL)migrateData { BOOL didMoveData = NO; NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; - os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + BOOL dataExistsInOldLocation = [self keyboardsExistInObsoleteDirectory]; + os_log([KMLogs dataLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); - // only move data if there is something there to move + // only move data if there is something to move if (dataExistsInOldLocation) { NSError *moveError = nil; didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory toURL:self.keymanKeyboardsDirectory error:&moveError]; if (moveError) { - os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); + os_log_error([KMLogs dataLog], "data migration failed: '%{public}@'", moveError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index fb7edaad012..7d3709ae4f2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -745,25 +745,23 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { - [self preparePersistence]; + [self prepareStorage]; } /** * Prepare the app environment for all the things that need to be persisted: - * namely, the keyboard data on disk and the settings in UserDefaults + * namely, the keyboard data on disk and the settings in UserDefaults */ -- (void)preparePersistence { +- (void)prepareStorage { [KMDataRepository.shared createDataDirectoryIfNecessary]; if ([KMSettingsRepository.shared dataMigrationNeeded]) { BOOL movedData = [KMDataRepository.shared migrateData]; - //os_log_info([KMLogs startupLog], "test: call migrateData again"); - //[KMDataRepository.shared migrateData]; [KMSettingsRepository.shared convertSettingsForMigration]; } [KMDataRepository.shared createKeyboardsDirectoryIfNecessary]; - [KMSettingsRepository.shared createStorageFlagIfNecessary]; + [KMSettingsRepository.shared setDataModelVersionIfNecessary]; } - (void)setDefaultKeymanMenuItems { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index 9a2fcb4c413..e6fe4dca38a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN + (KMSettingsRepository *)shared; - (BOOL)dataMigrationNeeded; - (void)convertSettingsForMigration; -- (void)createStorageFlagIfNecessary; +- (void)setDataModelVersionIfNecessary; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 5381651e313..489c4e63dc6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -7,14 +7,12 @@ * Created by Shawn Schantz on 2024-07-29. * * Singleton object for reading and writing Keyman application settings. - * Serves as an abstraction to StandardUserDefaults which is currently used to persist application settings. + * Serves as an abstraction to StandardUserDefaults which is currently used to store application settings. */ #import "KMSettingsRepository.h" -//#import "KMDataRepository.h" #import "KMLogs.h" -NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibrary"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; @@ -22,6 +20,14 @@ NSString *const kObsoletePathComponent = @"/Documents/"; NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; +/** + * Store the version number of the data model in the UserDefaults with this key. + * The first version, 1, is defined to indicate that we are storing the data/keyboards in the Library + * directory instead of in the Documents directory. + */ +NSString *const kDataModelVersion = @"KMDataModelVersion"; +NSInteger const kVersionStoreDataInLibraryDirectory = 1; + @implementation KMSettingsRepository + (KMSettingsRepository *)shared @@ -34,18 +40,29 @@ + (KMSettingsRepository *)shared return shared; } -- (void)createStorageFlagIfNecessary { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kStoreDataInLibraryKey]; +- (void)setDataModelVersionIfNecessary { + if (![self dataStoredInLibraryDirectory]) { + [[NSUserDefaults standardUserDefaults] setInteger:kVersionStoreDataInLibraryDirectory forKey:kDataModelVersion]; + } } +/** + * If the selectedKeyboard has not been set, then the settings have not been saved in the UserDefaults. + * If this method is called after applicationDidFinishLaunching, then it will always return true. + * If called from awakeFromNib, then it will return false when running for the first time. + */ - (BOOL)settingsExist { - return [[NSUserDefaults standardUserDefaults] objectForKey:kActiveKeyboardsKey] != nil; + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil; } +/** + * For the first numbered version of the data model, the app stores the keyboards under the /Library directory + * For versions before version 1, the keyboards were stored under the /Documents directory. + */ - (BOOL)dataStoredInLibraryDirectory { - return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreDataInLibraryKey]; + return [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion] == kVersionStoreDataInLibraryDirectory; } /** @@ -56,14 +73,47 @@ - (BOOL)dataStoredInLibraryDirectory */ - (BOOL)dataMigrationNeeded { BOOL keymanSettingsExist = [self settingsExist]; - os_log([KMLogs startupLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); + os_log([KMLogs dataLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); BOOL dataInLibrary = [self dataStoredInLibraryDirectory]; - os_log([KMLogs startupLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); + os_log([KMLogs dataLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); return !(keymanSettingsExist && dataInLibrary); } +- (NSString *)selectedKeyboard { + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; +} + +- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; +} + +- (NSMutableArray *)activeKeyboards { + NSMutableArray * activeKeyboards = [[[NSUserDefaults standardUserDefaults] arrayForKey:kActiveKeyboardsKey] mutableCopy]; + + if (!activeKeyboards) { + activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + } + return activeKeyboards; +} + +- (NSDictionary *)persistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData dictionaryForKey:kPersistedOptionsKey]; +} + +- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; +} + +- (void)removePersistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData removeObjectForKey:kPersistedOptionsKey]; +} + - (void)convertSettingsForMigration { [self convertSelectedKeyboardPathForMigration]; [self convertActiveKeyboardArrayForMigration]; @@ -78,7 +128,7 @@ - (void)convertSelectedKeyboardPathForMigration { if ([selectedKeyboardPath isNotEqualTo:newPathString]) { [self saveSelectedKeyboard:newPathString]; - os_log([KMLogs startupLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); + os_log([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); } } } @@ -96,15 +146,6 @@ - (NSString *)convertOldKeyboardPath:(NSString *)oldPath { return newPathString; } -- (NSString *)selectedKeyboard { - return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; -} - -- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; -} - - (void)convertActiveKeyboardArrayForMigration { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; NSMutableArray *activeKeyboards = [self activeKeyboards]; @@ -115,7 +156,7 @@ - (void)convertActiveKeyboardArrayForMigration { NSString *newPath = [self convertOldKeyboardPath:oldPath]; if ([oldPath isNotEqualTo:newPath]) { [convertedActiveKeyboards addObject:newPath]; - os_log([KMLogs startupLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); + os_log([KMLogs dataLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); // if we have adjusted at least one path, set flag didConvert = YES; } else { @@ -126,19 +167,10 @@ - (void)convertActiveKeyboardArrayForMigration { // only update array in UserDefaults if we actually converted something if (didConvert) { - [[NSUserDefaults standardUserDefaults] setObject:convertedActiveKeyboards forKey:kActiveKeyboardsKey]; + [[NSUserDefaults standardUserDefaults] setObject:convertedActiveKeyboards forKey:kActiveKeyboardsKey]; } } -- (NSMutableArray *)activeKeyboards { - NSMutableArray * activeKeyboards = [[[NSUserDefaults standardUserDefaults] arrayForKey:kActiveKeyboardsKey] mutableCopy]; - - if (!activeKeyboards) { - activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; - } - return activeKeyboards; -} - - (void)convertPersistedOptionsPathsForMigration { NSDictionary * optionsMap = [self persistedOptions]; NSMutableDictionary *mutableOptionsMap = nil; @@ -159,10 +191,10 @@ - (void)convertPersistedOptionsPathsForMigration { // insert options into new map with newly converted path as key [mutableOptionsMap setObject:optionsValue forKey:newPathString]; - os_log([KMLogs startupLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); + os_log([KMLogs dataLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); } else { // retain options that did not need converting - [mutableOptionsMap setObject:optionsValue forKey:key]; + [mutableOptionsMap setObject:optionsValue forKey:key]; } } if (optionsChanged) { @@ -171,20 +203,4 @@ - (void)convertPersistedOptionsPathsForMigration { } } -- (NSDictionary *)persistedOptions { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - return [userData dictionaryForKey:kPersistedOptionsKey]; -} - -- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; -} - -- (void)removePersistedOptions { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - return [userData removeObjectForKey:kPersistedOptionsKey]; -} - - @end From 4335f19a6ac7b6ba69d35f95e7e4adf85b2e4fb8 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 24 Jun 2024 09:48:26 -0500 Subject: [PATCH 025/262] fix(developer): remove redundant check in linter - simplify LdmlKeyboardCompiler.validate() by removing a redundant check --- developer/src/kmc-ldml/src/compiler/compiler.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index 59ed5f9a072..bdb11ac176d 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -291,10 +291,14 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { } /** - * Runs any linter steps + * Runs any linter steps, adding hints to the callbacks as needed * @internal + * @returns true unless there was a linter failure. */ private async lint(source: LDMLKeyboardXMLSourceFile, kmx: KMXPlus.KMXPlusFile) : Promise { + if (!kmx || !source) { + return false; + } // run each of the linters for (const linter of this.buildLinters(source, kmx)) { if (!await linter.lint()) { @@ -319,12 +323,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler { } // Run the linters - if (!await this.lint(source, kmx)) { - return false; - } - - // We are valid if we have a keyboard file at this point. - return !!kmx; + return (await this.lint(source, kmx)); } /** From c7f4631fea307c9d02859a765bab62b53e4ba1bd Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 08:34:57 +0700 Subject: [PATCH 026/262] fix(common/models): fix tsconfig issue from this PR's changes --- .../wordbreakers/src/main/tsconfig.json | 19 ------------------- common/models/wordbreakers/tsconfig.json | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) delete mode 100644 common/models/wordbreakers/src/main/tsconfig.json create mode 100644 common/models/wordbreakers/tsconfig.json diff --git a/common/models/wordbreakers/src/main/tsconfig.json b/common/models/wordbreakers/src/main/tsconfig.json deleted file mode 100644 index ce66f0bbfc1..00000000000 --- a/common/models/wordbreakers/src/main/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "../../../tsconfig.kmw-worker-base.json", - - "compilerOptions": { - "baseUrl": "./", - "outDir": "../../build/obj", - "tsBuildInfoFile": "../../build/obj/tsconfig.tsbuildinfo", - "rootDir": "./" - }, - "references": [ - { "path": "../../../types" } - ], - "include": [ - "./**/*" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json new file mode 100644 index 00000000000..64d193242de --- /dev/null +++ b/common/models/wordbreakers/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig.kmw-worker-base.json", + + "compilerOptions": { + "baseUrl": "./", + "outDir": "./build/obj", + "tsBuildInfoFile": "./build/obj/tsconfig.tsbuildinfo", + "rootDir": "./src/main" + }, + "references": [ + { "path": "../types" } + ], + "include": [ + "./src/main/**/*" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file From e2dffff68d0653c4f6d4961f8479550e438b1470 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 08:36:17 +0700 Subject: [PATCH 027/262] fix(common): fix data-compiler path references --- common/models/wordbreakers/package.json | 4 ++-- .../wordbreakers/src/data-compiler/index.ts | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index c2b295ec421..d433bcdff49 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -28,8 +28,8 @@ "./test-index": { "default": "./build/obj/test-index.js" }, - "./UNICODE_VERSION.md": { - "default": "./UNICODE_VERSION.md" + "./README.md": { + "default": "./README.md" } }, "directories": { diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts index 4e3a6db337f..c986d132815 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -30,13 +30,13 @@ const MAX_CODE_POINT = 0x10FFFF; //////////////////////////////////// Main //////////////////////////////////// -const projectDir = path.dirname(require.resolve("@keymanapp/models-wordbreakers/UNICODE_VERSION.md")); +const projectDir = path.dirname(require.resolve("@keymanapp/models-wordbreakers/README.md")); const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data.ts'); // The data files should be in this repository, with names matching the // Unicode version. -const wordBoundaryFilename = path.join(projectDir, `./resources/standards-data/unicode-character-database/WordBreakProperty.txt`); -const emojiDataFilename = path.join(projectDir, `./resources/standards-data/unicode-character-database/emoji-data.txt`); +const wordBoundaryFilename = path.join(projectDir, `../../../resources/standards-data/unicode-character-database/WordBreakProperty.txt`); +const emojiDataFilename = path.join(projectDir, `../../../resources/standards-data/unicode-character-database/emoji-data.txt`); ///////////////////////////// Word_Boundary file ///////////////////////////// @@ -105,6 +105,7 @@ let stream = fs.createWriteStream(generatedFilename); // Generate the file! stream.write(`// Automatically generated file. DO NOT MODIFY. +// The generator script is defined at /common/models/wordbreakers/src/data-compiler/index.ts. /** * Valid values for a word break property. @@ -140,6 +141,21 @@ export const enum I { Value = 1 } +/** + * Defines a mapping of all characters to their assigned word-breaking + * property type. + * + * There are implicit buckets starting at the char with specified code \`number\` + * of an entry up to, but not including, the value in the next entry. All + * entries in each bucket share the same property value. + * + * Consider the following two consecutive buckets: + * - [0x0041, WordBreakProperty.ALetter] + * - [0x005B, WordBreakProperty.Other] + * + * For this example, all characters from 0x0041 to 0x005B (that is, 'A'-'Z') + * have the wordbreaking property \`ALetter\`. + */ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ ${ // TODO: Two versions: one that's BMP-encoded, one that's non-BMP encoded. From 217ad29a5ba6e8e344d4fa791327334907eb32a6 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 08:36:25 +0700 Subject: [PATCH 028/262] fix(common/models): actually run the data-compiler --- common/models/wordbreakers/build.sh | 6 +- .../wordbreakers/src/main/default/data.ts | 180 ++++++++++++++---- 2 files changed, 149 insertions(+), 37 deletions(-) diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index 561bc5a1152..8dfa0f7cb6d 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -28,10 +28,12 @@ builder_parse "$@" function do_build() { tsc -b src/data-compiler/tsconfig.json - tsc -b src/main/tsconfig.json + node ./build/obj/data-compiler/index.js + + tsc -b ./tsconfig.json # Declaration bundling. - tsc -p src/main/tsconfig.json --emitDeclarationOnly --outFile ./build/lib/index.d.ts + tsc -p ./tsconfig.json --emitDeclarationOnly --outFile ./build/lib/index.d.ts } function do_test() { diff --git a/common/models/wordbreakers/src/main/default/data.ts b/common/models/wordbreakers/src/main/default/data.ts index 0b3bc14942a..e1cd8aeb88c 100644 --- a/common/models/wordbreakers/src/main/default/data.ts +++ b/common/models/wordbreakers/src/main/default/data.ts @@ -1,4 +1,5 @@ // Automatically generated file. DO NOT MODIFY. +// The generator script is defined at /common/models/wordbreakers/src/data-compiler/index.ts. /** * Valid values for a word break property. @@ -66,6 +67,21 @@ export const enum I { Value = 1 } +/** + * Defines a mapping of all characters to their assigned word-breaking + * property type. + * + * There are implicit buckets starting at the char with specified code `number` + * of an entry up to, but not including, the value in the next entry. All + * entries in each bucket share the same property value. + * + * Consider the following two consecutive buckets: + * - [0x0041, WordBreakProperty.ALetter] + * - [0x005B, WordBreakProperty.Other] + * + * For this example, all characters from 0x0041 to 0x005B (that is, 'A'-'Z') + * have the wordbreaking property `ALetter`. + */ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x0, WordBreakProperty.Other], [/*start*/ 0xA, WordBreakProperty.LF], @@ -161,7 +177,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x5F3, WordBreakProperty.ALetter], [/*start*/ 0x5F4, WordBreakProperty.MidLetter], [/*start*/ 0x5F5, WordBreakProperty.Other], - [/*start*/ 0x600, WordBreakProperty.Format], + [/*start*/ 0x600, WordBreakProperty.Numeric], [/*start*/ 0x606, WordBreakProperty.Other], [/*start*/ 0x60C, WordBreakProperty.MidNum], [/*start*/ 0x60E, WordBreakProperty.Other], @@ -182,7 +198,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x6D4, WordBreakProperty.Other], [/*start*/ 0x6D5, WordBreakProperty.ALetter], [/*start*/ 0x6D6, WordBreakProperty.Extend], - [/*start*/ 0x6DD, WordBreakProperty.Format], + [/*start*/ 0x6DD, WordBreakProperty.Numeric], [/*start*/ 0x6DE, WordBreakProperty.Other], [/*start*/ 0x6DF, WordBreakProperty.Extend], [/*start*/ 0x6E5, WordBreakProperty.ALetter], @@ -195,8 +211,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x6FD, WordBreakProperty.Other], [/*start*/ 0x6FF, WordBreakProperty.ALetter], [/*start*/ 0x700, WordBreakProperty.Other], - [/*start*/ 0x70F, WordBreakProperty.Format], - [/*start*/ 0x710, WordBreakProperty.ALetter], + [/*start*/ 0x70F, WordBreakProperty.ALetter], [/*start*/ 0x711, WordBreakProperty.Extend], [/*start*/ 0x712, WordBreakProperty.ALetter], [/*start*/ 0x730, WordBreakProperty.Extend], @@ -230,12 +245,16 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x85C, WordBreakProperty.Other], [/*start*/ 0x860, WordBreakProperty.ALetter], [/*start*/ 0x86B, WordBreakProperty.Other], + [/*start*/ 0x870, WordBreakProperty.ALetter], + [/*start*/ 0x888, WordBreakProperty.Other], + [/*start*/ 0x889, WordBreakProperty.ALetter], + [/*start*/ 0x88F, WordBreakProperty.Other], + [/*start*/ 0x890, WordBreakProperty.Numeric], + [/*start*/ 0x892, WordBreakProperty.Other], + [/*start*/ 0x898, WordBreakProperty.Extend], [/*start*/ 0x8A0, WordBreakProperty.ALetter], - [/*start*/ 0x8B5, WordBreakProperty.Other], - [/*start*/ 0x8B6, WordBreakProperty.ALetter], - [/*start*/ 0x8C8, WordBreakProperty.Other], - [/*start*/ 0x8D3, WordBreakProperty.Extend], - [/*start*/ 0x8E2, WordBreakProperty.Format], + [/*start*/ 0x8CA, WordBreakProperty.Extend], + [/*start*/ 0x8E2, WordBreakProperty.Numeric], [/*start*/ 0x8E3, WordBreakProperty.Extend], [/*start*/ 0x904, WordBreakProperty.ALetter], [/*start*/ 0x93A, WordBreakProperty.Extend], @@ -428,6 +447,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0xC29, WordBreakProperty.Other], [/*start*/ 0xC2A, WordBreakProperty.ALetter], [/*start*/ 0xC3A, WordBreakProperty.Other], + [/*start*/ 0xC3C, WordBreakProperty.Extend], [/*start*/ 0xC3D, WordBreakProperty.ALetter], [/*start*/ 0xC3E, WordBreakProperty.Extend], [/*start*/ 0xC45, WordBreakProperty.Other], @@ -439,6 +459,8 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0xC57, WordBreakProperty.Other], [/*start*/ 0xC58, WordBreakProperty.ALetter], [/*start*/ 0xC5B, WordBreakProperty.Other], + [/*start*/ 0xC5D, WordBreakProperty.ALetter], + [/*start*/ 0xC5E, WordBreakProperty.Other], [/*start*/ 0xC60, WordBreakProperty.ALetter], [/*start*/ 0xC62, WordBreakProperty.Extend], [/*start*/ 0xC64, WordBreakProperty.Other], @@ -467,7 +489,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0xCCE, WordBreakProperty.Other], [/*start*/ 0xCD5, WordBreakProperty.Extend], [/*start*/ 0xCD7, WordBreakProperty.Other], - [/*start*/ 0xCDE, WordBreakProperty.ALetter], + [/*start*/ 0xCDD, WordBreakProperty.ALetter], [/*start*/ 0xCDF, WordBreakProperty.Other], [/*start*/ 0xCE0, WordBreakProperty.ALetter], [/*start*/ 0xCE2, WordBreakProperty.Extend], @@ -475,7 +497,8 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0xCE6, WordBreakProperty.Numeric], [/*start*/ 0xCF0, WordBreakProperty.Other], [/*start*/ 0xCF1, WordBreakProperty.ALetter], - [/*start*/ 0xCF3, WordBreakProperty.Other], + [/*start*/ 0xCF3, WordBreakProperty.Extend], + [/*start*/ 0xCF4, WordBreakProperty.Other], [/*start*/ 0xD00, WordBreakProperty.Extend], [/*start*/ 0xD04, WordBreakProperty.ALetter], [/*start*/ 0xD0D, WordBreakProperty.Other], @@ -538,7 +561,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0xEB4, WordBreakProperty.Extend], [/*start*/ 0xEBD, WordBreakProperty.Other], [/*start*/ 0xEC8, WordBreakProperty.Extend], - [/*start*/ 0xECE, WordBreakProperty.Other], + [/*start*/ 0xECF, WordBreakProperty.Other], [/*start*/ 0xED0, WordBreakProperty.Numeric], [/*start*/ 0xEDA, WordBreakProperty.Other], [/*start*/ 0xF00, WordBreakProperty.ALetter], @@ -647,11 +670,9 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x16EE, WordBreakProperty.ALetter], [/*start*/ 0x16F9, WordBreakProperty.Other], [/*start*/ 0x1700, WordBreakProperty.ALetter], - [/*start*/ 0x170D, WordBreakProperty.Other], - [/*start*/ 0x170E, WordBreakProperty.ALetter], [/*start*/ 0x1712, WordBreakProperty.Extend], - [/*start*/ 0x1715, WordBreakProperty.Other], - [/*start*/ 0x1720, WordBreakProperty.ALetter], + [/*start*/ 0x1716, WordBreakProperty.Other], + [/*start*/ 0x171F, WordBreakProperty.ALetter], [/*start*/ 0x1732, WordBreakProperty.Extend], [/*start*/ 0x1735, WordBreakProperty.Other], [/*start*/ 0x1740, WordBreakProperty.ALetter], @@ -671,7 +692,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x17EA, WordBreakProperty.Other], [/*start*/ 0x180B, WordBreakProperty.Extend], [/*start*/ 0x180E, WordBreakProperty.Format], - [/*start*/ 0x180F, WordBreakProperty.Other], + [/*start*/ 0x180F, WordBreakProperty.Extend], [/*start*/ 0x1810, WordBreakProperty.Numeric], [/*start*/ 0x181A, WordBreakProperty.Other], [/*start*/ 0x1820, WordBreakProperty.ALetter], @@ -707,12 +728,12 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1A90, WordBreakProperty.Numeric], [/*start*/ 0x1A9A, WordBreakProperty.Other], [/*start*/ 0x1AB0, WordBreakProperty.Extend], - [/*start*/ 0x1AC1, WordBreakProperty.Other], + [/*start*/ 0x1ACF, WordBreakProperty.Other], [/*start*/ 0x1B00, WordBreakProperty.Extend], [/*start*/ 0x1B05, WordBreakProperty.ALetter], [/*start*/ 0x1B34, WordBreakProperty.Extend], [/*start*/ 0x1B45, WordBreakProperty.ALetter], - [/*start*/ 0x1B4C, WordBreakProperty.Other], + [/*start*/ 0x1B4D, WordBreakProperty.Other], [/*start*/ 0x1B50, WordBreakProperty.Numeric], [/*start*/ 0x1B5A, WordBreakProperty.Other], [/*start*/ 0x1B6B, WordBreakProperty.Extend], @@ -753,8 +774,6 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1CFB, WordBreakProperty.Other], [/*start*/ 0x1D00, WordBreakProperty.ALetter], [/*start*/ 0x1DC0, WordBreakProperty.Extend], - [/*start*/ 0x1DFA, WordBreakProperty.Other], - [/*start*/ 0x1DFB, WordBreakProperty.Extend], [/*start*/ 0x1E00, WordBreakProperty.ALetter], [/*start*/ 0x1F16, WordBreakProperty.Other], [/*start*/ 0x1F18, WordBreakProperty.ALetter], @@ -860,10 +879,6 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x24B6, WordBreakProperty.ALetter], [/*start*/ 0x24EA, WordBreakProperty.Other], [/*start*/ 0x2C00, WordBreakProperty.ALetter], - [/*start*/ 0x2C2F, WordBreakProperty.Other], - [/*start*/ 0x2C30, WordBreakProperty.ALetter], - [/*start*/ 0x2C5F, WordBreakProperty.Other], - [/*start*/ 0x2C60, WordBreakProperty.ALetter], [/*start*/ 0x2CE5, WordBreakProperty.Other], [/*start*/ 0x2CEB, WordBreakProperty.ALetter], [/*start*/ 0x2CEF, WordBreakProperty.Extend], @@ -952,10 +967,14 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0xA6F0, WordBreakProperty.Extend], [/*start*/ 0xA6F2, WordBreakProperty.Other], [/*start*/ 0xA708, WordBreakProperty.ALetter], - [/*start*/ 0xA7C0, WordBreakProperty.Other], - [/*start*/ 0xA7C2, WordBreakProperty.ALetter], [/*start*/ 0xA7CB, WordBreakProperty.Other], - [/*start*/ 0xA7F5, WordBreakProperty.ALetter], + [/*start*/ 0xA7D0, WordBreakProperty.ALetter], + [/*start*/ 0xA7D2, WordBreakProperty.Other], + [/*start*/ 0xA7D3, WordBreakProperty.ALetter], + [/*start*/ 0xA7D4, WordBreakProperty.Other], + [/*start*/ 0xA7D5, WordBreakProperty.ALetter], + [/*start*/ 0xA7DA, WordBreakProperty.Other], + [/*start*/ 0xA7F2, WordBreakProperty.ALetter], [/*start*/ 0xA802, WordBreakProperty.Extend], [/*start*/ 0xA803, WordBreakProperty.ALetter], [/*start*/ 0xA806, WordBreakProperty.Extend], @@ -1188,12 +1207,34 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x10528, WordBreakProperty.Other], [/*start*/ 0x10530, WordBreakProperty.ALetter], [/*start*/ 0x10564, WordBreakProperty.Other], + [/*start*/ 0x10570, WordBreakProperty.ALetter], + [/*start*/ 0x1057B, WordBreakProperty.Other], + [/*start*/ 0x1057C, WordBreakProperty.ALetter], + [/*start*/ 0x1058B, WordBreakProperty.Other], + [/*start*/ 0x1058C, WordBreakProperty.ALetter], + [/*start*/ 0x10593, WordBreakProperty.Other], + [/*start*/ 0x10594, WordBreakProperty.ALetter], + [/*start*/ 0x10596, WordBreakProperty.Other], + [/*start*/ 0x10597, WordBreakProperty.ALetter], + [/*start*/ 0x105A2, WordBreakProperty.Other], + [/*start*/ 0x105A3, WordBreakProperty.ALetter], + [/*start*/ 0x105B2, WordBreakProperty.Other], + [/*start*/ 0x105B3, WordBreakProperty.ALetter], + [/*start*/ 0x105BA, WordBreakProperty.Other], + [/*start*/ 0x105BB, WordBreakProperty.ALetter], + [/*start*/ 0x105BD, WordBreakProperty.Other], [/*start*/ 0x10600, WordBreakProperty.ALetter], [/*start*/ 0x10737, WordBreakProperty.Other], [/*start*/ 0x10740, WordBreakProperty.ALetter], [/*start*/ 0x10756, WordBreakProperty.Other], [/*start*/ 0x10760, WordBreakProperty.ALetter], [/*start*/ 0x10768, WordBreakProperty.Other], + [/*start*/ 0x10780, WordBreakProperty.ALetter], + [/*start*/ 0x10786, WordBreakProperty.Other], + [/*start*/ 0x10787, WordBreakProperty.ALetter], + [/*start*/ 0x107B1, WordBreakProperty.Other], + [/*start*/ 0x107B2, WordBreakProperty.ALetter], + [/*start*/ 0x107BB, WordBreakProperty.Other], [/*start*/ 0x10800, WordBreakProperty.ALetter], [/*start*/ 0x10806, WordBreakProperty.Other], [/*start*/ 0x10808, WordBreakProperty.ALetter], @@ -1272,6 +1313,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x10EAD, WordBreakProperty.Other], [/*start*/ 0x10EB0, WordBreakProperty.ALetter], [/*start*/ 0x10EB2, WordBreakProperty.Other], + [/*start*/ 0x10EFD, WordBreakProperty.Extend], [/*start*/ 0x10F00, WordBreakProperty.ALetter], [/*start*/ 0x10F1D, WordBreakProperty.Other], [/*start*/ 0x10F27, WordBreakProperty.ALetter], @@ -1279,6 +1321,9 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x10F30, WordBreakProperty.ALetter], [/*start*/ 0x10F46, WordBreakProperty.Extend], [/*start*/ 0x10F51, WordBreakProperty.Other], + [/*start*/ 0x10F70, WordBreakProperty.ALetter], + [/*start*/ 0x10F82, WordBreakProperty.Extend], + [/*start*/ 0x10F86, WordBreakProperty.Other], [/*start*/ 0x10FB0, WordBreakProperty.ALetter], [/*start*/ 0x10FC5, WordBreakProperty.Other], [/*start*/ 0x10FE0, WordBreakProperty.ALetter], @@ -1288,14 +1333,20 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x11038, WordBreakProperty.Extend], [/*start*/ 0x11047, WordBreakProperty.Other], [/*start*/ 0x11066, WordBreakProperty.Numeric], - [/*start*/ 0x11070, WordBreakProperty.Other], + [/*start*/ 0x11070, WordBreakProperty.Extend], + [/*start*/ 0x11071, WordBreakProperty.ALetter], + [/*start*/ 0x11073, WordBreakProperty.Extend], + [/*start*/ 0x11075, WordBreakProperty.ALetter], + [/*start*/ 0x11076, WordBreakProperty.Other], [/*start*/ 0x1107F, WordBreakProperty.Extend], [/*start*/ 0x11083, WordBreakProperty.ALetter], [/*start*/ 0x110B0, WordBreakProperty.Extend], [/*start*/ 0x110BB, WordBreakProperty.Other], - [/*start*/ 0x110BD, WordBreakProperty.Format], + [/*start*/ 0x110BD, WordBreakProperty.Numeric], [/*start*/ 0x110BE, WordBreakProperty.Other], - [/*start*/ 0x110CD, WordBreakProperty.Format], + [/*start*/ 0x110C2, WordBreakProperty.Extend], + [/*start*/ 0x110C3, WordBreakProperty.Other], + [/*start*/ 0x110CD, WordBreakProperty.Numeric], [/*start*/ 0x110CE, WordBreakProperty.Other], [/*start*/ 0x110D0, WordBreakProperty.ALetter], [/*start*/ 0x110E9, WordBreakProperty.Other], @@ -1335,7 +1386,9 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1122C, WordBreakProperty.Extend], [/*start*/ 0x11238, WordBreakProperty.Other], [/*start*/ 0x1123E, WordBreakProperty.Extend], - [/*start*/ 0x1123F, WordBreakProperty.Other], + [/*start*/ 0x1123F, WordBreakProperty.ALetter], + [/*start*/ 0x11241, WordBreakProperty.Extend], + [/*start*/ 0x11242, WordBreakProperty.Other], [/*start*/ 0x11280, WordBreakProperty.ALetter], [/*start*/ 0x11287, WordBreakProperty.Other], [/*start*/ 0x11288, WordBreakProperty.ALetter], @@ -1480,7 +1533,7 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x11A9A, WordBreakProperty.Other], [/*start*/ 0x11A9D, WordBreakProperty.ALetter], [/*start*/ 0x11A9E, WordBreakProperty.Other], - [/*start*/ 0x11AC0, WordBreakProperty.ALetter], + [/*start*/ 0x11AB0, WordBreakProperty.ALetter], [/*start*/ 0x11AF9, WordBreakProperty.Other], [/*start*/ 0x11C00, WordBreakProperty.ALetter], [/*start*/ 0x11C09, WordBreakProperty.Other], @@ -1532,6 +1585,18 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x11EE0, WordBreakProperty.ALetter], [/*start*/ 0x11EF3, WordBreakProperty.Extend], [/*start*/ 0x11EF7, WordBreakProperty.Other], + [/*start*/ 0x11F00, WordBreakProperty.Extend], + [/*start*/ 0x11F02, WordBreakProperty.ALetter], + [/*start*/ 0x11F03, WordBreakProperty.Extend], + [/*start*/ 0x11F04, WordBreakProperty.ALetter], + [/*start*/ 0x11F11, WordBreakProperty.Other], + [/*start*/ 0x11F12, WordBreakProperty.ALetter], + [/*start*/ 0x11F34, WordBreakProperty.Extend], + [/*start*/ 0x11F3B, WordBreakProperty.Other], + [/*start*/ 0x11F3E, WordBreakProperty.Extend], + [/*start*/ 0x11F43, WordBreakProperty.Other], + [/*start*/ 0x11F50, WordBreakProperty.Numeric], + [/*start*/ 0x11F5A, WordBreakProperty.Other], [/*start*/ 0x11FB0, WordBreakProperty.ALetter], [/*start*/ 0x11FB1, WordBreakProperty.Other], [/*start*/ 0x12000, WordBreakProperty.ALetter], @@ -1540,10 +1605,14 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1246F, WordBreakProperty.Other], [/*start*/ 0x12480, WordBreakProperty.ALetter], [/*start*/ 0x12544, WordBreakProperty.Other], + [/*start*/ 0x12F90, WordBreakProperty.ALetter], + [/*start*/ 0x12FF1, WordBreakProperty.Other], [/*start*/ 0x13000, WordBreakProperty.ALetter], - [/*start*/ 0x1342F, WordBreakProperty.Other], [/*start*/ 0x13430, WordBreakProperty.Format], - [/*start*/ 0x13439, WordBreakProperty.Other], + [/*start*/ 0x13440, WordBreakProperty.Extend], + [/*start*/ 0x13441, WordBreakProperty.ALetter], + [/*start*/ 0x13447, WordBreakProperty.Extend], + [/*start*/ 0x13456, WordBreakProperty.Other], [/*start*/ 0x14400, WordBreakProperty.ALetter], [/*start*/ 0x14647, WordBreakProperty.Other], [/*start*/ 0x16800, WordBreakProperty.ALetter], @@ -1552,6 +1621,10 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x16A5F, WordBreakProperty.Other], [/*start*/ 0x16A60, WordBreakProperty.Numeric], [/*start*/ 0x16A6A, WordBreakProperty.Other], + [/*start*/ 0x16A70, WordBreakProperty.ALetter], + [/*start*/ 0x16ABF, WordBreakProperty.Other], + [/*start*/ 0x16AC0, WordBreakProperty.Numeric], + [/*start*/ 0x16ACA, WordBreakProperty.Other], [/*start*/ 0x16AD0, WordBreakProperty.ALetter], [/*start*/ 0x16AEE, WordBreakProperty.Other], [/*start*/ 0x16AF0, WordBreakProperty.Extend], @@ -1585,8 +1658,18 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x16FE5, WordBreakProperty.Other], [/*start*/ 0x16FF0, WordBreakProperty.Extend], [/*start*/ 0x16FF2, WordBreakProperty.Other], + [/*start*/ 0x1AFF0, WordBreakProperty.Katakana], + [/*start*/ 0x1AFF4, WordBreakProperty.Other], + [/*start*/ 0x1AFF5, WordBreakProperty.Katakana], + [/*start*/ 0x1AFFC, WordBreakProperty.Other], + [/*start*/ 0x1AFFD, WordBreakProperty.Katakana], + [/*start*/ 0x1AFFF, WordBreakProperty.Other], [/*start*/ 0x1B000, WordBreakProperty.Katakana], [/*start*/ 0x1B001, WordBreakProperty.Other], + [/*start*/ 0x1B120, WordBreakProperty.Katakana], + [/*start*/ 0x1B123, WordBreakProperty.Other], + [/*start*/ 0x1B155, WordBreakProperty.Katakana], + [/*start*/ 0x1B156, WordBreakProperty.Other], [/*start*/ 0x1B164, WordBreakProperty.Katakana], [/*start*/ 0x1B168, WordBreakProperty.Other], [/*start*/ 0x1BC00, WordBreakProperty.ALetter], @@ -1601,6 +1684,10 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1BC9F, WordBreakProperty.Other], [/*start*/ 0x1BCA0, WordBreakProperty.Format], [/*start*/ 0x1BCA4, WordBreakProperty.Other], + [/*start*/ 0x1CF00, WordBreakProperty.Extend], + [/*start*/ 0x1CF2E, WordBreakProperty.Other], + [/*start*/ 0x1CF30, WordBreakProperty.Extend], + [/*start*/ 0x1CF47, WordBreakProperty.Other], [/*start*/ 0x1D165, WordBreakProperty.Extend], [/*start*/ 0x1D16A, WordBreakProperty.Other], [/*start*/ 0x1D16D, WordBreakProperty.Extend], @@ -1687,6 +1774,10 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1DAA0, WordBreakProperty.Other], [/*start*/ 0x1DAA1, WordBreakProperty.Extend], [/*start*/ 0x1DAB0, WordBreakProperty.Other], + [/*start*/ 0x1DF00, WordBreakProperty.ALetter], + [/*start*/ 0x1DF1F, WordBreakProperty.Other], + [/*start*/ 0x1DF25, WordBreakProperty.ALetter], + [/*start*/ 0x1DF2B, WordBreakProperty.Other], [/*start*/ 0x1E000, WordBreakProperty.Extend], [/*start*/ 0x1E007, WordBreakProperty.Other], [/*start*/ 0x1E008, WordBreakProperty.Extend], @@ -1697,6 +1788,10 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1E025, WordBreakProperty.Other], [/*start*/ 0x1E026, WordBreakProperty.Extend], [/*start*/ 0x1E02B, WordBreakProperty.Other], + [/*start*/ 0x1E030, WordBreakProperty.ALetter], + [/*start*/ 0x1E06E, WordBreakProperty.Other], + [/*start*/ 0x1E08F, WordBreakProperty.Extend], + [/*start*/ 0x1E090, WordBreakProperty.Other], [/*start*/ 0x1E100, WordBreakProperty.ALetter], [/*start*/ 0x1E12D, WordBreakProperty.Other], [/*start*/ 0x1E130, WordBreakProperty.Extend], @@ -1706,10 +1801,25 @@ export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ [/*start*/ 0x1E14A, WordBreakProperty.Other], [/*start*/ 0x1E14E, WordBreakProperty.ALetter], [/*start*/ 0x1E14F, WordBreakProperty.Other], + [/*start*/ 0x1E290, WordBreakProperty.ALetter], + [/*start*/ 0x1E2AE, WordBreakProperty.Extend], + [/*start*/ 0x1E2AF, WordBreakProperty.Other], [/*start*/ 0x1E2C0, WordBreakProperty.ALetter], [/*start*/ 0x1E2EC, WordBreakProperty.Extend], [/*start*/ 0x1E2F0, WordBreakProperty.Numeric], [/*start*/ 0x1E2FA, WordBreakProperty.Other], + [/*start*/ 0x1E4D0, WordBreakProperty.ALetter], + [/*start*/ 0x1E4EC, WordBreakProperty.Extend], + [/*start*/ 0x1E4F0, WordBreakProperty.Numeric], + [/*start*/ 0x1E4FA, WordBreakProperty.Other], + [/*start*/ 0x1E7E0, WordBreakProperty.ALetter], + [/*start*/ 0x1E7E7, WordBreakProperty.Other], + [/*start*/ 0x1E7E8, WordBreakProperty.ALetter], + [/*start*/ 0x1E7EC, WordBreakProperty.Other], + [/*start*/ 0x1E7ED, WordBreakProperty.ALetter], + [/*start*/ 0x1E7EF, WordBreakProperty.Other], + [/*start*/ 0x1E7F0, WordBreakProperty.ALetter], + [/*start*/ 0x1E7FF, WordBreakProperty.Other], [/*start*/ 0x1E800, WordBreakProperty.ALetter], [/*start*/ 0x1E8C5, WordBreakProperty.Other], [/*start*/ 0x1E8D0, WordBreakProperty.Extend], From c2da9cdbf641dc5f899211bd418696c642a821f0 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 08:38:21 +0700 Subject: [PATCH 029/262] change(common/models): data table compilation as configure, not build --- common/models/wordbreakers/build.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index 8dfa0f7cb6d..41c45f533ac 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -26,10 +26,14 @@ builder_describe_outputs \ builder_parse "$@" -function do_build() { +function do_configure() { + verify_npm_setup + tsc -b src/data-compiler/tsconfig.json node ./build/obj/data-compiler/index.js +} +function do_build() { tsc -b ./tsconfig.json # Declaration bundling. @@ -44,7 +48,7 @@ function do_test() { fi } -builder_run_action configure verify_npm_setup +builder_run_action configure do_configure builder_run_action clean rm -rf build/ builder_run_action build do_build builder_run_action test do_test \ No newline at end of file From 1ff33a329a6a8957b7f97a95591586dd11793c45 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 08:39:24 +0700 Subject: [PATCH 030/262] change(common/models): set unicode-data dep for configure step --- common/models/wordbreakers/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index 41c45f533ac..e6b503be17b 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -13,7 +13,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe "Builds the predictive-text wordbreaker implementation module" \ - "@/resources/standards-data/unicode-character-database" \ + "@/resources/standards-data/unicode-character-database configure" \ "clean" \ "configure" \ "build" \ From 5045df13dbf5b14b699e9cf7898984f5d05cc2ae Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 09:37:07 +0700 Subject: [PATCH 031/262] change(common/models): improve encoding format --- .../wordbreakers/src/data-compiler/index.ts | 55 +++++++++--------- .../wordbreakers/src/main/default/data.ts | Bin 90011 -> 9188 bytes .../wordbreakers/src/main/default/index.ts | 39 ++----------- 3 files changed, 33 insertions(+), 61 deletions(-) diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts index 71ac2147686..e4b5d72357c 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -2,8 +2,6 @@ // Original version found at: https://github.com/eddieantonio/unicode-default-word-boundary/blob/master/libexec/compile-word-break.js -// TODO: Adapt to produce two string-encoded arrays - one for BMP chars, one for non-BMP chars. - import fs from 'fs'; import path from 'path'; @@ -115,11 +113,13 @@ for(let range of ranges) { // already sorted end: undefined }); - nonBmpRanges.push({ - start: 0x10000, - property: finalBmpRange.property, - end: undefined - }); + if(range.start != 0x10000) { + nonBmpRanges.push({ + start: 0x10000, + property: finalBmpRange.property, + end: undefined + }); + } } nonBmpRanges.push(range); @@ -131,6 +131,14 @@ for(let range of ranges) { // already sorted // Save the output in the gen/ directory. let stream = fs.createWriteStream(generatedFilename); +function escape(codedChar: string) { + if(codedChar == '`' || codedChar == '\\') { + return '\\' + codedChar; + } else { + return codedChar; + } +} + // // Former entry in the original version by Eddie that was never included in our repo: // export const extendedPictographic = ${extendedPictographicRegExp}; @@ -164,37 +172,30 @@ ${ /* Enumerate the plain-text names for ease of lookup at runtime */ } ]; -/** - * Constants for indexing values in WORD_BREAK_PROPERTY. - */ -export const enum I { - Start = 0, - Value = 1 -} - export const WORD_BREAK_PROPERTY_BMP: string = \`${ // To consider: emit `\uxxxx` codes instead of the raw char? bmpRanges.map(({start, property}) => { - let codedStart = String.fromCodePoint(start); - if(codedStart == '`') { - // Prevents accidental unescaped use of the string start/end char. - // The backslash gets removed on file-load. - codedStart = '\\`'; - } - const codedProp = String.fromCharCode(categoryMap.get(property)); + let codedStart = escape(String.fromCodePoint(start)); + + // Offset the encoded property value to lie within a friendlier range, + // with characters that render naturally within code editors. + const codedProp = escape(String.fromCharCode(categoryMap.get(property) + 0x20)); return `${codedStart}${codedProp}`; }).join('') -}\` +}\`; export const WORD_BREAK_PROPERTY_NON_BMP: string = \`${ // To consider: emit `\uxxxx` codes instead of the raw char? nonBmpRanges.map(({start, property}) => { - const codedStart = String.fromCodePoint(start); - const codedProp = String.fromCharCode(categoryMap.get(property)); + const codedStart = escape(String.fromCodePoint(start)); + + // Offset the encoded property value to lie within a friendlier range, + // with characters that render naturally within code editors. + const codedProp = escape(String.fromCharCode(categoryMap.get(property) + 0x20)); return `${codedStart}${codedProp}`; }).join('') -} -\``); +}\`; +`); /** * Reads a Unicode character property file. diff --git a/common/models/wordbreakers/src/main/default/data.ts b/common/models/wordbreakers/src/main/default/data.ts index e1cd8aeb88c6d059e7c0f5e4fa04c9ea76e19404..8713fad43c0a0abfae8ca05a9d42141aa230afb2 100644 GIT binary patch literal 9188 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 literal 90011 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 diff --git a/common/models/wordbreakers/src/main/default/index.ts b/common/models/wordbreakers/src/main/default/index.ts index ef50ab4b31c..e606a3fbfb9 100644 --- a/common/models/wordbreakers/src/main/default/index.ts +++ b/common/models/wordbreakers/src/main/default/index.ts @@ -1,4 +1,6 @@ -import { WordBreakProperty, WORD_BREAK_PROPERTY, I, propertyMap } from "./data.js"; +import { WordBreakProperty, propertyMap } from "./data.js"; + +import { searchForProperty } from "./searchForProperty.js"; /** * A set of options used to customize and extend the behavior of the default @@ -566,7 +568,7 @@ function property(character: string, options?: DefaultWordBreakerOptions): WordB // TODO: remove dependence on character.codepointAt()? let codepoint = character.codePointAt(0) as number; - return searchForProperty(codepoint, 0, WORD_BREAK_PROPERTY.length - 1); + return searchForProperty(codepoint); } function propertyVal(propName: string, options?: DefaultWordBreakerOptions) { @@ -574,35 +576,4 @@ function propertyVal(propName: string, options?: DefaultWordBreakerOptions) { const customIndex = options?.customProperties?.findIndex(matcher) ?? -1; return customIndex != -1 ? -customIndex - 1 : propertyMap.findIndex(matcher); -} - -/** - * Binary search for the word break property of a given CODE POINT. - * - * The auto-generated data.ts master array defines a **character range** - * lookup table. If a character's codepoint is equal to or greater than - * the I.Start value for an entry and exclusively less than the next entry, - * it falls in the first entry's range bucket and is classified accordingly - * by this method. - */ -function searchForProperty(codePoint: number, left: number, right: number): WordBreakProperty { - // All items that are not found in the array are assigned the 'Other' property. - if (right < left) { - return WordBreakProperty.Other; - } - - let midpoint = left + ~~((right - left) / 2); - let candidate = WORD_BREAK_PROPERTY[midpoint]; - - let nextRange = WORD_BREAK_PROPERTY[midpoint + 1]; - let startOfNextRange = nextRange ? nextRange[I.Start] : Infinity; - - if (codePoint < candidate[I.Start]) { - return searchForProperty(codePoint, left, midpoint - 1); - } else if (codePoint >= startOfNextRange) { - return searchForProperty(codePoint, midpoint + 1, right); - } - - // We found it! - return candidate[I.Value]; -} +} \ No newline at end of file From fdecc602d336069ba809bdf7143c7804bbafaaff Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 09:37:22 +0700 Subject: [PATCH 032/262] feat(common/models): connect encoded lookup table --- .../src/main/default/searchForProperty.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/common/models/wordbreakers/src/main/default/searchForProperty.ts b/common/models/wordbreakers/src/main/default/searchForProperty.ts index 45a1641bd9d..9fa8f965961 100644 --- a/common/models/wordbreakers/src/main/default/searchForProperty.ts +++ b/common/models/wordbreakers/src/main/default/searchForProperty.ts @@ -1,20 +1,19 @@ -import { WordBreakProperty } from "./data.js"; +import { WordBreakProperty, WORD_BREAK_PROPERTY_BMP, WORD_BREAK_PROPERTY_NON_BMP } from "./data.js"; export function searchForProperty(codePoint: number): WordBreakProperty { const bucketSize = codePoint <= 0xFFFF ? 2 : 3; // SMP chars take a bit more space to encode. - // TODO: encode the strings & import them here. - const encodedArray = bucketSize == 2 ? "" /* BMP string */ : "" /* non-BMP string */; + const encodedArray = bucketSize == 2 ? WORD_BREAK_PROPERTY_BMP : WORD_BREAK_PROPERTY_NON_BMP; - return _searchForProperty(encodedArray, codePoint, bucketSize, 0, encodedArray.length / bucketSize - 1); + return _searchForProperty(encodedArray, codePoint, bucketSize, 0, encodedArray.length / bucketSize - 1) - 0x20; } /** * Binary search for the word break property of a given CODE POINT. * - * The auto-generated data.ts master array defines a **character range** - * lookup table. If a character's codepoint is equal to or greater than + * The auto-generated data.ts master strings encode **character range** + * lookup tables. If a character's codepoint is equal to or greater than * the start-of-range value for an entry and exclusively less than the next * entry's start-of-range, it falls within the first entry's range bucket * and is classified accordingly by this method. From b145adfaf8cccfb4923c14dc1e534929f0a93b88 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 09:50:45 +0700 Subject: [PATCH 033/262] fix(web): fixes value for end of BMP range --- .../wordbreakers/src/data-compiler/index.ts | 2 +- .../wordbreakers/src/main/default/data.ts | Bin 9188 -> 9188 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/src/data-compiler/index.ts index e4b5d72357c..1ff2f6dccf6 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/src/data-compiler/index.ts @@ -109,7 +109,7 @@ for(let range of ranges) { // already sorted const finalBmpRange = bmpRanges[bmpRanges.length - 1]; bmpRanges.push({ start: 0xFFFF, - property: range.property, + property: finalBmpRange.property, end: undefined }); diff --git a/common/models/wordbreakers/src/main/default/data.ts b/common/models/wordbreakers/src/main/default/data.ts index 8713fad43c0a0abfae8ca05a9d42141aa230afb2..5a4062be830d67e97df5794c78f10c50e190cf6a 100644 GIT binary patch delta 14 VcmaFj{=|L5EfGeA&9_B3`2jT-1<(Kh delta 14 VcmaFj{=|L5EfGfT&9_B3`2jUq1=|1s From 02f721dc24247b9e8b2b9c9652e4ec29ba8e353b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 09:51:49 +0700 Subject: [PATCH 034/262] feat(common/models): add property-lookup unit test set --- .../wordbreakers/test/test-search-property.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 common/models/wordbreakers/test/test-search-property.js diff --git a/common/models/wordbreakers/test/test-search-property.js b/common/models/wordbreakers/test/test-search-property.js new file mode 100644 index 00000000000..cdf80c45f45 --- /dev/null +++ b/common/models/wordbreakers/test/test-search-property.js @@ -0,0 +1,33 @@ +/** + * Smoke-test the default + */ + +import { assert } from 'chai'; +import { searchForProperty } from '../build/obj/default/searchForProperty.js'; +import { propertyMap } from '../build/obj/default/data.js'; + +describe('searchForProperty', () => { + it('correctly finds character classes for standard ASCII characters', () => { + assert.equal(searchForProperty('a'.codePointAt(0)), propertyMap.indexOf('ALetter')); + assert.equal(searchForProperty('Z'.codePointAt(0)), propertyMap.indexOf('ALetter')); + + assert.equal(searchForProperty("'".codePointAt(0)), propertyMap.indexOf('Single_Quote')); + assert.equal(searchForProperty('"'.codePointAt(0)), propertyMap.indexOf('Double_Quote')); + assert.equal(searchForProperty(','.codePointAt(0)), propertyMap.indexOf('MidNum')); + assert.equal(searchForProperty('.'.codePointAt(0)), propertyMap.indexOf('MidNumLet')); + assert.equal(searchForProperty('-'.codePointAt(0)), propertyMap.indexOf('Other')); + }); + + it('correctly finds character classes for specialized BMP characters', () => { + assert.equal(searchForProperty(0x05D0), propertyMap.indexOf('Hebrew_Letter')); + assert.equal(searchForProperty(0x3031), propertyMap.indexOf('Katakana')); + assert.equal(searchForProperty(0xFFFE), propertyMap.indexOf('Other')); + assert.equal(searchForProperty(0xFFFF), propertyMap.indexOf('Other')); + }); + + it('correctly finds character classes for non-BMP characters', () => { + assert.equal(searchForProperty(0x0001F1E6), propertyMap.indexOf('Regional_Indicator')); + assert.equal(searchForProperty(0x00013430), propertyMap.indexOf('Format')); + assert.equal(searchForProperty(0x00010000), propertyMap.indexOf('ALetter')); + }); +}); \ No newline at end of file From 6f1d4374e9fb315b6fd7a661b2029fa6ab2e583d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Aug 2024 10:05:53 +0700 Subject: [PATCH 035/262] feat(web): enable utf8 charset encoding for the build artifacts --- common/web/es-bundling/src/common-bundle.mts | 25 ++++++++++++++++---- common/web/lm-worker/build.sh | 2 ++ web/src/app/browser/build.sh | 3 +++ web/src/app/webview/build.sh | 2 ++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/common/web/es-bundling/src/common-bundle.mts b/common/web/es-bundling/src/common-bundle.mts index fb3dfb95a44..856d08ec053 100644 --- a/common/web/es-bundling/src/common-bundle.mts +++ b/common/web/es-bundling/src/common-bundle.mts @@ -3,6 +3,7 @@ import esbuild from 'esbuild'; import { esmConfiguration, forES6, iifeConfiguration } from './configuration.mjs'; import { prepareTslibTreeshaking } from './tslibTreeshaking.mjs'; +let CHARSET = 'ascii'; let FORMAT = 'iife'; let MINIFY = false; @@ -28,6 +29,8 @@ Parameters: Options: --help Shows this script's documentation + --charset= Sets the charset type for esbuild to emit. Defaults to 'ascii' + but may also be 'utf8'. --format= Sets the format type to use for the generated bundle. Should be 'iife' or 'esm'. @@ -49,15 +52,28 @@ if(process.argv.length > 2) { case '--help': doHelp(); break; + case '--charset': + let charsetOption = process.argv[++i]; + switch(charsetOption) { + case 'ascii': + case 'utf8': + CHARSET = charsetOption; + break; + default: + console.error(`Invalid bundling format specified: ${charsetOption}. Must be 'ascii' or 'utf8'.`); + doHelp(1); + break; + } + break; case '--format': // bc TS uses this exact flag. esbuild... uses sourcemap (in the JS config) - let input = process.argv[++i]; - switch(input) { + let formatOption = process.argv[++i]; + switch(formatOption) { case 'iife': case 'esm': - FORMAT = input; + FORMAT = formatOption; break; default: - console.error(`Invalid bundling format specified: ${input}. Must be 'iife' or 'esm'.`); + console.error(`Invalid bundling format specified: ${formatOption}. Must be 'iife' or 'esm'.`); doHelp(1); break; } @@ -128,6 +144,7 @@ const baseConfig = FORMAT == 'iife' ? iifeConfiguration : esmConfiguration; const config: esbuild.BuildOptions = { ...jsVersionTarget == 'es6' ? forES6(baseConfig) : baseConfig, entryPoints: [sourceFile], + charset: CHARSET as 'ascii' | 'utf8', outfile: destFile, minify: MINIFY, metafile: !!profilePath, diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 22ffa9208c5..cfad5b40718 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -64,6 +64,7 @@ function do_build() { # The ES6 target needs no polyfills - we go straight to the wrapped version. $bundle_cmd src/main/worker-main.ts \ --out $INTERMEDIATE/worker-main.js \ + --charset "utf8" \ --target "es6" \ --sourceRoot '@keymanapp/keyman/common/web/lm-worker/src/main' @@ -75,6 +76,7 @@ function do_build() { $bundle_cmd src/main/worker-main.ts \ --out $INTERMEDIATE/worker-main.min.js \ --minify \ + --charset "utf8" \ --profile build/filesize-profile.log \ --target "es6" \ --sourceRoot '@keymanapp/keyman/common/web/lm-worker/src/main' diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index 049bc056b5e..85c982ec792 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -55,11 +55,13 @@ compile_and_copy() { $BUNDLE_CMD "${SRC_ROOT}/debug-main.js" \ --out "${BUILD_ROOT}/debug/keymanweb.js" \ + --charset "utf8" \ --sourceRoot "@keymanapp/keyman/web/build/app/browser/debug" \ --target "es6" $BUNDLE_CMD "${SRC_ROOT}/release-main.js" \ --out "${BUILD_ROOT}/release/keymanweb.js" \ + --charset "utf8" \ --profile "${BUILD_ROOT}/filesize-profile.log" \ --sourceRoot "@keymanapp/keyman/web/build/app/browser/release" \ --minify \ @@ -67,6 +69,7 @@ compile_and_copy() { $BUNDLE_CMD "${BUILD_ROOT}/obj/test-index.js" \ --out "${BUILD_ROOT}/lib/index.mjs" \ + --charset "utf8" \ --sourceRoot "@keymanapp/keyman/web/build/app/browser/lib" \ --format esm diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh index e22e1b470ef..371c9325f28 100755 --- a/web/src/app/webview/build.sh +++ b/web/src/app/webview/build.sh @@ -45,11 +45,13 @@ compile_and_copy() { $BUNDLE_CMD "${SRC_ROOT}/debug-main.js" \ --out "${BUILD_ROOT}/debug/keymanweb-webview.js" \ + --charset "utf8" \ --sourceRoot "@keymanapp/keyman/web/build/app/webview/debug" \ --target "es6" $BUNDLE_CMD "${SRC_ROOT}/release-main.js" \ --out "${BUILD_ROOT}/release/keymanweb-webview.js" \ + --charset "utf8" \ --profile "${BUILD_ROOT}/filesize-profile.log" \ --sourceRoot "@keymanapp/keyman/web/build/app/webview/release" \ --minify \ From 88c538160a4de30825943a959fdf2ac9d0a6b5bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 05:13:47 +0000 Subject: [PATCH 036/262] chore(deps-dev): bump @75lb/deep-merge from 1.1.1 to 1.1.2 Bumps [@75lb/deep-merge](https://github.com/75lb/deep-merge) from 1.1.1 to 1.1.2. - [Commits](https://github.com/75lb/deep-merge/compare/v1.1.1...v1.1.2) --- updated-dependencies: - dependency-name: "@75lb/deep-merge" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26e925843d9..8f739de9e19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2071,12 +2071,12 @@ } }, "node_modules/@75lb/deep-merge": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@75lb/deep-merge/-/deep-merge-1.1.1.tgz", - "integrity": "sha512-xvgv6pkMGBA6GwdyJbNAnDmfAIR/DfWhrj9jgWh3TY7gRm3KO46x/GPjRg6wJ0nOepwqrNxFfojebh0Df4h4Tw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@75lb/deep-merge/-/deep-merge-1.1.2.tgz", + "integrity": "sha512-08K9ou5VNbheZFxM5tDWoqjA3ImC50DiuuJ2tj1yEPRfkp8lLLg6XAaJ4On+a0yAXor/8ay5gHnAIshRM44Kpw==", "dev": true, "dependencies": { - "lodash.assignwith": "^4.2.0", + "lodash": "^4.17.21", "typical": "^7.1.1" }, "engines": { @@ -10617,12 +10617,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.assignwith": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", - "integrity": "sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g==", - "dev": true - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", From 4c87a16b276fb2ba6b17895c8243766dba459ecf Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 5 Aug 2024 13:05:40 +0700 Subject: [PATCH 037/262] feat(android/engine): Add special handling for ENTER key --- .../com/keyman/android/SystemKeyboard.java | 3 ++ .../keyman/engine/KMKeyboardJSHandler.java | 29 ++++++++++- .../java/com/keyman/engine/KMManager.java | 49 ++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index c6885b2b564..9bc5a4a8558 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -147,6 +147,9 @@ public void onStartInput(EditorInfo attribute, boolean restarting) { KMManager.onStartInput(attribute, restarting); KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); + // Determine special handling for ENTER key + KMManager.setEnterMode(attribute.imeOptions); + // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, // making it an important time to detect orientation changes. Context appContext = getApplicationContext(); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java index 926ef094f74..426cfb3e52e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java @@ -20,6 +20,7 @@ import static android.content.Context.VIBRATOR_SERVICE; +import com.keyman.engine.KMManager.EnterModeType; import com.keyman.engine.KMManager.KeyboardType; import com.keyman.engine.data.Keyboard; import com.keyman.engine.util.CharSequenceUtil; @@ -153,7 +154,33 @@ public void run() { } if (s.length() > 0 && s.charAt(0) == '\n') { - keyDownUp(KeyEvent.KEYCODE_ENTER, 0); + if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + // Special handling of ENTER key + switch (KMManager.enterMode) { + // Go action + case GO : + ic.performEditorAction(EditorInfo.IME_ACTION_GO); + break; + + // Search action + case SEARCH : + ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH); + break; + + // Messaging apps + case NEWLINE : + // Send newline without advancing cursor + ic.commitText("\n", 0); + break; + + // Default ENTER action + default: + keyDownUp(KeyEvent.KEYCODE_ENTER, 0); + } + } else { + // In-app keyboard uses default ENTER action + keyDownUp(KeyEvent.KEYCODE_ENTER, 0); + } ic.endBatchEdit(); return; } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index aa1c43626a7..adef3d3fe51 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -25,7 +25,6 @@ import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Typeface; import android.inputmethodservice.InputMethodService; @@ -180,6 +179,15 @@ public String toString() { } } + // Enum for how the System Keyboard ENTER key is handled for the EditorInfo action + // Reference: https://developer.android.com/reference/android/view/inputmethod/EditorInfo#summary + public enum EnterModeType { + GO, // Go action + SEARCH, // Search action + NEWLINE, // Send newline character + DEFAULT, // Default ENTER action + } + protected static InputMethodService IMService; private static boolean debugMode = false; @@ -218,6 +226,9 @@ public String toString() { // regardless what the Settings preference is. private static boolean mayPredictOverride = false; + // Determine how system keyboard handles ENTER key + public static EnterModeType enterMode = EnterModeType.DEFAULT; + // Boolean for whether a keyboard can send embedded KMW crash reports to Sentry // When maySendCrashReport is false, KMW will still attempt to send crash reports, but it // will be blocked. @@ -1251,6 +1262,42 @@ public boolean accept(File pathname) { } } + /** + * Sets enterMode which specifies how the System keyboard ENTER key is handled + * + * @param imeOptions EditorInfo.imeOptions + */ + public static void setEnterMode(int imeOptions) { + int imeActions = imeOptions&EditorInfo.IME_MASK_ACTION; + EnterModeType value = EnterModeType.DEFAULT; + + switch (imeActions) { + case EditorInfo.IME_ACTION_GO: + value = EnterModeType.GO; + break; + + case EditorInfo.IME_ACTION_SEARCH: + value = EnterModeType.SEARCH; + break; + + case EditorInfo.IME_ACTION_DONE: + value = EnterModeType.NEWLINE; + break; + + default: + value = EnterModeType.DEFAULT; + } + enterMode = value; + } + + /** + * Get the value of enterMode + * @return EnterModeType + */ + public static EnterModeType getEnterMode() { + return enterMode; + } + /** * Sets mayPredictOverride true if the InputType field is a hidden password text field * (either TYPE_TEXT_VARIATION_PASSWORD or TYPE_TEXT_VARIATION_WEB_PASSWORD From ea2b062c5273684a78f46dce5da3c34ecbed1648 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 7 Aug 2024 15:21:40 +0700 Subject: [PATCH 038/262] fix(android/engine): Separate DONE from NEWLINE action --- .../java/com/keyman/android/SystemKeyboard.java | 6 +++--- .../com/keyman/engine/KMKeyboardJSHandler.java | 5 +++++ .../src/main/java/com/keyman/engine/KMManager.java | 14 +++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 9bc5a4a8558..ab9b4fcb449 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -147,9 +147,6 @@ public void onStartInput(EditorInfo attribute, boolean restarting) { KMManager.onStartInput(attribute, restarting); KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); - // Determine special handling for ENTER key - KMManager.setEnterMode(attribute.imeOptions); - // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, // making it an important time to detect orientation changes. Context appContext = getApplicationContext(); @@ -178,6 +175,9 @@ public void onStartInput(EditorInfo attribute, boolean restarting) { } } + // Determine special handling for ENTER key + KMManager.setEnterMode(attribute.imeOptions, inputType); + InputConnection ic = getCurrentInputConnection(); if (ic != null) { ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java index 426cfb3e52e..66af2ebe07d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java @@ -167,6 +167,11 @@ public void run() { ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH); break; + // Done action + case DONE : + ic.performEditorAction(EditorInfo.IME_ACTION_DONE); + break; + // Messaging apps case NEWLINE : // Send newline without advancing cursor diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index adef3d3fe51..adf9d30737d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -185,6 +185,7 @@ public enum EnterModeType { GO, // Go action SEARCH, // Search action NEWLINE, // Send newline character + DONE, // Done action DEFAULT, // Default ENTER action } @@ -1266,9 +1267,15 @@ public boolean accept(File pathname) { * Sets enterMode which specifies how the System keyboard ENTER key is handled * * @param imeOptions EditorInfo.imeOptions + * @param inputType InputType */ - public static void setEnterMode(int imeOptions) { - int imeActions = imeOptions&EditorInfo.IME_MASK_ACTION; + public static void setEnterMode(int imeOptions, int inputType) { + if ((inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0) { + enterMode = EnterModeType.NEWLINE; + return; + } + + int imeActions = imeOptions & EditorInfo.IME_MASK_ACTION; EnterModeType value = EnterModeType.DEFAULT; switch (imeActions) { @@ -1281,12 +1288,13 @@ public static void setEnterMode(int imeOptions) { break; case EditorInfo.IME_ACTION_DONE: - value = EnterModeType.NEWLINE; + value = EnterModeType.DONE; break; default: value = EnterModeType.DEFAULT; } + enterMode = value; } From dda43aa3994623da489ca7787aaf7b9539d468ca Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Aug 2024 12:51:30 +0700 Subject: [PATCH 039/262] change(common/models): moves wordbreaker data-gen from src/ to tools/, removes old data.ts --- common/models/wordbreakers/.gitignore | 3 +- common/models/wordbreakers/build.sh | 7 +- .../wordbreakers/src/main/default/data.ts | 1917 ----------------- .../wordbreakers/src/main/default/index.ts | 2 +- .../src/main/default/searchForProperty.ts | 2 +- .../wordbreakers/src/main/test-index.ts | 2 +- .../{src => tools}/data-compiler/index.ts | 2 +- .../data-compiler/tsconfig.json | 0 8 files changed, 11 insertions(+), 1924 deletions(-) delete mode 100644 common/models/wordbreakers/src/main/default/data.ts rename common/models/wordbreakers/{src => tools}/data-compiler/index.ts (99%) rename common/models/wordbreakers/{src => tools}/data-compiler/tsconfig.json (100%) diff --git a/common/models/wordbreakers/.gitignore b/common/models/wordbreakers/.gitignore index d16386367f7..c51c25f2fd7 100644 --- a/common/models/wordbreakers/.gitignore +++ b/common/models/wordbreakers/.gitignore @@ -1 +1,2 @@ -build/ \ No newline at end of file +build/ +**/**.inc.ts \ No newline at end of file diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index e6b503be17b..38123ee01c1 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -21,7 +21,7 @@ builder_describe "Builds the predictive-text wordbreaker implementation module" "--ci" builder_describe_outputs \ - configure /node_modules \ + configure src/main/default/data.inc.ts \ build build/obj/index.js builder_parse "$@" @@ -29,7 +29,10 @@ builder_parse "$@" function do_configure() { verify_npm_setup - tsc -b src/data-compiler/tsconfig.json + # This is a script used to build the data.inc.ts file needed by the + # default wordbreaker. We rarely update the backing data, but it + # is needed _before_ the `build` action's compilation step. + tsc -b tools/data-compiler/tsconfig.json node ./build/obj/data-compiler/index.js } diff --git a/common/models/wordbreakers/src/main/default/data.ts b/common/models/wordbreakers/src/main/default/data.ts deleted file mode 100644 index e1cd8aeb88c..00000000000 --- a/common/models/wordbreakers/src/main/default/data.ts +++ /dev/null @@ -1,1917 +0,0 @@ -// Automatically generated file. DO NOT MODIFY. -// The generator script is defined at /common/models/wordbreakers/src/data-compiler/index.ts. - -/** - * Valid values for a word break property. - * - * Is optimized away at compile-time; use `propertyMap` to find the mapped - * value at runtime for a property name if needed. - */ -export const enum WordBreakProperty { - Other, - LF, - Newline, - CR, - WSegSpace, - Double_Quote, - Single_Quote, - MidNum, - MidNumLet, - Numeric, - MidLetter, - ALetter, - ExtendNumLet, - Format, - Extend, - Hebrew_Letter, - ZWJ, - Katakana, - Regional_Indicator, - sot, - eot -}; - -/** - * Contains property names per associated index, as this is compiled away - * by TypeScript for `const enum` cases like `WordBreakProperty`. - */ -export const propertyMap = [ - "Other", - "LF", - "Newline", - "CR", - "WSegSpace", - "Double_Quote", - "Single_Quote", - "MidNum", - "MidNumLet", - "Numeric", - "MidLetter", - "ALetter", - "ExtendNumLet", - "Format", - "Extend", - "Hebrew_Letter", - "ZWJ", - "Katakana", - "Regional_Indicator", - "sot", - "eot" -]; - -/** - * Constants for indexing values in WORD_BREAK_PROPERTY. - */ -export const enum I { - Start = 0, - Value = 1 -} - -/** - * Defines a mapping of all characters to their assigned word-breaking - * property type. - * - * There are implicit buckets starting at the char with specified code `number` - * of an entry up to, but not including, the value in the next entry. All - * entries in each bucket share the same property value. - * - * Consider the following two consecutive buckets: - * - [0x0041, WordBreakProperty.ALetter] - * - [0x005B, WordBreakProperty.Other] - * - * For this example, all characters from 0x0041 to 0x005B (that is, 'A'-'Z') - * have the wordbreaking property `ALetter`. - */ -export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ - [/*start*/ 0x0, WordBreakProperty.Other], - [/*start*/ 0xA, WordBreakProperty.LF], - [/*start*/ 0xB, WordBreakProperty.Newline], - [/*start*/ 0xD, WordBreakProperty.CR], - [/*start*/ 0xE, WordBreakProperty.Other], - [/*start*/ 0x20, WordBreakProperty.WSegSpace], - [/*start*/ 0x21, WordBreakProperty.Other], - [/*start*/ 0x22, WordBreakProperty.Double_Quote], - [/*start*/ 0x23, WordBreakProperty.Other], - [/*start*/ 0x27, WordBreakProperty.Single_Quote], - [/*start*/ 0x28, WordBreakProperty.Other], - [/*start*/ 0x2C, WordBreakProperty.MidNum], - [/*start*/ 0x2D, WordBreakProperty.Other], - [/*start*/ 0x2E, WordBreakProperty.MidNumLet], - [/*start*/ 0x2F, WordBreakProperty.Other], - [/*start*/ 0x30, WordBreakProperty.Numeric], - [/*start*/ 0x3A, WordBreakProperty.MidLetter], - [/*start*/ 0x3B, WordBreakProperty.MidNum], - [/*start*/ 0x3C, WordBreakProperty.Other], - [/*start*/ 0x41, WordBreakProperty.ALetter], - [/*start*/ 0x5B, WordBreakProperty.Other], - [/*start*/ 0x5F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x60, WordBreakProperty.Other], - [/*start*/ 0x61, WordBreakProperty.ALetter], - [/*start*/ 0x7B, WordBreakProperty.Other], - [/*start*/ 0x85, WordBreakProperty.Newline], - [/*start*/ 0x86, WordBreakProperty.Other], - [/*start*/ 0xAA, WordBreakProperty.ALetter], - [/*start*/ 0xAB, WordBreakProperty.Other], - [/*start*/ 0xAD, WordBreakProperty.Format], - [/*start*/ 0xAE, WordBreakProperty.Other], - [/*start*/ 0xB5, WordBreakProperty.ALetter], - [/*start*/ 0xB6, WordBreakProperty.Other], - [/*start*/ 0xB7, WordBreakProperty.MidLetter], - [/*start*/ 0xB8, WordBreakProperty.Other], - [/*start*/ 0xBA, WordBreakProperty.ALetter], - [/*start*/ 0xBB, WordBreakProperty.Other], - [/*start*/ 0xC0, WordBreakProperty.ALetter], - [/*start*/ 0xD7, WordBreakProperty.Other], - [/*start*/ 0xD8, WordBreakProperty.ALetter], - [/*start*/ 0xF7, WordBreakProperty.Other], - [/*start*/ 0xF8, WordBreakProperty.ALetter], - [/*start*/ 0x2D8, WordBreakProperty.Other], - [/*start*/ 0x2DE, WordBreakProperty.ALetter], - [/*start*/ 0x300, WordBreakProperty.Extend], - [/*start*/ 0x370, WordBreakProperty.ALetter], - [/*start*/ 0x375, WordBreakProperty.Other], - [/*start*/ 0x376, WordBreakProperty.ALetter], - [/*start*/ 0x378, WordBreakProperty.Other], - [/*start*/ 0x37A, WordBreakProperty.ALetter], - [/*start*/ 0x37E, WordBreakProperty.MidNum], - [/*start*/ 0x37F, WordBreakProperty.ALetter], - [/*start*/ 0x380, WordBreakProperty.Other], - [/*start*/ 0x386, WordBreakProperty.ALetter], - [/*start*/ 0x387, WordBreakProperty.MidLetter], - [/*start*/ 0x388, WordBreakProperty.ALetter], - [/*start*/ 0x38B, WordBreakProperty.Other], - [/*start*/ 0x38C, WordBreakProperty.ALetter], - [/*start*/ 0x38D, WordBreakProperty.Other], - [/*start*/ 0x38E, WordBreakProperty.ALetter], - [/*start*/ 0x3A2, WordBreakProperty.Other], - [/*start*/ 0x3A3, WordBreakProperty.ALetter], - [/*start*/ 0x3F6, WordBreakProperty.Other], - [/*start*/ 0x3F7, WordBreakProperty.ALetter], - [/*start*/ 0x482, WordBreakProperty.Other], - [/*start*/ 0x483, WordBreakProperty.Extend], - [/*start*/ 0x48A, WordBreakProperty.ALetter], - [/*start*/ 0x530, WordBreakProperty.Other], - [/*start*/ 0x531, WordBreakProperty.ALetter], - [/*start*/ 0x557, WordBreakProperty.Other], - [/*start*/ 0x559, WordBreakProperty.ALetter], - [/*start*/ 0x55D, WordBreakProperty.Other], - [/*start*/ 0x55E, WordBreakProperty.ALetter], - [/*start*/ 0x55F, WordBreakProperty.MidLetter], - [/*start*/ 0x560, WordBreakProperty.ALetter], - [/*start*/ 0x589, WordBreakProperty.MidNum], - [/*start*/ 0x58A, WordBreakProperty.ALetter], - [/*start*/ 0x58B, WordBreakProperty.Other], - [/*start*/ 0x591, WordBreakProperty.Extend], - [/*start*/ 0x5BE, WordBreakProperty.Other], - [/*start*/ 0x5BF, WordBreakProperty.Extend], - [/*start*/ 0x5C0, WordBreakProperty.Other], - [/*start*/ 0x5C1, WordBreakProperty.Extend], - [/*start*/ 0x5C3, WordBreakProperty.Other], - [/*start*/ 0x5C4, WordBreakProperty.Extend], - [/*start*/ 0x5C6, WordBreakProperty.Other], - [/*start*/ 0x5C7, WordBreakProperty.Extend], - [/*start*/ 0x5C8, WordBreakProperty.Other], - [/*start*/ 0x5D0, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0x5EB, WordBreakProperty.Other], - [/*start*/ 0x5EF, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0x5F3, WordBreakProperty.ALetter], - [/*start*/ 0x5F4, WordBreakProperty.MidLetter], - [/*start*/ 0x5F5, WordBreakProperty.Other], - [/*start*/ 0x600, WordBreakProperty.Numeric], - [/*start*/ 0x606, WordBreakProperty.Other], - [/*start*/ 0x60C, WordBreakProperty.MidNum], - [/*start*/ 0x60E, WordBreakProperty.Other], - [/*start*/ 0x610, WordBreakProperty.Extend], - [/*start*/ 0x61B, WordBreakProperty.Other], - [/*start*/ 0x61C, WordBreakProperty.Format], - [/*start*/ 0x61D, WordBreakProperty.Other], - [/*start*/ 0x620, WordBreakProperty.ALetter], - [/*start*/ 0x64B, WordBreakProperty.Extend], - [/*start*/ 0x660, WordBreakProperty.Numeric], - [/*start*/ 0x66A, WordBreakProperty.Other], - [/*start*/ 0x66B, WordBreakProperty.Numeric], - [/*start*/ 0x66C, WordBreakProperty.MidNum], - [/*start*/ 0x66D, WordBreakProperty.Other], - [/*start*/ 0x66E, WordBreakProperty.ALetter], - [/*start*/ 0x670, WordBreakProperty.Extend], - [/*start*/ 0x671, WordBreakProperty.ALetter], - [/*start*/ 0x6D4, WordBreakProperty.Other], - [/*start*/ 0x6D5, WordBreakProperty.ALetter], - [/*start*/ 0x6D6, WordBreakProperty.Extend], - [/*start*/ 0x6DD, WordBreakProperty.Numeric], - [/*start*/ 0x6DE, WordBreakProperty.Other], - [/*start*/ 0x6DF, WordBreakProperty.Extend], - [/*start*/ 0x6E5, WordBreakProperty.ALetter], - [/*start*/ 0x6E7, WordBreakProperty.Extend], - [/*start*/ 0x6E9, WordBreakProperty.Other], - [/*start*/ 0x6EA, WordBreakProperty.Extend], - [/*start*/ 0x6EE, WordBreakProperty.ALetter], - [/*start*/ 0x6F0, WordBreakProperty.Numeric], - [/*start*/ 0x6FA, WordBreakProperty.ALetter], - [/*start*/ 0x6FD, WordBreakProperty.Other], - [/*start*/ 0x6FF, WordBreakProperty.ALetter], - [/*start*/ 0x700, WordBreakProperty.Other], - [/*start*/ 0x70F, WordBreakProperty.ALetter], - [/*start*/ 0x711, WordBreakProperty.Extend], - [/*start*/ 0x712, WordBreakProperty.ALetter], - [/*start*/ 0x730, WordBreakProperty.Extend], - [/*start*/ 0x74B, WordBreakProperty.Other], - [/*start*/ 0x74D, WordBreakProperty.ALetter], - [/*start*/ 0x7A6, WordBreakProperty.Extend], - [/*start*/ 0x7B1, WordBreakProperty.ALetter], - [/*start*/ 0x7B2, WordBreakProperty.Other], - [/*start*/ 0x7C0, WordBreakProperty.Numeric], - [/*start*/ 0x7CA, WordBreakProperty.ALetter], - [/*start*/ 0x7EB, WordBreakProperty.Extend], - [/*start*/ 0x7F4, WordBreakProperty.ALetter], - [/*start*/ 0x7F6, WordBreakProperty.Other], - [/*start*/ 0x7F8, WordBreakProperty.MidNum], - [/*start*/ 0x7F9, WordBreakProperty.Other], - [/*start*/ 0x7FA, WordBreakProperty.ALetter], - [/*start*/ 0x7FB, WordBreakProperty.Other], - [/*start*/ 0x7FD, WordBreakProperty.Extend], - [/*start*/ 0x7FE, WordBreakProperty.Other], - [/*start*/ 0x800, WordBreakProperty.ALetter], - [/*start*/ 0x816, WordBreakProperty.Extend], - [/*start*/ 0x81A, WordBreakProperty.ALetter], - [/*start*/ 0x81B, WordBreakProperty.Extend], - [/*start*/ 0x824, WordBreakProperty.ALetter], - [/*start*/ 0x825, WordBreakProperty.Extend], - [/*start*/ 0x828, WordBreakProperty.ALetter], - [/*start*/ 0x829, WordBreakProperty.Extend], - [/*start*/ 0x82E, WordBreakProperty.Other], - [/*start*/ 0x840, WordBreakProperty.ALetter], - [/*start*/ 0x859, WordBreakProperty.Extend], - [/*start*/ 0x85C, WordBreakProperty.Other], - [/*start*/ 0x860, WordBreakProperty.ALetter], - [/*start*/ 0x86B, WordBreakProperty.Other], - [/*start*/ 0x870, WordBreakProperty.ALetter], - [/*start*/ 0x888, WordBreakProperty.Other], - [/*start*/ 0x889, WordBreakProperty.ALetter], - [/*start*/ 0x88F, WordBreakProperty.Other], - [/*start*/ 0x890, WordBreakProperty.Numeric], - [/*start*/ 0x892, WordBreakProperty.Other], - [/*start*/ 0x898, WordBreakProperty.Extend], - [/*start*/ 0x8A0, WordBreakProperty.ALetter], - [/*start*/ 0x8CA, WordBreakProperty.Extend], - [/*start*/ 0x8E2, WordBreakProperty.Numeric], - [/*start*/ 0x8E3, WordBreakProperty.Extend], - [/*start*/ 0x904, WordBreakProperty.ALetter], - [/*start*/ 0x93A, WordBreakProperty.Extend], - [/*start*/ 0x93D, WordBreakProperty.ALetter], - [/*start*/ 0x93E, WordBreakProperty.Extend], - [/*start*/ 0x950, WordBreakProperty.ALetter], - [/*start*/ 0x951, WordBreakProperty.Extend], - [/*start*/ 0x958, WordBreakProperty.ALetter], - [/*start*/ 0x962, WordBreakProperty.Extend], - [/*start*/ 0x964, WordBreakProperty.Other], - [/*start*/ 0x966, WordBreakProperty.Numeric], - [/*start*/ 0x970, WordBreakProperty.Other], - [/*start*/ 0x971, WordBreakProperty.ALetter], - [/*start*/ 0x981, WordBreakProperty.Extend], - [/*start*/ 0x984, WordBreakProperty.Other], - [/*start*/ 0x985, WordBreakProperty.ALetter], - [/*start*/ 0x98D, WordBreakProperty.Other], - [/*start*/ 0x98F, WordBreakProperty.ALetter], - [/*start*/ 0x991, WordBreakProperty.Other], - [/*start*/ 0x993, WordBreakProperty.ALetter], - [/*start*/ 0x9A9, WordBreakProperty.Other], - [/*start*/ 0x9AA, WordBreakProperty.ALetter], - [/*start*/ 0x9B1, WordBreakProperty.Other], - [/*start*/ 0x9B2, WordBreakProperty.ALetter], - [/*start*/ 0x9B3, WordBreakProperty.Other], - [/*start*/ 0x9B6, WordBreakProperty.ALetter], - [/*start*/ 0x9BA, WordBreakProperty.Other], - [/*start*/ 0x9BC, WordBreakProperty.Extend], - [/*start*/ 0x9BD, WordBreakProperty.ALetter], - [/*start*/ 0x9BE, WordBreakProperty.Extend], - [/*start*/ 0x9C5, WordBreakProperty.Other], - [/*start*/ 0x9C7, WordBreakProperty.Extend], - [/*start*/ 0x9C9, WordBreakProperty.Other], - [/*start*/ 0x9CB, WordBreakProperty.Extend], - [/*start*/ 0x9CE, WordBreakProperty.ALetter], - [/*start*/ 0x9CF, WordBreakProperty.Other], - [/*start*/ 0x9D7, WordBreakProperty.Extend], - [/*start*/ 0x9D8, WordBreakProperty.Other], - [/*start*/ 0x9DC, WordBreakProperty.ALetter], - [/*start*/ 0x9DE, WordBreakProperty.Other], - [/*start*/ 0x9DF, WordBreakProperty.ALetter], - [/*start*/ 0x9E2, WordBreakProperty.Extend], - [/*start*/ 0x9E4, WordBreakProperty.Other], - [/*start*/ 0x9E6, WordBreakProperty.Numeric], - [/*start*/ 0x9F0, WordBreakProperty.ALetter], - [/*start*/ 0x9F2, WordBreakProperty.Other], - [/*start*/ 0x9FC, WordBreakProperty.ALetter], - [/*start*/ 0x9FD, WordBreakProperty.Other], - [/*start*/ 0x9FE, WordBreakProperty.Extend], - [/*start*/ 0x9FF, WordBreakProperty.Other], - [/*start*/ 0xA01, WordBreakProperty.Extend], - [/*start*/ 0xA04, WordBreakProperty.Other], - [/*start*/ 0xA05, WordBreakProperty.ALetter], - [/*start*/ 0xA0B, WordBreakProperty.Other], - [/*start*/ 0xA0F, WordBreakProperty.ALetter], - [/*start*/ 0xA11, WordBreakProperty.Other], - [/*start*/ 0xA13, WordBreakProperty.ALetter], - [/*start*/ 0xA29, WordBreakProperty.Other], - [/*start*/ 0xA2A, WordBreakProperty.ALetter], - [/*start*/ 0xA31, WordBreakProperty.Other], - [/*start*/ 0xA32, WordBreakProperty.ALetter], - [/*start*/ 0xA34, WordBreakProperty.Other], - [/*start*/ 0xA35, WordBreakProperty.ALetter], - [/*start*/ 0xA37, WordBreakProperty.Other], - [/*start*/ 0xA38, WordBreakProperty.ALetter], - [/*start*/ 0xA3A, WordBreakProperty.Other], - [/*start*/ 0xA3C, WordBreakProperty.Extend], - [/*start*/ 0xA3D, WordBreakProperty.Other], - [/*start*/ 0xA3E, WordBreakProperty.Extend], - [/*start*/ 0xA43, WordBreakProperty.Other], - [/*start*/ 0xA47, WordBreakProperty.Extend], - [/*start*/ 0xA49, WordBreakProperty.Other], - [/*start*/ 0xA4B, WordBreakProperty.Extend], - [/*start*/ 0xA4E, WordBreakProperty.Other], - [/*start*/ 0xA51, WordBreakProperty.Extend], - [/*start*/ 0xA52, WordBreakProperty.Other], - [/*start*/ 0xA59, WordBreakProperty.ALetter], - [/*start*/ 0xA5D, WordBreakProperty.Other], - [/*start*/ 0xA5E, WordBreakProperty.ALetter], - [/*start*/ 0xA5F, WordBreakProperty.Other], - [/*start*/ 0xA66, WordBreakProperty.Numeric], - [/*start*/ 0xA70, WordBreakProperty.Extend], - [/*start*/ 0xA72, WordBreakProperty.ALetter], - [/*start*/ 0xA75, WordBreakProperty.Extend], - [/*start*/ 0xA76, WordBreakProperty.Other], - [/*start*/ 0xA81, WordBreakProperty.Extend], - [/*start*/ 0xA84, WordBreakProperty.Other], - [/*start*/ 0xA85, WordBreakProperty.ALetter], - [/*start*/ 0xA8E, WordBreakProperty.Other], - [/*start*/ 0xA8F, WordBreakProperty.ALetter], - [/*start*/ 0xA92, WordBreakProperty.Other], - [/*start*/ 0xA93, WordBreakProperty.ALetter], - [/*start*/ 0xAA9, WordBreakProperty.Other], - [/*start*/ 0xAAA, WordBreakProperty.ALetter], - [/*start*/ 0xAB1, WordBreakProperty.Other], - [/*start*/ 0xAB2, WordBreakProperty.ALetter], - [/*start*/ 0xAB4, WordBreakProperty.Other], - [/*start*/ 0xAB5, WordBreakProperty.ALetter], - [/*start*/ 0xABA, WordBreakProperty.Other], - [/*start*/ 0xABC, WordBreakProperty.Extend], - [/*start*/ 0xABD, WordBreakProperty.ALetter], - [/*start*/ 0xABE, WordBreakProperty.Extend], - [/*start*/ 0xAC6, WordBreakProperty.Other], - [/*start*/ 0xAC7, WordBreakProperty.Extend], - [/*start*/ 0xACA, WordBreakProperty.Other], - [/*start*/ 0xACB, WordBreakProperty.Extend], - [/*start*/ 0xACE, WordBreakProperty.Other], - [/*start*/ 0xAD0, WordBreakProperty.ALetter], - [/*start*/ 0xAD1, WordBreakProperty.Other], - [/*start*/ 0xAE0, WordBreakProperty.ALetter], - [/*start*/ 0xAE2, WordBreakProperty.Extend], - [/*start*/ 0xAE4, WordBreakProperty.Other], - [/*start*/ 0xAE6, WordBreakProperty.Numeric], - [/*start*/ 0xAF0, WordBreakProperty.Other], - [/*start*/ 0xAF9, WordBreakProperty.ALetter], - [/*start*/ 0xAFA, WordBreakProperty.Extend], - [/*start*/ 0xB00, WordBreakProperty.Other], - [/*start*/ 0xB01, WordBreakProperty.Extend], - [/*start*/ 0xB04, WordBreakProperty.Other], - [/*start*/ 0xB05, WordBreakProperty.ALetter], - [/*start*/ 0xB0D, WordBreakProperty.Other], - [/*start*/ 0xB0F, WordBreakProperty.ALetter], - [/*start*/ 0xB11, WordBreakProperty.Other], - [/*start*/ 0xB13, WordBreakProperty.ALetter], - [/*start*/ 0xB29, WordBreakProperty.Other], - [/*start*/ 0xB2A, WordBreakProperty.ALetter], - [/*start*/ 0xB31, WordBreakProperty.Other], - [/*start*/ 0xB32, WordBreakProperty.ALetter], - [/*start*/ 0xB34, WordBreakProperty.Other], - [/*start*/ 0xB35, WordBreakProperty.ALetter], - [/*start*/ 0xB3A, WordBreakProperty.Other], - [/*start*/ 0xB3C, WordBreakProperty.Extend], - [/*start*/ 0xB3D, WordBreakProperty.ALetter], - [/*start*/ 0xB3E, WordBreakProperty.Extend], - [/*start*/ 0xB45, WordBreakProperty.Other], - [/*start*/ 0xB47, WordBreakProperty.Extend], - [/*start*/ 0xB49, WordBreakProperty.Other], - [/*start*/ 0xB4B, WordBreakProperty.Extend], - [/*start*/ 0xB4E, WordBreakProperty.Other], - [/*start*/ 0xB55, WordBreakProperty.Extend], - [/*start*/ 0xB58, WordBreakProperty.Other], - [/*start*/ 0xB5C, WordBreakProperty.ALetter], - [/*start*/ 0xB5E, WordBreakProperty.Other], - [/*start*/ 0xB5F, WordBreakProperty.ALetter], - [/*start*/ 0xB62, WordBreakProperty.Extend], - [/*start*/ 0xB64, WordBreakProperty.Other], - [/*start*/ 0xB66, WordBreakProperty.Numeric], - [/*start*/ 0xB70, WordBreakProperty.Other], - [/*start*/ 0xB71, WordBreakProperty.ALetter], - [/*start*/ 0xB72, WordBreakProperty.Other], - [/*start*/ 0xB82, WordBreakProperty.Extend], - [/*start*/ 0xB83, WordBreakProperty.ALetter], - [/*start*/ 0xB84, WordBreakProperty.Other], - [/*start*/ 0xB85, WordBreakProperty.ALetter], - [/*start*/ 0xB8B, WordBreakProperty.Other], - [/*start*/ 0xB8E, WordBreakProperty.ALetter], - [/*start*/ 0xB91, WordBreakProperty.Other], - [/*start*/ 0xB92, WordBreakProperty.ALetter], - [/*start*/ 0xB96, WordBreakProperty.Other], - [/*start*/ 0xB99, WordBreakProperty.ALetter], - [/*start*/ 0xB9B, WordBreakProperty.Other], - [/*start*/ 0xB9C, WordBreakProperty.ALetter], - [/*start*/ 0xB9D, WordBreakProperty.Other], - [/*start*/ 0xB9E, WordBreakProperty.ALetter], - [/*start*/ 0xBA0, WordBreakProperty.Other], - [/*start*/ 0xBA3, WordBreakProperty.ALetter], - [/*start*/ 0xBA5, WordBreakProperty.Other], - [/*start*/ 0xBA8, WordBreakProperty.ALetter], - [/*start*/ 0xBAB, WordBreakProperty.Other], - [/*start*/ 0xBAE, WordBreakProperty.ALetter], - [/*start*/ 0xBBA, WordBreakProperty.Other], - [/*start*/ 0xBBE, WordBreakProperty.Extend], - [/*start*/ 0xBC3, WordBreakProperty.Other], - [/*start*/ 0xBC6, WordBreakProperty.Extend], - [/*start*/ 0xBC9, WordBreakProperty.Other], - [/*start*/ 0xBCA, WordBreakProperty.Extend], - [/*start*/ 0xBCE, WordBreakProperty.Other], - [/*start*/ 0xBD0, WordBreakProperty.ALetter], - [/*start*/ 0xBD1, WordBreakProperty.Other], - [/*start*/ 0xBD7, WordBreakProperty.Extend], - [/*start*/ 0xBD8, WordBreakProperty.Other], - [/*start*/ 0xBE6, WordBreakProperty.Numeric], - [/*start*/ 0xBF0, WordBreakProperty.Other], - [/*start*/ 0xC00, WordBreakProperty.Extend], - [/*start*/ 0xC05, WordBreakProperty.ALetter], - [/*start*/ 0xC0D, WordBreakProperty.Other], - [/*start*/ 0xC0E, WordBreakProperty.ALetter], - [/*start*/ 0xC11, WordBreakProperty.Other], - [/*start*/ 0xC12, WordBreakProperty.ALetter], - [/*start*/ 0xC29, WordBreakProperty.Other], - [/*start*/ 0xC2A, WordBreakProperty.ALetter], - [/*start*/ 0xC3A, WordBreakProperty.Other], - [/*start*/ 0xC3C, WordBreakProperty.Extend], - [/*start*/ 0xC3D, WordBreakProperty.ALetter], - [/*start*/ 0xC3E, WordBreakProperty.Extend], - [/*start*/ 0xC45, WordBreakProperty.Other], - [/*start*/ 0xC46, WordBreakProperty.Extend], - [/*start*/ 0xC49, WordBreakProperty.Other], - [/*start*/ 0xC4A, WordBreakProperty.Extend], - [/*start*/ 0xC4E, WordBreakProperty.Other], - [/*start*/ 0xC55, WordBreakProperty.Extend], - [/*start*/ 0xC57, WordBreakProperty.Other], - [/*start*/ 0xC58, WordBreakProperty.ALetter], - [/*start*/ 0xC5B, WordBreakProperty.Other], - [/*start*/ 0xC5D, WordBreakProperty.ALetter], - [/*start*/ 0xC5E, WordBreakProperty.Other], - [/*start*/ 0xC60, WordBreakProperty.ALetter], - [/*start*/ 0xC62, WordBreakProperty.Extend], - [/*start*/ 0xC64, WordBreakProperty.Other], - [/*start*/ 0xC66, WordBreakProperty.Numeric], - [/*start*/ 0xC70, WordBreakProperty.Other], - [/*start*/ 0xC80, WordBreakProperty.ALetter], - [/*start*/ 0xC81, WordBreakProperty.Extend], - [/*start*/ 0xC84, WordBreakProperty.Other], - [/*start*/ 0xC85, WordBreakProperty.ALetter], - [/*start*/ 0xC8D, WordBreakProperty.Other], - [/*start*/ 0xC8E, WordBreakProperty.ALetter], - [/*start*/ 0xC91, WordBreakProperty.Other], - [/*start*/ 0xC92, WordBreakProperty.ALetter], - [/*start*/ 0xCA9, WordBreakProperty.Other], - [/*start*/ 0xCAA, WordBreakProperty.ALetter], - [/*start*/ 0xCB4, WordBreakProperty.Other], - [/*start*/ 0xCB5, WordBreakProperty.ALetter], - [/*start*/ 0xCBA, WordBreakProperty.Other], - [/*start*/ 0xCBC, WordBreakProperty.Extend], - [/*start*/ 0xCBD, WordBreakProperty.ALetter], - [/*start*/ 0xCBE, WordBreakProperty.Extend], - [/*start*/ 0xCC5, WordBreakProperty.Other], - [/*start*/ 0xCC6, WordBreakProperty.Extend], - [/*start*/ 0xCC9, WordBreakProperty.Other], - [/*start*/ 0xCCA, WordBreakProperty.Extend], - [/*start*/ 0xCCE, WordBreakProperty.Other], - [/*start*/ 0xCD5, WordBreakProperty.Extend], - [/*start*/ 0xCD7, WordBreakProperty.Other], - [/*start*/ 0xCDD, WordBreakProperty.ALetter], - [/*start*/ 0xCDF, WordBreakProperty.Other], - [/*start*/ 0xCE0, WordBreakProperty.ALetter], - [/*start*/ 0xCE2, WordBreakProperty.Extend], - [/*start*/ 0xCE4, WordBreakProperty.Other], - [/*start*/ 0xCE6, WordBreakProperty.Numeric], - [/*start*/ 0xCF0, WordBreakProperty.Other], - [/*start*/ 0xCF1, WordBreakProperty.ALetter], - [/*start*/ 0xCF3, WordBreakProperty.Extend], - [/*start*/ 0xCF4, WordBreakProperty.Other], - [/*start*/ 0xD00, WordBreakProperty.Extend], - [/*start*/ 0xD04, WordBreakProperty.ALetter], - [/*start*/ 0xD0D, WordBreakProperty.Other], - [/*start*/ 0xD0E, WordBreakProperty.ALetter], - [/*start*/ 0xD11, WordBreakProperty.Other], - [/*start*/ 0xD12, WordBreakProperty.ALetter], - [/*start*/ 0xD3B, WordBreakProperty.Extend], - [/*start*/ 0xD3D, WordBreakProperty.ALetter], - [/*start*/ 0xD3E, WordBreakProperty.Extend], - [/*start*/ 0xD45, WordBreakProperty.Other], - [/*start*/ 0xD46, WordBreakProperty.Extend], - [/*start*/ 0xD49, WordBreakProperty.Other], - [/*start*/ 0xD4A, WordBreakProperty.Extend], - [/*start*/ 0xD4E, WordBreakProperty.ALetter], - [/*start*/ 0xD4F, WordBreakProperty.Other], - [/*start*/ 0xD54, WordBreakProperty.ALetter], - [/*start*/ 0xD57, WordBreakProperty.Extend], - [/*start*/ 0xD58, WordBreakProperty.Other], - [/*start*/ 0xD5F, WordBreakProperty.ALetter], - [/*start*/ 0xD62, WordBreakProperty.Extend], - [/*start*/ 0xD64, WordBreakProperty.Other], - [/*start*/ 0xD66, WordBreakProperty.Numeric], - [/*start*/ 0xD70, WordBreakProperty.Other], - [/*start*/ 0xD7A, WordBreakProperty.ALetter], - [/*start*/ 0xD80, WordBreakProperty.Other], - [/*start*/ 0xD81, WordBreakProperty.Extend], - [/*start*/ 0xD84, WordBreakProperty.Other], - [/*start*/ 0xD85, WordBreakProperty.ALetter], - [/*start*/ 0xD97, WordBreakProperty.Other], - [/*start*/ 0xD9A, WordBreakProperty.ALetter], - [/*start*/ 0xDB2, WordBreakProperty.Other], - [/*start*/ 0xDB3, WordBreakProperty.ALetter], - [/*start*/ 0xDBC, WordBreakProperty.Other], - [/*start*/ 0xDBD, WordBreakProperty.ALetter], - [/*start*/ 0xDBE, WordBreakProperty.Other], - [/*start*/ 0xDC0, WordBreakProperty.ALetter], - [/*start*/ 0xDC7, WordBreakProperty.Other], - [/*start*/ 0xDCA, WordBreakProperty.Extend], - [/*start*/ 0xDCB, WordBreakProperty.Other], - [/*start*/ 0xDCF, WordBreakProperty.Extend], - [/*start*/ 0xDD5, WordBreakProperty.Other], - [/*start*/ 0xDD6, WordBreakProperty.Extend], - [/*start*/ 0xDD7, WordBreakProperty.Other], - [/*start*/ 0xDD8, WordBreakProperty.Extend], - [/*start*/ 0xDE0, WordBreakProperty.Other], - [/*start*/ 0xDE6, WordBreakProperty.Numeric], - [/*start*/ 0xDF0, WordBreakProperty.Other], - [/*start*/ 0xDF2, WordBreakProperty.Extend], - [/*start*/ 0xDF4, WordBreakProperty.Other], - [/*start*/ 0xE31, WordBreakProperty.Extend], - [/*start*/ 0xE32, WordBreakProperty.Other], - [/*start*/ 0xE34, WordBreakProperty.Extend], - [/*start*/ 0xE3B, WordBreakProperty.Other], - [/*start*/ 0xE47, WordBreakProperty.Extend], - [/*start*/ 0xE4F, WordBreakProperty.Other], - [/*start*/ 0xE50, WordBreakProperty.Numeric], - [/*start*/ 0xE5A, WordBreakProperty.Other], - [/*start*/ 0xEB1, WordBreakProperty.Extend], - [/*start*/ 0xEB2, WordBreakProperty.Other], - [/*start*/ 0xEB4, WordBreakProperty.Extend], - [/*start*/ 0xEBD, WordBreakProperty.Other], - [/*start*/ 0xEC8, WordBreakProperty.Extend], - [/*start*/ 0xECF, WordBreakProperty.Other], - [/*start*/ 0xED0, WordBreakProperty.Numeric], - [/*start*/ 0xEDA, WordBreakProperty.Other], - [/*start*/ 0xF00, WordBreakProperty.ALetter], - [/*start*/ 0xF01, WordBreakProperty.Other], - [/*start*/ 0xF18, WordBreakProperty.Extend], - [/*start*/ 0xF1A, WordBreakProperty.Other], - [/*start*/ 0xF20, WordBreakProperty.Numeric], - [/*start*/ 0xF2A, WordBreakProperty.Other], - [/*start*/ 0xF35, WordBreakProperty.Extend], - [/*start*/ 0xF36, WordBreakProperty.Other], - [/*start*/ 0xF37, WordBreakProperty.Extend], - [/*start*/ 0xF38, WordBreakProperty.Other], - [/*start*/ 0xF39, WordBreakProperty.Extend], - [/*start*/ 0xF3A, WordBreakProperty.Other], - [/*start*/ 0xF3E, WordBreakProperty.Extend], - [/*start*/ 0xF40, WordBreakProperty.ALetter], - [/*start*/ 0xF48, WordBreakProperty.Other], - [/*start*/ 0xF49, WordBreakProperty.ALetter], - [/*start*/ 0xF6D, WordBreakProperty.Other], - [/*start*/ 0xF71, WordBreakProperty.Extend], - [/*start*/ 0xF85, WordBreakProperty.Other], - [/*start*/ 0xF86, WordBreakProperty.Extend], - [/*start*/ 0xF88, WordBreakProperty.ALetter], - [/*start*/ 0xF8D, WordBreakProperty.Extend], - [/*start*/ 0xF98, WordBreakProperty.Other], - [/*start*/ 0xF99, WordBreakProperty.Extend], - [/*start*/ 0xFBD, WordBreakProperty.Other], - [/*start*/ 0xFC6, WordBreakProperty.Extend], - [/*start*/ 0xFC7, WordBreakProperty.Other], - [/*start*/ 0x102B, WordBreakProperty.Extend], - [/*start*/ 0x103F, WordBreakProperty.Other], - [/*start*/ 0x1040, WordBreakProperty.Numeric], - [/*start*/ 0x104A, WordBreakProperty.Other], - [/*start*/ 0x1056, WordBreakProperty.Extend], - [/*start*/ 0x105A, WordBreakProperty.Other], - [/*start*/ 0x105E, WordBreakProperty.Extend], - [/*start*/ 0x1061, WordBreakProperty.Other], - [/*start*/ 0x1062, WordBreakProperty.Extend], - [/*start*/ 0x1065, WordBreakProperty.Other], - [/*start*/ 0x1067, WordBreakProperty.Extend], - [/*start*/ 0x106E, WordBreakProperty.Other], - [/*start*/ 0x1071, WordBreakProperty.Extend], - [/*start*/ 0x1075, WordBreakProperty.Other], - [/*start*/ 0x1082, WordBreakProperty.Extend], - [/*start*/ 0x108E, WordBreakProperty.Other], - [/*start*/ 0x108F, WordBreakProperty.Extend], - [/*start*/ 0x1090, WordBreakProperty.Numeric], - [/*start*/ 0x109A, WordBreakProperty.Extend], - [/*start*/ 0x109E, WordBreakProperty.Other], - [/*start*/ 0x10A0, WordBreakProperty.ALetter], - [/*start*/ 0x10C6, WordBreakProperty.Other], - [/*start*/ 0x10C7, WordBreakProperty.ALetter], - [/*start*/ 0x10C8, WordBreakProperty.Other], - [/*start*/ 0x10CD, WordBreakProperty.ALetter], - [/*start*/ 0x10CE, WordBreakProperty.Other], - [/*start*/ 0x10D0, WordBreakProperty.ALetter], - [/*start*/ 0x10FB, WordBreakProperty.Other], - [/*start*/ 0x10FC, WordBreakProperty.ALetter], - [/*start*/ 0x1249, WordBreakProperty.Other], - [/*start*/ 0x124A, WordBreakProperty.ALetter], - [/*start*/ 0x124E, WordBreakProperty.Other], - [/*start*/ 0x1250, WordBreakProperty.ALetter], - [/*start*/ 0x1257, WordBreakProperty.Other], - [/*start*/ 0x1258, WordBreakProperty.ALetter], - [/*start*/ 0x1259, WordBreakProperty.Other], - [/*start*/ 0x125A, WordBreakProperty.ALetter], - [/*start*/ 0x125E, WordBreakProperty.Other], - [/*start*/ 0x1260, WordBreakProperty.ALetter], - [/*start*/ 0x1289, WordBreakProperty.Other], - [/*start*/ 0x128A, WordBreakProperty.ALetter], - [/*start*/ 0x128E, WordBreakProperty.Other], - [/*start*/ 0x1290, WordBreakProperty.ALetter], - [/*start*/ 0x12B1, WordBreakProperty.Other], - [/*start*/ 0x12B2, WordBreakProperty.ALetter], - [/*start*/ 0x12B6, WordBreakProperty.Other], - [/*start*/ 0x12B8, WordBreakProperty.ALetter], - [/*start*/ 0x12BF, WordBreakProperty.Other], - [/*start*/ 0x12C0, WordBreakProperty.ALetter], - [/*start*/ 0x12C1, WordBreakProperty.Other], - [/*start*/ 0x12C2, WordBreakProperty.ALetter], - [/*start*/ 0x12C6, WordBreakProperty.Other], - [/*start*/ 0x12C8, WordBreakProperty.ALetter], - [/*start*/ 0x12D7, WordBreakProperty.Other], - [/*start*/ 0x12D8, WordBreakProperty.ALetter], - [/*start*/ 0x1311, WordBreakProperty.Other], - [/*start*/ 0x1312, WordBreakProperty.ALetter], - [/*start*/ 0x1316, WordBreakProperty.Other], - [/*start*/ 0x1318, WordBreakProperty.ALetter], - [/*start*/ 0x135B, WordBreakProperty.Other], - [/*start*/ 0x135D, WordBreakProperty.Extend], - [/*start*/ 0x1360, WordBreakProperty.Other], - [/*start*/ 0x1380, WordBreakProperty.ALetter], - [/*start*/ 0x1390, WordBreakProperty.Other], - [/*start*/ 0x13A0, WordBreakProperty.ALetter], - [/*start*/ 0x13F6, WordBreakProperty.Other], - [/*start*/ 0x13F8, WordBreakProperty.ALetter], - [/*start*/ 0x13FE, WordBreakProperty.Other], - [/*start*/ 0x1401, WordBreakProperty.ALetter], - [/*start*/ 0x166D, WordBreakProperty.Other], - [/*start*/ 0x166F, WordBreakProperty.ALetter], - [/*start*/ 0x1680, WordBreakProperty.WSegSpace], - [/*start*/ 0x1681, WordBreakProperty.ALetter], - [/*start*/ 0x169B, WordBreakProperty.Other], - [/*start*/ 0x16A0, WordBreakProperty.ALetter], - [/*start*/ 0x16EB, WordBreakProperty.Other], - [/*start*/ 0x16EE, WordBreakProperty.ALetter], - [/*start*/ 0x16F9, WordBreakProperty.Other], - [/*start*/ 0x1700, WordBreakProperty.ALetter], - [/*start*/ 0x1712, WordBreakProperty.Extend], - [/*start*/ 0x1716, WordBreakProperty.Other], - [/*start*/ 0x171F, WordBreakProperty.ALetter], - [/*start*/ 0x1732, WordBreakProperty.Extend], - [/*start*/ 0x1735, WordBreakProperty.Other], - [/*start*/ 0x1740, WordBreakProperty.ALetter], - [/*start*/ 0x1752, WordBreakProperty.Extend], - [/*start*/ 0x1754, WordBreakProperty.Other], - [/*start*/ 0x1760, WordBreakProperty.ALetter], - [/*start*/ 0x176D, WordBreakProperty.Other], - [/*start*/ 0x176E, WordBreakProperty.ALetter], - [/*start*/ 0x1771, WordBreakProperty.Other], - [/*start*/ 0x1772, WordBreakProperty.Extend], - [/*start*/ 0x1774, WordBreakProperty.Other], - [/*start*/ 0x17B4, WordBreakProperty.Extend], - [/*start*/ 0x17D4, WordBreakProperty.Other], - [/*start*/ 0x17DD, WordBreakProperty.Extend], - [/*start*/ 0x17DE, WordBreakProperty.Other], - [/*start*/ 0x17E0, WordBreakProperty.Numeric], - [/*start*/ 0x17EA, WordBreakProperty.Other], - [/*start*/ 0x180B, WordBreakProperty.Extend], - [/*start*/ 0x180E, WordBreakProperty.Format], - [/*start*/ 0x180F, WordBreakProperty.Extend], - [/*start*/ 0x1810, WordBreakProperty.Numeric], - [/*start*/ 0x181A, WordBreakProperty.Other], - [/*start*/ 0x1820, WordBreakProperty.ALetter], - [/*start*/ 0x1879, WordBreakProperty.Other], - [/*start*/ 0x1880, WordBreakProperty.ALetter], - [/*start*/ 0x1885, WordBreakProperty.Extend], - [/*start*/ 0x1887, WordBreakProperty.ALetter], - [/*start*/ 0x18A9, WordBreakProperty.Extend], - [/*start*/ 0x18AA, WordBreakProperty.ALetter], - [/*start*/ 0x18AB, WordBreakProperty.Other], - [/*start*/ 0x18B0, WordBreakProperty.ALetter], - [/*start*/ 0x18F6, WordBreakProperty.Other], - [/*start*/ 0x1900, WordBreakProperty.ALetter], - [/*start*/ 0x191F, WordBreakProperty.Other], - [/*start*/ 0x1920, WordBreakProperty.Extend], - [/*start*/ 0x192C, WordBreakProperty.Other], - [/*start*/ 0x1930, WordBreakProperty.Extend], - [/*start*/ 0x193C, WordBreakProperty.Other], - [/*start*/ 0x1946, WordBreakProperty.Numeric], - [/*start*/ 0x1950, WordBreakProperty.Other], - [/*start*/ 0x19D0, WordBreakProperty.Numeric], - [/*start*/ 0x19DA, WordBreakProperty.Other], - [/*start*/ 0x1A00, WordBreakProperty.ALetter], - [/*start*/ 0x1A17, WordBreakProperty.Extend], - [/*start*/ 0x1A1C, WordBreakProperty.Other], - [/*start*/ 0x1A55, WordBreakProperty.Extend], - [/*start*/ 0x1A5F, WordBreakProperty.Other], - [/*start*/ 0x1A60, WordBreakProperty.Extend], - [/*start*/ 0x1A7D, WordBreakProperty.Other], - [/*start*/ 0x1A7F, WordBreakProperty.Extend], - [/*start*/ 0x1A80, WordBreakProperty.Numeric], - [/*start*/ 0x1A8A, WordBreakProperty.Other], - [/*start*/ 0x1A90, WordBreakProperty.Numeric], - [/*start*/ 0x1A9A, WordBreakProperty.Other], - [/*start*/ 0x1AB0, WordBreakProperty.Extend], - [/*start*/ 0x1ACF, WordBreakProperty.Other], - [/*start*/ 0x1B00, WordBreakProperty.Extend], - [/*start*/ 0x1B05, WordBreakProperty.ALetter], - [/*start*/ 0x1B34, WordBreakProperty.Extend], - [/*start*/ 0x1B45, WordBreakProperty.ALetter], - [/*start*/ 0x1B4D, WordBreakProperty.Other], - [/*start*/ 0x1B50, WordBreakProperty.Numeric], - [/*start*/ 0x1B5A, WordBreakProperty.Other], - [/*start*/ 0x1B6B, WordBreakProperty.Extend], - [/*start*/ 0x1B74, WordBreakProperty.Other], - [/*start*/ 0x1B80, WordBreakProperty.Extend], - [/*start*/ 0x1B83, WordBreakProperty.ALetter], - [/*start*/ 0x1BA1, WordBreakProperty.Extend], - [/*start*/ 0x1BAE, WordBreakProperty.ALetter], - [/*start*/ 0x1BB0, WordBreakProperty.Numeric], - [/*start*/ 0x1BBA, WordBreakProperty.ALetter], - [/*start*/ 0x1BE6, WordBreakProperty.Extend], - [/*start*/ 0x1BF4, WordBreakProperty.Other], - [/*start*/ 0x1C00, WordBreakProperty.ALetter], - [/*start*/ 0x1C24, WordBreakProperty.Extend], - [/*start*/ 0x1C38, WordBreakProperty.Other], - [/*start*/ 0x1C40, WordBreakProperty.Numeric], - [/*start*/ 0x1C4A, WordBreakProperty.Other], - [/*start*/ 0x1C4D, WordBreakProperty.ALetter], - [/*start*/ 0x1C50, WordBreakProperty.Numeric], - [/*start*/ 0x1C5A, WordBreakProperty.ALetter], - [/*start*/ 0x1C7E, WordBreakProperty.Other], - [/*start*/ 0x1C80, WordBreakProperty.ALetter], - [/*start*/ 0x1C89, WordBreakProperty.Other], - [/*start*/ 0x1C90, WordBreakProperty.ALetter], - [/*start*/ 0x1CBB, WordBreakProperty.Other], - [/*start*/ 0x1CBD, WordBreakProperty.ALetter], - [/*start*/ 0x1CC0, WordBreakProperty.Other], - [/*start*/ 0x1CD0, WordBreakProperty.Extend], - [/*start*/ 0x1CD3, WordBreakProperty.Other], - [/*start*/ 0x1CD4, WordBreakProperty.Extend], - [/*start*/ 0x1CE9, WordBreakProperty.ALetter], - [/*start*/ 0x1CED, WordBreakProperty.Extend], - [/*start*/ 0x1CEE, WordBreakProperty.ALetter], - [/*start*/ 0x1CF4, WordBreakProperty.Extend], - [/*start*/ 0x1CF5, WordBreakProperty.ALetter], - [/*start*/ 0x1CF7, WordBreakProperty.Extend], - [/*start*/ 0x1CFA, WordBreakProperty.ALetter], - [/*start*/ 0x1CFB, WordBreakProperty.Other], - [/*start*/ 0x1D00, WordBreakProperty.ALetter], - [/*start*/ 0x1DC0, WordBreakProperty.Extend], - [/*start*/ 0x1E00, WordBreakProperty.ALetter], - [/*start*/ 0x1F16, WordBreakProperty.Other], - [/*start*/ 0x1F18, WordBreakProperty.ALetter], - [/*start*/ 0x1F1E, WordBreakProperty.Other], - [/*start*/ 0x1F20, WordBreakProperty.ALetter], - [/*start*/ 0x1F46, WordBreakProperty.Other], - [/*start*/ 0x1F48, WordBreakProperty.ALetter], - [/*start*/ 0x1F4E, WordBreakProperty.Other], - [/*start*/ 0x1F50, WordBreakProperty.ALetter], - [/*start*/ 0x1F58, WordBreakProperty.Other], - [/*start*/ 0x1F59, WordBreakProperty.ALetter], - [/*start*/ 0x1F5A, WordBreakProperty.Other], - [/*start*/ 0x1F5B, WordBreakProperty.ALetter], - [/*start*/ 0x1F5C, WordBreakProperty.Other], - [/*start*/ 0x1F5D, WordBreakProperty.ALetter], - [/*start*/ 0x1F5E, WordBreakProperty.Other], - [/*start*/ 0x1F5F, WordBreakProperty.ALetter], - [/*start*/ 0x1F7E, WordBreakProperty.Other], - [/*start*/ 0x1F80, WordBreakProperty.ALetter], - [/*start*/ 0x1FB5, WordBreakProperty.Other], - [/*start*/ 0x1FB6, WordBreakProperty.ALetter], - [/*start*/ 0x1FBD, WordBreakProperty.Other], - [/*start*/ 0x1FBE, WordBreakProperty.ALetter], - [/*start*/ 0x1FBF, WordBreakProperty.Other], - [/*start*/ 0x1FC2, WordBreakProperty.ALetter], - [/*start*/ 0x1FC5, WordBreakProperty.Other], - [/*start*/ 0x1FC6, WordBreakProperty.ALetter], - [/*start*/ 0x1FCD, WordBreakProperty.Other], - [/*start*/ 0x1FD0, WordBreakProperty.ALetter], - [/*start*/ 0x1FD4, WordBreakProperty.Other], - [/*start*/ 0x1FD6, WordBreakProperty.ALetter], - [/*start*/ 0x1FDC, WordBreakProperty.Other], - [/*start*/ 0x1FE0, WordBreakProperty.ALetter], - [/*start*/ 0x1FED, WordBreakProperty.Other], - [/*start*/ 0x1FF2, WordBreakProperty.ALetter], - [/*start*/ 0x1FF5, WordBreakProperty.Other], - [/*start*/ 0x1FF6, WordBreakProperty.ALetter], - [/*start*/ 0x1FFD, WordBreakProperty.Other], - [/*start*/ 0x2000, WordBreakProperty.WSegSpace], - [/*start*/ 0x2007, WordBreakProperty.Other], - [/*start*/ 0x2008, WordBreakProperty.WSegSpace], - [/*start*/ 0x200B, WordBreakProperty.Other], - [/*start*/ 0x200C, WordBreakProperty.Extend], - [/*start*/ 0x200D, WordBreakProperty.ZWJ], - [/*start*/ 0x200E, WordBreakProperty.Format], - [/*start*/ 0x2010, WordBreakProperty.Other], - [/*start*/ 0x2018, WordBreakProperty.MidNumLet], - [/*start*/ 0x201A, WordBreakProperty.Other], - [/*start*/ 0x2024, WordBreakProperty.MidNumLet], - [/*start*/ 0x2025, WordBreakProperty.Other], - [/*start*/ 0x2027, WordBreakProperty.MidLetter], - [/*start*/ 0x2028, WordBreakProperty.Newline], - [/*start*/ 0x202A, WordBreakProperty.Format], - [/*start*/ 0x202F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x2030, WordBreakProperty.Other], - [/*start*/ 0x203F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x2041, WordBreakProperty.Other], - [/*start*/ 0x2044, WordBreakProperty.MidNum], - [/*start*/ 0x2045, WordBreakProperty.Other], - [/*start*/ 0x2054, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x2055, WordBreakProperty.Other], - [/*start*/ 0x205F, WordBreakProperty.WSegSpace], - [/*start*/ 0x2060, WordBreakProperty.Format], - [/*start*/ 0x2065, WordBreakProperty.Other], - [/*start*/ 0x2066, WordBreakProperty.Format], - [/*start*/ 0x2070, WordBreakProperty.Other], - [/*start*/ 0x2071, WordBreakProperty.ALetter], - [/*start*/ 0x2072, WordBreakProperty.Other], - [/*start*/ 0x207F, WordBreakProperty.ALetter], - [/*start*/ 0x2080, WordBreakProperty.Other], - [/*start*/ 0x2090, WordBreakProperty.ALetter], - [/*start*/ 0x209D, WordBreakProperty.Other], - [/*start*/ 0x20D0, WordBreakProperty.Extend], - [/*start*/ 0x20F1, WordBreakProperty.Other], - [/*start*/ 0x2102, WordBreakProperty.ALetter], - [/*start*/ 0x2103, WordBreakProperty.Other], - [/*start*/ 0x2107, WordBreakProperty.ALetter], - [/*start*/ 0x2108, WordBreakProperty.Other], - [/*start*/ 0x210A, WordBreakProperty.ALetter], - [/*start*/ 0x2114, WordBreakProperty.Other], - [/*start*/ 0x2115, WordBreakProperty.ALetter], - [/*start*/ 0x2116, WordBreakProperty.Other], - [/*start*/ 0x2119, WordBreakProperty.ALetter], - [/*start*/ 0x211E, WordBreakProperty.Other], - [/*start*/ 0x2124, WordBreakProperty.ALetter], - [/*start*/ 0x2125, WordBreakProperty.Other], - [/*start*/ 0x2126, WordBreakProperty.ALetter], - [/*start*/ 0x2127, WordBreakProperty.Other], - [/*start*/ 0x2128, WordBreakProperty.ALetter], - [/*start*/ 0x2129, WordBreakProperty.Other], - [/*start*/ 0x212A, WordBreakProperty.ALetter], - [/*start*/ 0x212E, WordBreakProperty.Other], - [/*start*/ 0x212F, WordBreakProperty.ALetter], - [/*start*/ 0x213A, WordBreakProperty.Other], - [/*start*/ 0x213C, WordBreakProperty.ALetter], - [/*start*/ 0x2140, WordBreakProperty.Other], - [/*start*/ 0x2145, WordBreakProperty.ALetter], - [/*start*/ 0x214A, WordBreakProperty.Other], - [/*start*/ 0x214E, WordBreakProperty.ALetter], - [/*start*/ 0x214F, WordBreakProperty.Other], - [/*start*/ 0x2160, WordBreakProperty.ALetter], - [/*start*/ 0x2189, WordBreakProperty.Other], - [/*start*/ 0x24B6, WordBreakProperty.ALetter], - [/*start*/ 0x24EA, WordBreakProperty.Other], - [/*start*/ 0x2C00, WordBreakProperty.ALetter], - [/*start*/ 0x2CE5, WordBreakProperty.Other], - [/*start*/ 0x2CEB, WordBreakProperty.ALetter], - [/*start*/ 0x2CEF, WordBreakProperty.Extend], - [/*start*/ 0x2CF2, WordBreakProperty.ALetter], - [/*start*/ 0x2CF4, WordBreakProperty.Other], - [/*start*/ 0x2D00, WordBreakProperty.ALetter], - [/*start*/ 0x2D26, WordBreakProperty.Other], - [/*start*/ 0x2D27, WordBreakProperty.ALetter], - [/*start*/ 0x2D28, WordBreakProperty.Other], - [/*start*/ 0x2D2D, WordBreakProperty.ALetter], - [/*start*/ 0x2D2E, WordBreakProperty.Other], - [/*start*/ 0x2D30, WordBreakProperty.ALetter], - [/*start*/ 0x2D68, WordBreakProperty.Other], - [/*start*/ 0x2D6F, WordBreakProperty.ALetter], - [/*start*/ 0x2D70, WordBreakProperty.Other], - [/*start*/ 0x2D7F, WordBreakProperty.Extend], - [/*start*/ 0x2D80, WordBreakProperty.ALetter], - [/*start*/ 0x2D97, WordBreakProperty.Other], - [/*start*/ 0x2DA0, WordBreakProperty.ALetter], - [/*start*/ 0x2DA7, WordBreakProperty.Other], - [/*start*/ 0x2DA8, WordBreakProperty.ALetter], - [/*start*/ 0x2DAF, WordBreakProperty.Other], - [/*start*/ 0x2DB0, WordBreakProperty.ALetter], - [/*start*/ 0x2DB7, WordBreakProperty.Other], - [/*start*/ 0x2DB8, WordBreakProperty.ALetter], - [/*start*/ 0x2DBF, WordBreakProperty.Other], - [/*start*/ 0x2DC0, WordBreakProperty.ALetter], - [/*start*/ 0x2DC7, WordBreakProperty.Other], - [/*start*/ 0x2DC8, WordBreakProperty.ALetter], - [/*start*/ 0x2DCF, WordBreakProperty.Other], - [/*start*/ 0x2DD0, WordBreakProperty.ALetter], - [/*start*/ 0x2DD7, WordBreakProperty.Other], - [/*start*/ 0x2DD8, WordBreakProperty.ALetter], - [/*start*/ 0x2DDF, WordBreakProperty.Other], - [/*start*/ 0x2DE0, WordBreakProperty.Extend], - [/*start*/ 0x2E00, WordBreakProperty.Other], - [/*start*/ 0x2E2F, WordBreakProperty.ALetter], - [/*start*/ 0x2E30, WordBreakProperty.Other], - [/*start*/ 0x3000, WordBreakProperty.WSegSpace], - [/*start*/ 0x3001, WordBreakProperty.Other], - [/*start*/ 0x3005, WordBreakProperty.ALetter], - [/*start*/ 0x3006, WordBreakProperty.Other], - [/*start*/ 0x302A, WordBreakProperty.Extend], - [/*start*/ 0x3030, WordBreakProperty.Other], - [/*start*/ 0x3031, WordBreakProperty.Katakana], - [/*start*/ 0x3036, WordBreakProperty.Other], - [/*start*/ 0x303B, WordBreakProperty.ALetter], - [/*start*/ 0x303D, WordBreakProperty.Other], - [/*start*/ 0x3099, WordBreakProperty.Extend], - [/*start*/ 0x309B, WordBreakProperty.Katakana], - [/*start*/ 0x309D, WordBreakProperty.Other], - [/*start*/ 0x30A0, WordBreakProperty.Katakana], - [/*start*/ 0x30FB, WordBreakProperty.Other], - [/*start*/ 0x30FC, WordBreakProperty.Katakana], - [/*start*/ 0x3100, WordBreakProperty.Other], - [/*start*/ 0x3105, WordBreakProperty.ALetter], - [/*start*/ 0x3130, WordBreakProperty.Other], - [/*start*/ 0x3131, WordBreakProperty.ALetter], - [/*start*/ 0x318F, WordBreakProperty.Other], - [/*start*/ 0x31A0, WordBreakProperty.ALetter], - [/*start*/ 0x31C0, WordBreakProperty.Other], - [/*start*/ 0x31F0, WordBreakProperty.Katakana], - [/*start*/ 0x3200, WordBreakProperty.Other], - [/*start*/ 0x32D0, WordBreakProperty.Katakana], - [/*start*/ 0x32FF, WordBreakProperty.Other], - [/*start*/ 0x3300, WordBreakProperty.Katakana], - [/*start*/ 0x3358, WordBreakProperty.Other], - [/*start*/ 0xA000, WordBreakProperty.ALetter], - [/*start*/ 0xA48D, WordBreakProperty.Other], - [/*start*/ 0xA4D0, WordBreakProperty.ALetter], - [/*start*/ 0xA4FE, WordBreakProperty.Other], - [/*start*/ 0xA500, WordBreakProperty.ALetter], - [/*start*/ 0xA60D, WordBreakProperty.Other], - [/*start*/ 0xA610, WordBreakProperty.ALetter], - [/*start*/ 0xA620, WordBreakProperty.Numeric], - [/*start*/ 0xA62A, WordBreakProperty.ALetter], - [/*start*/ 0xA62C, WordBreakProperty.Other], - [/*start*/ 0xA640, WordBreakProperty.ALetter], - [/*start*/ 0xA66F, WordBreakProperty.Extend], - [/*start*/ 0xA673, WordBreakProperty.Other], - [/*start*/ 0xA674, WordBreakProperty.Extend], - [/*start*/ 0xA67E, WordBreakProperty.Other], - [/*start*/ 0xA67F, WordBreakProperty.ALetter], - [/*start*/ 0xA69E, WordBreakProperty.Extend], - [/*start*/ 0xA6A0, WordBreakProperty.ALetter], - [/*start*/ 0xA6F0, WordBreakProperty.Extend], - [/*start*/ 0xA6F2, WordBreakProperty.Other], - [/*start*/ 0xA708, WordBreakProperty.ALetter], - [/*start*/ 0xA7CB, WordBreakProperty.Other], - [/*start*/ 0xA7D0, WordBreakProperty.ALetter], - [/*start*/ 0xA7D2, WordBreakProperty.Other], - [/*start*/ 0xA7D3, WordBreakProperty.ALetter], - [/*start*/ 0xA7D4, WordBreakProperty.Other], - [/*start*/ 0xA7D5, WordBreakProperty.ALetter], - [/*start*/ 0xA7DA, WordBreakProperty.Other], - [/*start*/ 0xA7F2, WordBreakProperty.ALetter], - [/*start*/ 0xA802, WordBreakProperty.Extend], - [/*start*/ 0xA803, WordBreakProperty.ALetter], - [/*start*/ 0xA806, WordBreakProperty.Extend], - [/*start*/ 0xA807, WordBreakProperty.ALetter], - [/*start*/ 0xA80B, WordBreakProperty.Extend], - [/*start*/ 0xA80C, WordBreakProperty.ALetter], - [/*start*/ 0xA823, WordBreakProperty.Extend], - [/*start*/ 0xA828, WordBreakProperty.Other], - [/*start*/ 0xA82C, WordBreakProperty.Extend], - [/*start*/ 0xA82D, WordBreakProperty.Other], - [/*start*/ 0xA840, WordBreakProperty.ALetter], - [/*start*/ 0xA874, WordBreakProperty.Other], - [/*start*/ 0xA880, WordBreakProperty.Extend], - [/*start*/ 0xA882, WordBreakProperty.ALetter], - [/*start*/ 0xA8B4, WordBreakProperty.Extend], - [/*start*/ 0xA8C6, WordBreakProperty.Other], - [/*start*/ 0xA8D0, WordBreakProperty.Numeric], - [/*start*/ 0xA8DA, WordBreakProperty.Other], - [/*start*/ 0xA8E0, WordBreakProperty.Extend], - [/*start*/ 0xA8F2, WordBreakProperty.ALetter], - [/*start*/ 0xA8F8, WordBreakProperty.Other], - [/*start*/ 0xA8FB, WordBreakProperty.ALetter], - [/*start*/ 0xA8FC, WordBreakProperty.Other], - [/*start*/ 0xA8FD, WordBreakProperty.ALetter], - [/*start*/ 0xA8FF, WordBreakProperty.Extend], - [/*start*/ 0xA900, WordBreakProperty.Numeric], - [/*start*/ 0xA90A, WordBreakProperty.ALetter], - [/*start*/ 0xA926, WordBreakProperty.Extend], - [/*start*/ 0xA92E, WordBreakProperty.Other], - [/*start*/ 0xA930, WordBreakProperty.ALetter], - [/*start*/ 0xA947, WordBreakProperty.Extend], - [/*start*/ 0xA954, WordBreakProperty.Other], - [/*start*/ 0xA960, WordBreakProperty.ALetter], - [/*start*/ 0xA97D, WordBreakProperty.Other], - [/*start*/ 0xA980, WordBreakProperty.Extend], - [/*start*/ 0xA984, WordBreakProperty.ALetter], - [/*start*/ 0xA9B3, WordBreakProperty.Extend], - [/*start*/ 0xA9C1, WordBreakProperty.Other], - [/*start*/ 0xA9CF, WordBreakProperty.ALetter], - [/*start*/ 0xA9D0, WordBreakProperty.Numeric], - [/*start*/ 0xA9DA, WordBreakProperty.Other], - [/*start*/ 0xA9E5, WordBreakProperty.Extend], - [/*start*/ 0xA9E6, WordBreakProperty.Other], - [/*start*/ 0xA9F0, WordBreakProperty.Numeric], - [/*start*/ 0xA9FA, WordBreakProperty.Other], - [/*start*/ 0xAA00, WordBreakProperty.ALetter], - [/*start*/ 0xAA29, WordBreakProperty.Extend], - [/*start*/ 0xAA37, WordBreakProperty.Other], - [/*start*/ 0xAA40, WordBreakProperty.ALetter], - [/*start*/ 0xAA43, WordBreakProperty.Extend], - [/*start*/ 0xAA44, WordBreakProperty.ALetter], - [/*start*/ 0xAA4C, WordBreakProperty.Extend], - [/*start*/ 0xAA4E, WordBreakProperty.Other], - [/*start*/ 0xAA50, WordBreakProperty.Numeric], - [/*start*/ 0xAA5A, WordBreakProperty.Other], - [/*start*/ 0xAA7B, WordBreakProperty.Extend], - [/*start*/ 0xAA7E, WordBreakProperty.Other], - [/*start*/ 0xAAB0, WordBreakProperty.Extend], - [/*start*/ 0xAAB1, WordBreakProperty.Other], - [/*start*/ 0xAAB2, WordBreakProperty.Extend], - [/*start*/ 0xAAB5, WordBreakProperty.Other], - [/*start*/ 0xAAB7, WordBreakProperty.Extend], - [/*start*/ 0xAAB9, WordBreakProperty.Other], - [/*start*/ 0xAABE, WordBreakProperty.Extend], - [/*start*/ 0xAAC0, WordBreakProperty.Other], - [/*start*/ 0xAAC1, WordBreakProperty.Extend], - [/*start*/ 0xAAC2, WordBreakProperty.Other], - [/*start*/ 0xAAE0, WordBreakProperty.ALetter], - [/*start*/ 0xAAEB, WordBreakProperty.Extend], - [/*start*/ 0xAAF0, WordBreakProperty.Other], - [/*start*/ 0xAAF2, WordBreakProperty.ALetter], - [/*start*/ 0xAAF5, WordBreakProperty.Extend], - [/*start*/ 0xAAF7, WordBreakProperty.Other], - [/*start*/ 0xAB01, WordBreakProperty.ALetter], - [/*start*/ 0xAB07, WordBreakProperty.Other], - [/*start*/ 0xAB09, WordBreakProperty.ALetter], - [/*start*/ 0xAB0F, WordBreakProperty.Other], - [/*start*/ 0xAB11, WordBreakProperty.ALetter], - [/*start*/ 0xAB17, WordBreakProperty.Other], - [/*start*/ 0xAB20, WordBreakProperty.ALetter], - [/*start*/ 0xAB27, WordBreakProperty.Other], - [/*start*/ 0xAB28, WordBreakProperty.ALetter], - [/*start*/ 0xAB2F, WordBreakProperty.Other], - [/*start*/ 0xAB30, WordBreakProperty.ALetter], - [/*start*/ 0xAB6A, WordBreakProperty.Other], - [/*start*/ 0xAB70, WordBreakProperty.ALetter], - [/*start*/ 0xABE3, WordBreakProperty.Extend], - [/*start*/ 0xABEB, WordBreakProperty.Other], - [/*start*/ 0xABEC, WordBreakProperty.Extend], - [/*start*/ 0xABEE, WordBreakProperty.Other], - [/*start*/ 0xABF0, WordBreakProperty.Numeric], - [/*start*/ 0xABFA, WordBreakProperty.Other], - [/*start*/ 0xAC00, WordBreakProperty.ALetter], - [/*start*/ 0xD7A4, WordBreakProperty.Other], - [/*start*/ 0xD7B0, WordBreakProperty.ALetter], - [/*start*/ 0xD7C7, WordBreakProperty.Other], - [/*start*/ 0xD7CB, WordBreakProperty.ALetter], - [/*start*/ 0xD7FC, WordBreakProperty.Other], - [/*start*/ 0xFB00, WordBreakProperty.ALetter], - [/*start*/ 0xFB07, WordBreakProperty.Other], - [/*start*/ 0xFB13, WordBreakProperty.ALetter], - [/*start*/ 0xFB18, WordBreakProperty.Other], - [/*start*/ 0xFB1D, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB1E, WordBreakProperty.Extend], - [/*start*/ 0xFB1F, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB29, WordBreakProperty.Other], - [/*start*/ 0xFB2A, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB37, WordBreakProperty.Other], - [/*start*/ 0xFB38, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB3D, WordBreakProperty.Other], - [/*start*/ 0xFB3E, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB3F, WordBreakProperty.Other], - [/*start*/ 0xFB40, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB42, WordBreakProperty.Other], - [/*start*/ 0xFB43, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB45, WordBreakProperty.Other], - [/*start*/ 0xFB46, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB50, WordBreakProperty.ALetter], - [/*start*/ 0xFBB2, WordBreakProperty.Other], - [/*start*/ 0xFBD3, WordBreakProperty.ALetter], - [/*start*/ 0xFD3E, WordBreakProperty.Other], - [/*start*/ 0xFD50, WordBreakProperty.ALetter], - [/*start*/ 0xFD90, WordBreakProperty.Other], - [/*start*/ 0xFD92, WordBreakProperty.ALetter], - [/*start*/ 0xFDC8, WordBreakProperty.Other], - [/*start*/ 0xFDF0, WordBreakProperty.ALetter], - [/*start*/ 0xFDFC, WordBreakProperty.Other], - [/*start*/ 0xFE00, WordBreakProperty.Extend], - [/*start*/ 0xFE10, WordBreakProperty.MidNum], - [/*start*/ 0xFE11, WordBreakProperty.Other], - [/*start*/ 0xFE13, WordBreakProperty.MidLetter], - [/*start*/ 0xFE14, WordBreakProperty.MidNum], - [/*start*/ 0xFE15, WordBreakProperty.Other], - [/*start*/ 0xFE20, WordBreakProperty.Extend], - [/*start*/ 0xFE30, WordBreakProperty.Other], - [/*start*/ 0xFE33, WordBreakProperty.ExtendNumLet], - [/*start*/ 0xFE35, WordBreakProperty.Other], - [/*start*/ 0xFE4D, WordBreakProperty.ExtendNumLet], - [/*start*/ 0xFE50, WordBreakProperty.MidNum], - [/*start*/ 0xFE51, WordBreakProperty.Other], - [/*start*/ 0xFE52, WordBreakProperty.MidNumLet], - [/*start*/ 0xFE53, WordBreakProperty.Other], - [/*start*/ 0xFE54, WordBreakProperty.MidNum], - [/*start*/ 0xFE55, WordBreakProperty.MidLetter], - [/*start*/ 0xFE56, WordBreakProperty.Other], - [/*start*/ 0xFE70, WordBreakProperty.ALetter], - [/*start*/ 0xFE75, WordBreakProperty.Other], - [/*start*/ 0xFE76, WordBreakProperty.ALetter], - [/*start*/ 0xFEFD, WordBreakProperty.Other], - [/*start*/ 0xFEFF, WordBreakProperty.Format], - [/*start*/ 0xFF00, WordBreakProperty.Other], - [/*start*/ 0xFF07, WordBreakProperty.MidNumLet], - [/*start*/ 0xFF08, WordBreakProperty.Other], - [/*start*/ 0xFF0C, WordBreakProperty.MidNum], - [/*start*/ 0xFF0D, WordBreakProperty.Other], - [/*start*/ 0xFF0E, WordBreakProperty.MidNumLet], - [/*start*/ 0xFF0F, WordBreakProperty.Other], - [/*start*/ 0xFF10, WordBreakProperty.Numeric], - [/*start*/ 0xFF1A, WordBreakProperty.MidLetter], - [/*start*/ 0xFF1B, WordBreakProperty.MidNum], - [/*start*/ 0xFF1C, WordBreakProperty.Other], - [/*start*/ 0xFF21, WordBreakProperty.ALetter], - [/*start*/ 0xFF3B, WordBreakProperty.Other], - [/*start*/ 0xFF3F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0xFF40, WordBreakProperty.Other], - [/*start*/ 0xFF41, WordBreakProperty.ALetter], - [/*start*/ 0xFF5B, WordBreakProperty.Other], - [/*start*/ 0xFF66, WordBreakProperty.Katakana], - [/*start*/ 0xFF9E, WordBreakProperty.Extend], - [/*start*/ 0xFFA0, WordBreakProperty.ALetter], - [/*start*/ 0xFFBF, WordBreakProperty.Other], - [/*start*/ 0xFFC2, WordBreakProperty.ALetter], - [/*start*/ 0xFFC8, WordBreakProperty.Other], - [/*start*/ 0xFFCA, WordBreakProperty.ALetter], - [/*start*/ 0xFFD0, WordBreakProperty.Other], - [/*start*/ 0xFFD2, WordBreakProperty.ALetter], - [/*start*/ 0xFFD8, WordBreakProperty.Other], - [/*start*/ 0xFFDA, WordBreakProperty.ALetter], - [/*start*/ 0xFFDD, WordBreakProperty.Other], - [/*start*/ 0xFFF9, WordBreakProperty.Format], - [/*start*/ 0xFFFC, WordBreakProperty.Other], - [/*start*/ 0x10000, WordBreakProperty.ALetter], - [/*start*/ 0x1000C, WordBreakProperty.Other], - [/*start*/ 0x1000D, WordBreakProperty.ALetter], - [/*start*/ 0x10027, WordBreakProperty.Other], - [/*start*/ 0x10028, WordBreakProperty.ALetter], - [/*start*/ 0x1003B, WordBreakProperty.Other], - [/*start*/ 0x1003C, WordBreakProperty.ALetter], - [/*start*/ 0x1003E, WordBreakProperty.Other], - [/*start*/ 0x1003F, WordBreakProperty.ALetter], - [/*start*/ 0x1004E, WordBreakProperty.Other], - [/*start*/ 0x10050, WordBreakProperty.ALetter], - [/*start*/ 0x1005E, WordBreakProperty.Other], - [/*start*/ 0x10080, WordBreakProperty.ALetter], - [/*start*/ 0x100FB, WordBreakProperty.Other], - [/*start*/ 0x10140, WordBreakProperty.ALetter], - [/*start*/ 0x10175, WordBreakProperty.Other], - [/*start*/ 0x101FD, WordBreakProperty.Extend], - [/*start*/ 0x101FE, WordBreakProperty.Other], - [/*start*/ 0x10280, WordBreakProperty.ALetter], - [/*start*/ 0x1029D, WordBreakProperty.Other], - [/*start*/ 0x102A0, WordBreakProperty.ALetter], - [/*start*/ 0x102D1, WordBreakProperty.Other], - [/*start*/ 0x102E0, WordBreakProperty.Extend], - [/*start*/ 0x102E1, WordBreakProperty.Other], - [/*start*/ 0x10300, WordBreakProperty.ALetter], - [/*start*/ 0x10320, WordBreakProperty.Other], - [/*start*/ 0x1032D, WordBreakProperty.ALetter], - [/*start*/ 0x1034B, WordBreakProperty.Other], - [/*start*/ 0x10350, WordBreakProperty.ALetter], - [/*start*/ 0x10376, WordBreakProperty.Extend], - [/*start*/ 0x1037B, WordBreakProperty.Other], - [/*start*/ 0x10380, WordBreakProperty.ALetter], - [/*start*/ 0x1039E, WordBreakProperty.Other], - [/*start*/ 0x103A0, WordBreakProperty.ALetter], - [/*start*/ 0x103C4, WordBreakProperty.Other], - [/*start*/ 0x103C8, WordBreakProperty.ALetter], - [/*start*/ 0x103D0, WordBreakProperty.Other], - [/*start*/ 0x103D1, WordBreakProperty.ALetter], - [/*start*/ 0x103D6, WordBreakProperty.Other], - [/*start*/ 0x10400, WordBreakProperty.ALetter], - [/*start*/ 0x1049E, WordBreakProperty.Other], - [/*start*/ 0x104A0, WordBreakProperty.Numeric], - [/*start*/ 0x104AA, WordBreakProperty.Other], - [/*start*/ 0x104B0, WordBreakProperty.ALetter], - [/*start*/ 0x104D4, WordBreakProperty.Other], - [/*start*/ 0x104D8, WordBreakProperty.ALetter], - [/*start*/ 0x104FC, WordBreakProperty.Other], - [/*start*/ 0x10500, WordBreakProperty.ALetter], - [/*start*/ 0x10528, WordBreakProperty.Other], - [/*start*/ 0x10530, WordBreakProperty.ALetter], - [/*start*/ 0x10564, WordBreakProperty.Other], - [/*start*/ 0x10570, WordBreakProperty.ALetter], - [/*start*/ 0x1057B, WordBreakProperty.Other], - [/*start*/ 0x1057C, WordBreakProperty.ALetter], - [/*start*/ 0x1058B, WordBreakProperty.Other], - [/*start*/ 0x1058C, WordBreakProperty.ALetter], - [/*start*/ 0x10593, WordBreakProperty.Other], - [/*start*/ 0x10594, WordBreakProperty.ALetter], - [/*start*/ 0x10596, WordBreakProperty.Other], - [/*start*/ 0x10597, WordBreakProperty.ALetter], - [/*start*/ 0x105A2, WordBreakProperty.Other], - [/*start*/ 0x105A3, WordBreakProperty.ALetter], - [/*start*/ 0x105B2, WordBreakProperty.Other], - [/*start*/ 0x105B3, WordBreakProperty.ALetter], - [/*start*/ 0x105BA, WordBreakProperty.Other], - [/*start*/ 0x105BB, WordBreakProperty.ALetter], - [/*start*/ 0x105BD, WordBreakProperty.Other], - [/*start*/ 0x10600, WordBreakProperty.ALetter], - [/*start*/ 0x10737, WordBreakProperty.Other], - [/*start*/ 0x10740, WordBreakProperty.ALetter], - [/*start*/ 0x10756, WordBreakProperty.Other], - [/*start*/ 0x10760, WordBreakProperty.ALetter], - [/*start*/ 0x10768, WordBreakProperty.Other], - [/*start*/ 0x10780, WordBreakProperty.ALetter], - [/*start*/ 0x10786, WordBreakProperty.Other], - [/*start*/ 0x10787, WordBreakProperty.ALetter], - [/*start*/ 0x107B1, WordBreakProperty.Other], - [/*start*/ 0x107B2, WordBreakProperty.ALetter], - [/*start*/ 0x107BB, WordBreakProperty.Other], - [/*start*/ 0x10800, WordBreakProperty.ALetter], - [/*start*/ 0x10806, WordBreakProperty.Other], - [/*start*/ 0x10808, WordBreakProperty.ALetter], - [/*start*/ 0x10809, WordBreakProperty.Other], - [/*start*/ 0x1080A, WordBreakProperty.ALetter], - [/*start*/ 0x10836, WordBreakProperty.Other], - [/*start*/ 0x10837, WordBreakProperty.ALetter], - [/*start*/ 0x10839, WordBreakProperty.Other], - [/*start*/ 0x1083C, WordBreakProperty.ALetter], - [/*start*/ 0x1083D, WordBreakProperty.Other], - [/*start*/ 0x1083F, WordBreakProperty.ALetter], - [/*start*/ 0x10856, WordBreakProperty.Other], - [/*start*/ 0x10860, WordBreakProperty.ALetter], - [/*start*/ 0x10877, WordBreakProperty.Other], - [/*start*/ 0x10880, WordBreakProperty.ALetter], - [/*start*/ 0x1089F, WordBreakProperty.Other], - [/*start*/ 0x108E0, WordBreakProperty.ALetter], - [/*start*/ 0x108F3, WordBreakProperty.Other], - [/*start*/ 0x108F4, WordBreakProperty.ALetter], - [/*start*/ 0x108F6, WordBreakProperty.Other], - [/*start*/ 0x10900, WordBreakProperty.ALetter], - [/*start*/ 0x10916, WordBreakProperty.Other], - [/*start*/ 0x10920, WordBreakProperty.ALetter], - [/*start*/ 0x1093A, WordBreakProperty.Other], - [/*start*/ 0x10980, WordBreakProperty.ALetter], - [/*start*/ 0x109B8, WordBreakProperty.Other], - [/*start*/ 0x109BE, WordBreakProperty.ALetter], - [/*start*/ 0x109C0, WordBreakProperty.Other], - [/*start*/ 0x10A00, WordBreakProperty.ALetter], - [/*start*/ 0x10A01, WordBreakProperty.Extend], - [/*start*/ 0x10A04, WordBreakProperty.Other], - [/*start*/ 0x10A05, WordBreakProperty.Extend], - [/*start*/ 0x10A07, WordBreakProperty.Other], - [/*start*/ 0x10A0C, WordBreakProperty.Extend], - [/*start*/ 0x10A10, WordBreakProperty.ALetter], - [/*start*/ 0x10A14, WordBreakProperty.Other], - [/*start*/ 0x10A15, WordBreakProperty.ALetter], - [/*start*/ 0x10A18, WordBreakProperty.Other], - [/*start*/ 0x10A19, WordBreakProperty.ALetter], - [/*start*/ 0x10A36, WordBreakProperty.Other], - [/*start*/ 0x10A38, WordBreakProperty.Extend], - [/*start*/ 0x10A3B, WordBreakProperty.Other], - [/*start*/ 0x10A3F, WordBreakProperty.Extend], - [/*start*/ 0x10A40, WordBreakProperty.Other], - [/*start*/ 0x10A60, WordBreakProperty.ALetter], - [/*start*/ 0x10A7D, WordBreakProperty.Other], - [/*start*/ 0x10A80, WordBreakProperty.ALetter], - [/*start*/ 0x10A9D, WordBreakProperty.Other], - [/*start*/ 0x10AC0, WordBreakProperty.ALetter], - [/*start*/ 0x10AC8, WordBreakProperty.Other], - [/*start*/ 0x10AC9, WordBreakProperty.ALetter], - [/*start*/ 0x10AE5, WordBreakProperty.Extend], - [/*start*/ 0x10AE7, WordBreakProperty.Other], - [/*start*/ 0x10B00, WordBreakProperty.ALetter], - [/*start*/ 0x10B36, WordBreakProperty.Other], - [/*start*/ 0x10B40, WordBreakProperty.ALetter], - [/*start*/ 0x10B56, WordBreakProperty.Other], - [/*start*/ 0x10B60, WordBreakProperty.ALetter], - [/*start*/ 0x10B73, WordBreakProperty.Other], - [/*start*/ 0x10B80, WordBreakProperty.ALetter], - [/*start*/ 0x10B92, WordBreakProperty.Other], - [/*start*/ 0x10C00, WordBreakProperty.ALetter], - [/*start*/ 0x10C49, WordBreakProperty.Other], - [/*start*/ 0x10C80, WordBreakProperty.ALetter], - [/*start*/ 0x10CB3, WordBreakProperty.Other], - [/*start*/ 0x10CC0, WordBreakProperty.ALetter], - [/*start*/ 0x10CF3, WordBreakProperty.Other], - [/*start*/ 0x10D00, WordBreakProperty.ALetter], - [/*start*/ 0x10D24, WordBreakProperty.Extend], - [/*start*/ 0x10D28, WordBreakProperty.Other], - [/*start*/ 0x10D30, WordBreakProperty.Numeric], - [/*start*/ 0x10D3A, WordBreakProperty.Other], - [/*start*/ 0x10E80, WordBreakProperty.ALetter], - [/*start*/ 0x10EAA, WordBreakProperty.Other], - [/*start*/ 0x10EAB, WordBreakProperty.Extend], - [/*start*/ 0x10EAD, WordBreakProperty.Other], - [/*start*/ 0x10EB0, WordBreakProperty.ALetter], - [/*start*/ 0x10EB2, WordBreakProperty.Other], - [/*start*/ 0x10EFD, WordBreakProperty.Extend], - [/*start*/ 0x10F00, WordBreakProperty.ALetter], - [/*start*/ 0x10F1D, WordBreakProperty.Other], - [/*start*/ 0x10F27, WordBreakProperty.ALetter], - [/*start*/ 0x10F28, WordBreakProperty.Other], - [/*start*/ 0x10F30, WordBreakProperty.ALetter], - [/*start*/ 0x10F46, WordBreakProperty.Extend], - [/*start*/ 0x10F51, WordBreakProperty.Other], - [/*start*/ 0x10F70, WordBreakProperty.ALetter], - [/*start*/ 0x10F82, WordBreakProperty.Extend], - [/*start*/ 0x10F86, WordBreakProperty.Other], - [/*start*/ 0x10FB0, WordBreakProperty.ALetter], - [/*start*/ 0x10FC5, WordBreakProperty.Other], - [/*start*/ 0x10FE0, WordBreakProperty.ALetter], - [/*start*/ 0x10FF7, WordBreakProperty.Other], - [/*start*/ 0x11000, WordBreakProperty.Extend], - [/*start*/ 0x11003, WordBreakProperty.ALetter], - [/*start*/ 0x11038, WordBreakProperty.Extend], - [/*start*/ 0x11047, WordBreakProperty.Other], - [/*start*/ 0x11066, WordBreakProperty.Numeric], - [/*start*/ 0x11070, WordBreakProperty.Extend], - [/*start*/ 0x11071, WordBreakProperty.ALetter], - [/*start*/ 0x11073, WordBreakProperty.Extend], - [/*start*/ 0x11075, WordBreakProperty.ALetter], - [/*start*/ 0x11076, WordBreakProperty.Other], - [/*start*/ 0x1107F, WordBreakProperty.Extend], - [/*start*/ 0x11083, WordBreakProperty.ALetter], - [/*start*/ 0x110B0, WordBreakProperty.Extend], - [/*start*/ 0x110BB, WordBreakProperty.Other], - [/*start*/ 0x110BD, WordBreakProperty.Numeric], - [/*start*/ 0x110BE, WordBreakProperty.Other], - [/*start*/ 0x110C2, WordBreakProperty.Extend], - [/*start*/ 0x110C3, WordBreakProperty.Other], - [/*start*/ 0x110CD, WordBreakProperty.Numeric], - [/*start*/ 0x110CE, WordBreakProperty.Other], - [/*start*/ 0x110D0, WordBreakProperty.ALetter], - [/*start*/ 0x110E9, WordBreakProperty.Other], - [/*start*/ 0x110F0, WordBreakProperty.Numeric], - [/*start*/ 0x110FA, WordBreakProperty.Other], - [/*start*/ 0x11100, WordBreakProperty.Extend], - [/*start*/ 0x11103, WordBreakProperty.ALetter], - [/*start*/ 0x11127, WordBreakProperty.Extend], - [/*start*/ 0x11135, WordBreakProperty.Other], - [/*start*/ 0x11136, WordBreakProperty.Numeric], - [/*start*/ 0x11140, WordBreakProperty.Other], - [/*start*/ 0x11144, WordBreakProperty.ALetter], - [/*start*/ 0x11145, WordBreakProperty.Extend], - [/*start*/ 0x11147, WordBreakProperty.ALetter], - [/*start*/ 0x11148, WordBreakProperty.Other], - [/*start*/ 0x11150, WordBreakProperty.ALetter], - [/*start*/ 0x11173, WordBreakProperty.Extend], - [/*start*/ 0x11174, WordBreakProperty.Other], - [/*start*/ 0x11176, WordBreakProperty.ALetter], - [/*start*/ 0x11177, WordBreakProperty.Other], - [/*start*/ 0x11180, WordBreakProperty.Extend], - [/*start*/ 0x11183, WordBreakProperty.ALetter], - [/*start*/ 0x111B3, WordBreakProperty.Extend], - [/*start*/ 0x111C1, WordBreakProperty.ALetter], - [/*start*/ 0x111C5, WordBreakProperty.Other], - [/*start*/ 0x111C9, WordBreakProperty.Extend], - [/*start*/ 0x111CD, WordBreakProperty.Other], - [/*start*/ 0x111CE, WordBreakProperty.Extend], - [/*start*/ 0x111D0, WordBreakProperty.Numeric], - [/*start*/ 0x111DA, WordBreakProperty.ALetter], - [/*start*/ 0x111DB, WordBreakProperty.Other], - [/*start*/ 0x111DC, WordBreakProperty.ALetter], - [/*start*/ 0x111DD, WordBreakProperty.Other], - [/*start*/ 0x11200, WordBreakProperty.ALetter], - [/*start*/ 0x11212, WordBreakProperty.Other], - [/*start*/ 0x11213, WordBreakProperty.ALetter], - [/*start*/ 0x1122C, WordBreakProperty.Extend], - [/*start*/ 0x11238, WordBreakProperty.Other], - [/*start*/ 0x1123E, WordBreakProperty.Extend], - [/*start*/ 0x1123F, WordBreakProperty.ALetter], - [/*start*/ 0x11241, WordBreakProperty.Extend], - [/*start*/ 0x11242, WordBreakProperty.Other], - [/*start*/ 0x11280, WordBreakProperty.ALetter], - [/*start*/ 0x11287, WordBreakProperty.Other], - [/*start*/ 0x11288, WordBreakProperty.ALetter], - [/*start*/ 0x11289, WordBreakProperty.Other], - [/*start*/ 0x1128A, WordBreakProperty.ALetter], - [/*start*/ 0x1128E, WordBreakProperty.Other], - [/*start*/ 0x1128F, WordBreakProperty.ALetter], - [/*start*/ 0x1129E, WordBreakProperty.Other], - [/*start*/ 0x1129F, WordBreakProperty.ALetter], - [/*start*/ 0x112A9, WordBreakProperty.Other], - [/*start*/ 0x112B0, WordBreakProperty.ALetter], - [/*start*/ 0x112DF, WordBreakProperty.Extend], - [/*start*/ 0x112EB, WordBreakProperty.Other], - [/*start*/ 0x112F0, WordBreakProperty.Numeric], - [/*start*/ 0x112FA, WordBreakProperty.Other], - [/*start*/ 0x11300, WordBreakProperty.Extend], - [/*start*/ 0x11304, WordBreakProperty.Other], - [/*start*/ 0x11305, WordBreakProperty.ALetter], - [/*start*/ 0x1130D, WordBreakProperty.Other], - [/*start*/ 0x1130F, WordBreakProperty.ALetter], - [/*start*/ 0x11311, WordBreakProperty.Other], - [/*start*/ 0x11313, WordBreakProperty.ALetter], - [/*start*/ 0x11329, WordBreakProperty.Other], - [/*start*/ 0x1132A, WordBreakProperty.ALetter], - [/*start*/ 0x11331, WordBreakProperty.Other], - [/*start*/ 0x11332, WordBreakProperty.ALetter], - [/*start*/ 0x11334, WordBreakProperty.Other], - [/*start*/ 0x11335, WordBreakProperty.ALetter], - [/*start*/ 0x1133A, WordBreakProperty.Other], - [/*start*/ 0x1133B, WordBreakProperty.Extend], - [/*start*/ 0x1133D, WordBreakProperty.ALetter], - [/*start*/ 0x1133E, WordBreakProperty.Extend], - [/*start*/ 0x11345, WordBreakProperty.Other], - [/*start*/ 0x11347, WordBreakProperty.Extend], - [/*start*/ 0x11349, WordBreakProperty.Other], - [/*start*/ 0x1134B, WordBreakProperty.Extend], - [/*start*/ 0x1134E, WordBreakProperty.Other], - [/*start*/ 0x11350, WordBreakProperty.ALetter], - [/*start*/ 0x11351, WordBreakProperty.Other], - [/*start*/ 0x11357, WordBreakProperty.Extend], - [/*start*/ 0x11358, WordBreakProperty.Other], - [/*start*/ 0x1135D, WordBreakProperty.ALetter], - [/*start*/ 0x11362, WordBreakProperty.Extend], - [/*start*/ 0x11364, WordBreakProperty.Other], - [/*start*/ 0x11366, WordBreakProperty.Extend], - [/*start*/ 0x1136D, WordBreakProperty.Other], - [/*start*/ 0x11370, WordBreakProperty.Extend], - [/*start*/ 0x11375, WordBreakProperty.Other], - [/*start*/ 0x11400, WordBreakProperty.ALetter], - [/*start*/ 0x11435, WordBreakProperty.Extend], - [/*start*/ 0x11447, WordBreakProperty.ALetter], - [/*start*/ 0x1144B, WordBreakProperty.Other], - [/*start*/ 0x11450, WordBreakProperty.Numeric], - [/*start*/ 0x1145A, WordBreakProperty.Other], - [/*start*/ 0x1145E, WordBreakProperty.Extend], - [/*start*/ 0x1145F, WordBreakProperty.ALetter], - [/*start*/ 0x11462, WordBreakProperty.Other], - [/*start*/ 0x11480, WordBreakProperty.ALetter], - [/*start*/ 0x114B0, WordBreakProperty.Extend], - [/*start*/ 0x114C4, WordBreakProperty.ALetter], - [/*start*/ 0x114C6, WordBreakProperty.Other], - [/*start*/ 0x114C7, WordBreakProperty.ALetter], - [/*start*/ 0x114C8, WordBreakProperty.Other], - [/*start*/ 0x114D0, WordBreakProperty.Numeric], - [/*start*/ 0x114DA, WordBreakProperty.Other], - [/*start*/ 0x11580, WordBreakProperty.ALetter], - [/*start*/ 0x115AF, WordBreakProperty.Extend], - [/*start*/ 0x115B6, WordBreakProperty.Other], - [/*start*/ 0x115B8, WordBreakProperty.Extend], - [/*start*/ 0x115C1, WordBreakProperty.Other], - [/*start*/ 0x115D8, WordBreakProperty.ALetter], - [/*start*/ 0x115DC, WordBreakProperty.Extend], - [/*start*/ 0x115DE, WordBreakProperty.Other], - [/*start*/ 0x11600, WordBreakProperty.ALetter], - [/*start*/ 0x11630, WordBreakProperty.Extend], - [/*start*/ 0x11641, WordBreakProperty.Other], - [/*start*/ 0x11644, WordBreakProperty.ALetter], - [/*start*/ 0x11645, WordBreakProperty.Other], - [/*start*/ 0x11650, WordBreakProperty.Numeric], - [/*start*/ 0x1165A, WordBreakProperty.Other], - [/*start*/ 0x11680, WordBreakProperty.ALetter], - [/*start*/ 0x116AB, WordBreakProperty.Extend], - [/*start*/ 0x116B8, WordBreakProperty.ALetter], - [/*start*/ 0x116B9, WordBreakProperty.Other], - [/*start*/ 0x116C0, WordBreakProperty.Numeric], - [/*start*/ 0x116CA, WordBreakProperty.Other], - [/*start*/ 0x1171D, WordBreakProperty.Extend], - [/*start*/ 0x1172C, WordBreakProperty.Other], - [/*start*/ 0x11730, WordBreakProperty.Numeric], - [/*start*/ 0x1173A, WordBreakProperty.Other], - [/*start*/ 0x11800, WordBreakProperty.ALetter], - [/*start*/ 0x1182C, WordBreakProperty.Extend], - [/*start*/ 0x1183B, WordBreakProperty.Other], - [/*start*/ 0x118A0, WordBreakProperty.ALetter], - [/*start*/ 0x118E0, WordBreakProperty.Numeric], - [/*start*/ 0x118EA, WordBreakProperty.Other], - [/*start*/ 0x118FF, WordBreakProperty.ALetter], - [/*start*/ 0x11907, WordBreakProperty.Other], - [/*start*/ 0x11909, WordBreakProperty.ALetter], - [/*start*/ 0x1190A, WordBreakProperty.Other], - [/*start*/ 0x1190C, WordBreakProperty.ALetter], - [/*start*/ 0x11914, WordBreakProperty.Other], - [/*start*/ 0x11915, WordBreakProperty.ALetter], - [/*start*/ 0x11917, WordBreakProperty.Other], - [/*start*/ 0x11918, WordBreakProperty.ALetter], - [/*start*/ 0x11930, WordBreakProperty.Extend], - [/*start*/ 0x11936, WordBreakProperty.Other], - [/*start*/ 0x11937, WordBreakProperty.Extend], - [/*start*/ 0x11939, WordBreakProperty.Other], - [/*start*/ 0x1193B, WordBreakProperty.Extend], - [/*start*/ 0x1193F, WordBreakProperty.ALetter], - [/*start*/ 0x11940, WordBreakProperty.Extend], - [/*start*/ 0x11941, WordBreakProperty.ALetter], - [/*start*/ 0x11942, WordBreakProperty.Extend], - [/*start*/ 0x11944, WordBreakProperty.Other], - [/*start*/ 0x11950, WordBreakProperty.Numeric], - [/*start*/ 0x1195A, WordBreakProperty.Other], - [/*start*/ 0x119A0, WordBreakProperty.ALetter], - [/*start*/ 0x119A8, WordBreakProperty.Other], - [/*start*/ 0x119AA, WordBreakProperty.ALetter], - [/*start*/ 0x119D1, WordBreakProperty.Extend], - [/*start*/ 0x119D8, WordBreakProperty.Other], - [/*start*/ 0x119DA, WordBreakProperty.Extend], - [/*start*/ 0x119E1, WordBreakProperty.ALetter], - [/*start*/ 0x119E2, WordBreakProperty.Other], - [/*start*/ 0x119E3, WordBreakProperty.ALetter], - [/*start*/ 0x119E4, WordBreakProperty.Extend], - [/*start*/ 0x119E5, WordBreakProperty.Other], - [/*start*/ 0x11A00, WordBreakProperty.ALetter], - [/*start*/ 0x11A01, WordBreakProperty.Extend], - [/*start*/ 0x11A0B, WordBreakProperty.ALetter], - [/*start*/ 0x11A33, WordBreakProperty.Extend], - [/*start*/ 0x11A3A, WordBreakProperty.ALetter], - [/*start*/ 0x11A3B, WordBreakProperty.Extend], - [/*start*/ 0x11A3F, WordBreakProperty.Other], - [/*start*/ 0x11A47, WordBreakProperty.Extend], - [/*start*/ 0x11A48, WordBreakProperty.Other], - [/*start*/ 0x11A50, WordBreakProperty.ALetter], - [/*start*/ 0x11A51, WordBreakProperty.Extend], - [/*start*/ 0x11A5C, WordBreakProperty.ALetter], - [/*start*/ 0x11A8A, WordBreakProperty.Extend], - [/*start*/ 0x11A9A, WordBreakProperty.Other], - [/*start*/ 0x11A9D, WordBreakProperty.ALetter], - [/*start*/ 0x11A9E, WordBreakProperty.Other], - [/*start*/ 0x11AB0, WordBreakProperty.ALetter], - [/*start*/ 0x11AF9, WordBreakProperty.Other], - [/*start*/ 0x11C00, WordBreakProperty.ALetter], - [/*start*/ 0x11C09, WordBreakProperty.Other], - [/*start*/ 0x11C0A, WordBreakProperty.ALetter], - [/*start*/ 0x11C2F, WordBreakProperty.Extend], - [/*start*/ 0x11C37, WordBreakProperty.Other], - [/*start*/ 0x11C38, WordBreakProperty.Extend], - [/*start*/ 0x11C40, WordBreakProperty.ALetter], - [/*start*/ 0x11C41, WordBreakProperty.Other], - [/*start*/ 0x11C50, WordBreakProperty.Numeric], - [/*start*/ 0x11C5A, WordBreakProperty.Other], - [/*start*/ 0x11C72, WordBreakProperty.ALetter], - [/*start*/ 0x11C90, WordBreakProperty.Other], - [/*start*/ 0x11C92, WordBreakProperty.Extend], - [/*start*/ 0x11CA8, WordBreakProperty.Other], - [/*start*/ 0x11CA9, WordBreakProperty.Extend], - [/*start*/ 0x11CB7, WordBreakProperty.Other], - [/*start*/ 0x11D00, WordBreakProperty.ALetter], - [/*start*/ 0x11D07, WordBreakProperty.Other], - [/*start*/ 0x11D08, WordBreakProperty.ALetter], - [/*start*/ 0x11D0A, WordBreakProperty.Other], - [/*start*/ 0x11D0B, WordBreakProperty.ALetter], - [/*start*/ 0x11D31, WordBreakProperty.Extend], - [/*start*/ 0x11D37, WordBreakProperty.Other], - [/*start*/ 0x11D3A, WordBreakProperty.Extend], - [/*start*/ 0x11D3B, WordBreakProperty.Other], - [/*start*/ 0x11D3C, WordBreakProperty.Extend], - [/*start*/ 0x11D3E, WordBreakProperty.Other], - [/*start*/ 0x11D3F, WordBreakProperty.Extend], - [/*start*/ 0x11D46, WordBreakProperty.ALetter], - [/*start*/ 0x11D47, WordBreakProperty.Extend], - [/*start*/ 0x11D48, WordBreakProperty.Other], - [/*start*/ 0x11D50, WordBreakProperty.Numeric], - [/*start*/ 0x11D5A, WordBreakProperty.Other], - [/*start*/ 0x11D60, WordBreakProperty.ALetter], - [/*start*/ 0x11D66, WordBreakProperty.Other], - [/*start*/ 0x11D67, WordBreakProperty.ALetter], - [/*start*/ 0x11D69, WordBreakProperty.Other], - [/*start*/ 0x11D6A, WordBreakProperty.ALetter], - [/*start*/ 0x11D8A, WordBreakProperty.Extend], - [/*start*/ 0x11D8F, WordBreakProperty.Other], - [/*start*/ 0x11D90, WordBreakProperty.Extend], - [/*start*/ 0x11D92, WordBreakProperty.Other], - [/*start*/ 0x11D93, WordBreakProperty.Extend], - [/*start*/ 0x11D98, WordBreakProperty.ALetter], - [/*start*/ 0x11D99, WordBreakProperty.Other], - [/*start*/ 0x11DA0, WordBreakProperty.Numeric], - [/*start*/ 0x11DAA, WordBreakProperty.Other], - [/*start*/ 0x11EE0, WordBreakProperty.ALetter], - [/*start*/ 0x11EF3, WordBreakProperty.Extend], - [/*start*/ 0x11EF7, WordBreakProperty.Other], - [/*start*/ 0x11F00, WordBreakProperty.Extend], - [/*start*/ 0x11F02, WordBreakProperty.ALetter], - [/*start*/ 0x11F03, WordBreakProperty.Extend], - [/*start*/ 0x11F04, WordBreakProperty.ALetter], - [/*start*/ 0x11F11, WordBreakProperty.Other], - [/*start*/ 0x11F12, WordBreakProperty.ALetter], - [/*start*/ 0x11F34, WordBreakProperty.Extend], - [/*start*/ 0x11F3B, WordBreakProperty.Other], - [/*start*/ 0x11F3E, WordBreakProperty.Extend], - [/*start*/ 0x11F43, WordBreakProperty.Other], - [/*start*/ 0x11F50, WordBreakProperty.Numeric], - [/*start*/ 0x11F5A, WordBreakProperty.Other], - [/*start*/ 0x11FB0, WordBreakProperty.ALetter], - [/*start*/ 0x11FB1, WordBreakProperty.Other], - [/*start*/ 0x12000, WordBreakProperty.ALetter], - [/*start*/ 0x1239A, WordBreakProperty.Other], - [/*start*/ 0x12400, WordBreakProperty.ALetter], - [/*start*/ 0x1246F, WordBreakProperty.Other], - [/*start*/ 0x12480, WordBreakProperty.ALetter], - [/*start*/ 0x12544, WordBreakProperty.Other], - [/*start*/ 0x12F90, WordBreakProperty.ALetter], - [/*start*/ 0x12FF1, WordBreakProperty.Other], - [/*start*/ 0x13000, WordBreakProperty.ALetter], - [/*start*/ 0x13430, WordBreakProperty.Format], - [/*start*/ 0x13440, WordBreakProperty.Extend], - [/*start*/ 0x13441, WordBreakProperty.ALetter], - [/*start*/ 0x13447, WordBreakProperty.Extend], - [/*start*/ 0x13456, WordBreakProperty.Other], - [/*start*/ 0x14400, WordBreakProperty.ALetter], - [/*start*/ 0x14647, WordBreakProperty.Other], - [/*start*/ 0x16800, WordBreakProperty.ALetter], - [/*start*/ 0x16A39, WordBreakProperty.Other], - [/*start*/ 0x16A40, WordBreakProperty.ALetter], - [/*start*/ 0x16A5F, WordBreakProperty.Other], - [/*start*/ 0x16A60, WordBreakProperty.Numeric], - [/*start*/ 0x16A6A, WordBreakProperty.Other], - [/*start*/ 0x16A70, WordBreakProperty.ALetter], - [/*start*/ 0x16ABF, WordBreakProperty.Other], - [/*start*/ 0x16AC0, WordBreakProperty.Numeric], - [/*start*/ 0x16ACA, WordBreakProperty.Other], - [/*start*/ 0x16AD0, WordBreakProperty.ALetter], - [/*start*/ 0x16AEE, WordBreakProperty.Other], - [/*start*/ 0x16AF0, WordBreakProperty.Extend], - [/*start*/ 0x16AF5, WordBreakProperty.Other], - [/*start*/ 0x16B00, WordBreakProperty.ALetter], - [/*start*/ 0x16B30, WordBreakProperty.Extend], - [/*start*/ 0x16B37, WordBreakProperty.Other], - [/*start*/ 0x16B40, WordBreakProperty.ALetter], - [/*start*/ 0x16B44, WordBreakProperty.Other], - [/*start*/ 0x16B50, WordBreakProperty.Numeric], - [/*start*/ 0x16B5A, WordBreakProperty.Other], - [/*start*/ 0x16B63, WordBreakProperty.ALetter], - [/*start*/ 0x16B78, WordBreakProperty.Other], - [/*start*/ 0x16B7D, WordBreakProperty.ALetter], - [/*start*/ 0x16B90, WordBreakProperty.Other], - [/*start*/ 0x16E40, WordBreakProperty.ALetter], - [/*start*/ 0x16E80, WordBreakProperty.Other], - [/*start*/ 0x16F00, WordBreakProperty.ALetter], - [/*start*/ 0x16F4B, WordBreakProperty.Other], - [/*start*/ 0x16F4F, WordBreakProperty.Extend], - [/*start*/ 0x16F50, WordBreakProperty.ALetter], - [/*start*/ 0x16F51, WordBreakProperty.Extend], - [/*start*/ 0x16F88, WordBreakProperty.Other], - [/*start*/ 0x16F8F, WordBreakProperty.Extend], - [/*start*/ 0x16F93, WordBreakProperty.ALetter], - [/*start*/ 0x16FA0, WordBreakProperty.Other], - [/*start*/ 0x16FE0, WordBreakProperty.ALetter], - [/*start*/ 0x16FE2, WordBreakProperty.Other], - [/*start*/ 0x16FE3, WordBreakProperty.ALetter], - [/*start*/ 0x16FE4, WordBreakProperty.Extend], - [/*start*/ 0x16FE5, WordBreakProperty.Other], - [/*start*/ 0x16FF0, WordBreakProperty.Extend], - [/*start*/ 0x16FF2, WordBreakProperty.Other], - [/*start*/ 0x1AFF0, WordBreakProperty.Katakana], - [/*start*/ 0x1AFF4, WordBreakProperty.Other], - [/*start*/ 0x1AFF5, WordBreakProperty.Katakana], - [/*start*/ 0x1AFFC, WordBreakProperty.Other], - [/*start*/ 0x1AFFD, WordBreakProperty.Katakana], - [/*start*/ 0x1AFFF, WordBreakProperty.Other], - [/*start*/ 0x1B000, WordBreakProperty.Katakana], - [/*start*/ 0x1B001, WordBreakProperty.Other], - [/*start*/ 0x1B120, WordBreakProperty.Katakana], - [/*start*/ 0x1B123, WordBreakProperty.Other], - [/*start*/ 0x1B155, WordBreakProperty.Katakana], - [/*start*/ 0x1B156, WordBreakProperty.Other], - [/*start*/ 0x1B164, WordBreakProperty.Katakana], - [/*start*/ 0x1B168, WordBreakProperty.Other], - [/*start*/ 0x1BC00, WordBreakProperty.ALetter], - [/*start*/ 0x1BC6B, WordBreakProperty.Other], - [/*start*/ 0x1BC70, WordBreakProperty.ALetter], - [/*start*/ 0x1BC7D, WordBreakProperty.Other], - [/*start*/ 0x1BC80, WordBreakProperty.ALetter], - [/*start*/ 0x1BC89, WordBreakProperty.Other], - [/*start*/ 0x1BC90, WordBreakProperty.ALetter], - [/*start*/ 0x1BC9A, WordBreakProperty.Other], - [/*start*/ 0x1BC9D, WordBreakProperty.Extend], - [/*start*/ 0x1BC9F, WordBreakProperty.Other], - [/*start*/ 0x1BCA0, WordBreakProperty.Format], - [/*start*/ 0x1BCA4, WordBreakProperty.Other], - [/*start*/ 0x1CF00, WordBreakProperty.Extend], - [/*start*/ 0x1CF2E, WordBreakProperty.Other], - [/*start*/ 0x1CF30, WordBreakProperty.Extend], - [/*start*/ 0x1CF47, WordBreakProperty.Other], - [/*start*/ 0x1D165, WordBreakProperty.Extend], - [/*start*/ 0x1D16A, WordBreakProperty.Other], - [/*start*/ 0x1D16D, WordBreakProperty.Extend], - [/*start*/ 0x1D173, WordBreakProperty.Format], - [/*start*/ 0x1D17B, WordBreakProperty.Extend], - [/*start*/ 0x1D183, WordBreakProperty.Other], - [/*start*/ 0x1D185, WordBreakProperty.Extend], - [/*start*/ 0x1D18C, WordBreakProperty.Other], - [/*start*/ 0x1D1AA, WordBreakProperty.Extend], - [/*start*/ 0x1D1AE, WordBreakProperty.Other], - [/*start*/ 0x1D242, WordBreakProperty.Extend], - [/*start*/ 0x1D245, WordBreakProperty.Other], - [/*start*/ 0x1D400, WordBreakProperty.ALetter], - [/*start*/ 0x1D455, WordBreakProperty.Other], - [/*start*/ 0x1D456, WordBreakProperty.ALetter], - [/*start*/ 0x1D49D, WordBreakProperty.Other], - [/*start*/ 0x1D49E, WordBreakProperty.ALetter], - [/*start*/ 0x1D4A0, WordBreakProperty.Other], - [/*start*/ 0x1D4A2, WordBreakProperty.ALetter], - [/*start*/ 0x1D4A3, WordBreakProperty.Other], - [/*start*/ 0x1D4A5, WordBreakProperty.ALetter], - [/*start*/ 0x1D4A7, WordBreakProperty.Other], - [/*start*/ 0x1D4A9, WordBreakProperty.ALetter], - [/*start*/ 0x1D4AD, WordBreakProperty.Other], - [/*start*/ 0x1D4AE, WordBreakProperty.ALetter], - [/*start*/ 0x1D4BA, WordBreakProperty.Other], - [/*start*/ 0x1D4BB, WordBreakProperty.ALetter], - [/*start*/ 0x1D4BC, WordBreakProperty.Other], - [/*start*/ 0x1D4BD, WordBreakProperty.ALetter], - [/*start*/ 0x1D4C4, WordBreakProperty.Other], - [/*start*/ 0x1D4C5, WordBreakProperty.ALetter], - [/*start*/ 0x1D506, WordBreakProperty.Other], - [/*start*/ 0x1D507, WordBreakProperty.ALetter], - [/*start*/ 0x1D50B, WordBreakProperty.Other], - [/*start*/ 0x1D50D, WordBreakProperty.ALetter], - [/*start*/ 0x1D515, WordBreakProperty.Other], - [/*start*/ 0x1D516, WordBreakProperty.ALetter], - [/*start*/ 0x1D51D, WordBreakProperty.Other], - [/*start*/ 0x1D51E, WordBreakProperty.ALetter], - [/*start*/ 0x1D53A, WordBreakProperty.Other], - [/*start*/ 0x1D53B, WordBreakProperty.ALetter], - [/*start*/ 0x1D53F, WordBreakProperty.Other], - [/*start*/ 0x1D540, WordBreakProperty.ALetter], - [/*start*/ 0x1D545, WordBreakProperty.Other], - [/*start*/ 0x1D546, WordBreakProperty.ALetter], - [/*start*/ 0x1D547, WordBreakProperty.Other], - [/*start*/ 0x1D54A, WordBreakProperty.ALetter], - [/*start*/ 0x1D551, WordBreakProperty.Other], - [/*start*/ 0x1D552, WordBreakProperty.ALetter], - [/*start*/ 0x1D6A6, WordBreakProperty.Other], - [/*start*/ 0x1D6A8, WordBreakProperty.ALetter], - [/*start*/ 0x1D6C1, WordBreakProperty.Other], - [/*start*/ 0x1D6C2, WordBreakProperty.ALetter], - [/*start*/ 0x1D6DB, WordBreakProperty.Other], - [/*start*/ 0x1D6DC, WordBreakProperty.ALetter], - [/*start*/ 0x1D6FB, WordBreakProperty.Other], - [/*start*/ 0x1D6FC, WordBreakProperty.ALetter], - [/*start*/ 0x1D715, WordBreakProperty.Other], - [/*start*/ 0x1D716, WordBreakProperty.ALetter], - [/*start*/ 0x1D735, WordBreakProperty.Other], - [/*start*/ 0x1D736, WordBreakProperty.ALetter], - [/*start*/ 0x1D74F, WordBreakProperty.Other], - [/*start*/ 0x1D750, WordBreakProperty.ALetter], - [/*start*/ 0x1D76F, WordBreakProperty.Other], - [/*start*/ 0x1D770, WordBreakProperty.ALetter], - [/*start*/ 0x1D789, WordBreakProperty.Other], - [/*start*/ 0x1D78A, WordBreakProperty.ALetter], - [/*start*/ 0x1D7A9, WordBreakProperty.Other], - [/*start*/ 0x1D7AA, WordBreakProperty.ALetter], - [/*start*/ 0x1D7C3, WordBreakProperty.Other], - [/*start*/ 0x1D7C4, WordBreakProperty.ALetter], - [/*start*/ 0x1D7CC, WordBreakProperty.Other], - [/*start*/ 0x1D7CE, WordBreakProperty.Numeric], - [/*start*/ 0x1D800, WordBreakProperty.Other], - [/*start*/ 0x1DA00, WordBreakProperty.Extend], - [/*start*/ 0x1DA37, WordBreakProperty.Other], - [/*start*/ 0x1DA3B, WordBreakProperty.Extend], - [/*start*/ 0x1DA6D, WordBreakProperty.Other], - [/*start*/ 0x1DA75, WordBreakProperty.Extend], - [/*start*/ 0x1DA76, WordBreakProperty.Other], - [/*start*/ 0x1DA84, WordBreakProperty.Extend], - [/*start*/ 0x1DA85, WordBreakProperty.Other], - [/*start*/ 0x1DA9B, WordBreakProperty.Extend], - [/*start*/ 0x1DAA0, WordBreakProperty.Other], - [/*start*/ 0x1DAA1, WordBreakProperty.Extend], - [/*start*/ 0x1DAB0, WordBreakProperty.Other], - [/*start*/ 0x1DF00, WordBreakProperty.ALetter], - [/*start*/ 0x1DF1F, WordBreakProperty.Other], - [/*start*/ 0x1DF25, WordBreakProperty.ALetter], - [/*start*/ 0x1DF2B, WordBreakProperty.Other], - [/*start*/ 0x1E000, WordBreakProperty.Extend], - [/*start*/ 0x1E007, WordBreakProperty.Other], - [/*start*/ 0x1E008, WordBreakProperty.Extend], - [/*start*/ 0x1E019, WordBreakProperty.Other], - [/*start*/ 0x1E01B, WordBreakProperty.Extend], - [/*start*/ 0x1E022, WordBreakProperty.Other], - [/*start*/ 0x1E023, WordBreakProperty.Extend], - [/*start*/ 0x1E025, WordBreakProperty.Other], - [/*start*/ 0x1E026, WordBreakProperty.Extend], - [/*start*/ 0x1E02B, WordBreakProperty.Other], - [/*start*/ 0x1E030, WordBreakProperty.ALetter], - [/*start*/ 0x1E06E, WordBreakProperty.Other], - [/*start*/ 0x1E08F, WordBreakProperty.Extend], - [/*start*/ 0x1E090, WordBreakProperty.Other], - [/*start*/ 0x1E100, WordBreakProperty.ALetter], - [/*start*/ 0x1E12D, WordBreakProperty.Other], - [/*start*/ 0x1E130, WordBreakProperty.Extend], - [/*start*/ 0x1E137, WordBreakProperty.ALetter], - [/*start*/ 0x1E13E, WordBreakProperty.Other], - [/*start*/ 0x1E140, WordBreakProperty.Numeric], - [/*start*/ 0x1E14A, WordBreakProperty.Other], - [/*start*/ 0x1E14E, WordBreakProperty.ALetter], - [/*start*/ 0x1E14F, WordBreakProperty.Other], - [/*start*/ 0x1E290, WordBreakProperty.ALetter], - [/*start*/ 0x1E2AE, WordBreakProperty.Extend], - [/*start*/ 0x1E2AF, WordBreakProperty.Other], - [/*start*/ 0x1E2C0, WordBreakProperty.ALetter], - [/*start*/ 0x1E2EC, WordBreakProperty.Extend], - [/*start*/ 0x1E2F0, WordBreakProperty.Numeric], - [/*start*/ 0x1E2FA, WordBreakProperty.Other], - [/*start*/ 0x1E4D0, WordBreakProperty.ALetter], - [/*start*/ 0x1E4EC, WordBreakProperty.Extend], - [/*start*/ 0x1E4F0, WordBreakProperty.Numeric], - [/*start*/ 0x1E4FA, WordBreakProperty.Other], - [/*start*/ 0x1E7E0, WordBreakProperty.ALetter], - [/*start*/ 0x1E7E7, WordBreakProperty.Other], - [/*start*/ 0x1E7E8, WordBreakProperty.ALetter], - [/*start*/ 0x1E7EC, WordBreakProperty.Other], - [/*start*/ 0x1E7ED, WordBreakProperty.ALetter], - [/*start*/ 0x1E7EF, WordBreakProperty.Other], - [/*start*/ 0x1E7F0, WordBreakProperty.ALetter], - [/*start*/ 0x1E7FF, WordBreakProperty.Other], - [/*start*/ 0x1E800, WordBreakProperty.ALetter], - [/*start*/ 0x1E8C5, WordBreakProperty.Other], - [/*start*/ 0x1E8D0, WordBreakProperty.Extend], - [/*start*/ 0x1E8D7, WordBreakProperty.Other], - [/*start*/ 0x1E900, WordBreakProperty.ALetter], - [/*start*/ 0x1E944, WordBreakProperty.Extend], - [/*start*/ 0x1E94B, WordBreakProperty.ALetter], - [/*start*/ 0x1E94C, WordBreakProperty.Other], - [/*start*/ 0x1E950, WordBreakProperty.Numeric], - [/*start*/ 0x1E95A, WordBreakProperty.Other], - [/*start*/ 0x1EE00, WordBreakProperty.ALetter], - [/*start*/ 0x1EE04, WordBreakProperty.Other], - [/*start*/ 0x1EE05, WordBreakProperty.ALetter], - [/*start*/ 0x1EE20, WordBreakProperty.Other], - [/*start*/ 0x1EE21, WordBreakProperty.ALetter], - [/*start*/ 0x1EE23, WordBreakProperty.Other], - [/*start*/ 0x1EE24, WordBreakProperty.ALetter], - [/*start*/ 0x1EE25, WordBreakProperty.Other], - [/*start*/ 0x1EE27, WordBreakProperty.ALetter], - [/*start*/ 0x1EE28, WordBreakProperty.Other], - [/*start*/ 0x1EE29, WordBreakProperty.ALetter], - [/*start*/ 0x1EE33, WordBreakProperty.Other], - [/*start*/ 0x1EE34, WordBreakProperty.ALetter], - [/*start*/ 0x1EE38, WordBreakProperty.Other], - [/*start*/ 0x1EE39, WordBreakProperty.ALetter], - [/*start*/ 0x1EE3A, WordBreakProperty.Other], - [/*start*/ 0x1EE3B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE3C, WordBreakProperty.Other], - [/*start*/ 0x1EE42, WordBreakProperty.ALetter], - [/*start*/ 0x1EE43, WordBreakProperty.Other], - [/*start*/ 0x1EE47, WordBreakProperty.ALetter], - [/*start*/ 0x1EE48, WordBreakProperty.Other], - [/*start*/ 0x1EE49, WordBreakProperty.ALetter], - [/*start*/ 0x1EE4A, WordBreakProperty.Other], - [/*start*/ 0x1EE4B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE4C, WordBreakProperty.Other], - [/*start*/ 0x1EE4D, WordBreakProperty.ALetter], - [/*start*/ 0x1EE50, WordBreakProperty.Other], - [/*start*/ 0x1EE51, WordBreakProperty.ALetter], - [/*start*/ 0x1EE53, WordBreakProperty.Other], - [/*start*/ 0x1EE54, WordBreakProperty.ALetter], - [/*start*/ 0x1EE55, WordBreakProperty.Other], - [/*start*/ 0x1EE57, WordBreakProperty.ALetter], - [/*start*/ 0x1EE58, WordBreakProperty.Other], - [/*start*/ 0x1EE59, WordBreakProperty.ALetter], - [/*start*/ 0x1EE5A, WordBreakProperty.Other], - [/*start*/ 0x1EE5B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE5C, WordBreakProperty.Other], - [/*start*/ 0x1EE5D, WordBreakProperty.ALetter], - [/*start*/ 0x1EE5E, WordBreakProperty.Other], - [/*start*/ 0x1EE5F, WordBreakProperty.ALetter], - [/*start*/ 0x1EE60, WordBreakProperty.Other], - [/*start*/ 0x1EE61, WordBreakProperty.ALetter], - [/*start*/ 0x1EE63, WordBreakProperty.Other], - [/*start*/ 0x1EE64, WordBreakProperty.ALetter], - [/*start*/ 0x1EE65, WordBreakProperty.Other], - [/*start*/ 0x1EE67, WordBreakProperty.ALetter], - [/*start*/ 0x1EE6B, WordBreakProperty.Other], - [/*start*/ 0x1EE6C, WordBreakProperty.ALetter], - [/*start*/ 0x1EE73, WordBreakProperty.Other], - [/*start*/ 0x1EE74, WordBreakProperty.ALetter], - [/*start*/ 0x1EE78, WordBreakProperty.Other], - [/*start*/ 0x1EE79, WordBreakProperty.ALetter], - [/*start*/ 0x1EE7D, WordBreakProperty.Other], - [/*start*/ 0x1EE7E, WordBreakProperty.ALetter], - [/*start*/ 0x1EE7F, WordBreakProperty.Other], - [/*start*/ 0x1EE80, WordBreakProperty.ALetter], - [/*start*/ 0x1EE8A, WordBreakProperty.Other], - [/*start*/ 0x1EE8B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE9C, WordBreakProperty.Other], - [/*start*/ 0x1EEA1, WordBreakProperty.ALetter], - [/*start*/ 0x1EEA4, WordBreakProperty.Other], - [/*start*/ 0x1EEA5, WordBreakProperty.ALetter], - [/*start*/ 0x1EEAA, WordBreakProperty.Other], - [/*start*/ 0x1EEAB, WordBreakProperty.ALetter], - [/*start*/ 0x1EEBC, WordBreakProperty.Other], - [/*start*/ 0x1F130, WordBreakProperty.ALetter], - [/*start*/ 0x1F14A, WordBreakProperty.Other], - [/*start*/ 0x1F150, WordBreakProperty.ALetter], - [/*start*/ 0x1F16A, WordBreakProperty.Other], - [/*start*/ 0x1F170, WordBreakProperty.ALetter], - [/*start*/ 0x1F18A, WordBreakProperty.Other], - [/*start*/ 0x1F1E6, WordBreakProperty.Regional_Indicator], - [/*start*/ 0x1F200, WordBreakProperty.Other], - [/*start*/ 0x1F3FB, WordBreakProperty.Extend], - [/*start*/ 0x1F400, WordBreakProperty.Other], - [/*start*/ 0x1FBF0, WordBreakProperty.Numeric], - [/*start*/ 0x1FBFA, WordBreakProperty.Other], - [/*start*/ 0xE0001, WordBreakProperty.Format], - [/*start*/ 0xE0002, WordBreakProperty.Other], - [/*start*/ 0xE0020, WordBreakProperty.Extend], - [/*start*/ 0xE0080, WordBreakProperty.Other], - [/*start*/ 0xE0100, WordBreakProperty.Extend], - [/*start*/ 0xE01F0, WordBreakProperty.Other], -]; diff --git a/common/models/wordbreakers/src/main/default/index.ts b/common/models/wordbreakers/src/main/default/index.ts index ef50ab4b31c..8df6774de2d 100644 --- a/common/models/wordbreakers/src/main/default/index.ts +++ b/common/models/wordbreakers/src/main/default/index.ts @@ -1,4 +1,4 @@ -import { WordBreakProperty, WORD_BREAK_PROPERTY, I, propertyMap } from "./data.js"; +import { WordBreakProperty, WORD_BREAK_PROPERTY, I, propertyMap } from "./data.inc.js"; /** * A set of options used to customize and extend the behavior of the default diff --git a/common/models/wordbreakers/src/main/default/searchForProperty.ts b/common/models/wordbreakers/src/main/default/searchForProperty.ts index 45a1641bd9d..fda82dd7626 100644 --- a/common/models/wordbreakers/src/main/default/searchForProperty.ts +++ b/common/models/wordbreakers/src/main/default/searchForProperty.ts @@ -1,4 +1,4 @@ -import { WordBreakProperty } from "./data.js"; +import { WordBreakProperty } from "./data.inc.js"; export function searchForProperty(codePoint: number): WordBreakProperty { const bucketSize = codePoint <= 0xFFFF ? 2 : 3; diff --git a/common/models/wordbreakers/src/main/test-index.ts b/common/models/wordbreakers/src/main/test-index.ts index a69cc42ae3d..da24a4fcf82 100644 --- a/common/models/wordbreakers/src/main/test-index.ts +++ b/common/models/wordbreakers/src/main/test-index.ts @@ -2,5 +2,5 @@ export * from './index.js'; // Exposes some internal properties for unit-test accessibility -export { WordBreakProperty } from './default/data.js'; +export { WordBreakProperty } from './default/data.inc.js'; export { searchForProperty } from './default/searchForProperty.js'; diff --git a/common/models/wordbreakers/src/data-compiler/index.ts b/common/models/wordbreakers/tools/data-compiler/index.ts similarity index 99% rename from common/models/wordbreakers/src/data-compiler/index.ts rename to common/models/wordbreakers/tools/data-compiler/index.ts index c986d132815..28c52be1aa5 100644 --- a/common/models/wordbreakers/src/data-compiler/index.ts +++ b/common/models/wordbreakers/tools/data-compiler/index.ts @@ -31,7 +31,7 @@ const MAX_CODE_POINT = 0x10FFFF; //////////////////////////////////// Main //////////////////////////////////// const projectDir = path.dirname(require.resolve("@keymanapp/models-wordbreakers/README.md")); -const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data.ts'); +const generatedFilename = path.join(projectDir, 'src', 'main', 'default', 'data.inc.ts'); // The data files should be in this repository, with names matching the // Unicode version. diff --git a/common/models/wordbreakers/src/data-compiler/tsconfig.json b/common/models/wordbreakers/tools/data-compiler/tsconfig.json similarity index 100% rename from common/models/wordbreakers/src/data-compiler/tsconfig.json rename to common/models/wordbreakers/tools/data-compiler/tsconfig.json From 7016e5b40bdbff256641689a88ad2ffadda22cff Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Aug 2024 12:53:04 +0700 Subject: [PATCH 040/262] chore(common/models): removes incomplete early check-in --- .../src/main/default/searchForProperty.ts | 44 ------------------- .../wordbreakers/src/main/test-index.ts | 1 - 2 files changed, 45 deletions(-) delete mode 100644 common/models/wordbreakers/src/main/default/searchForProperty.ts diff --git a/common/models/wordbreakers/src/main/default/searchForProperty.ts b/common/models/wordbreakers/src/main/default/searchForProperty.ts deleted file mode 100644 index fda82dd7626..00000000000 --- a/common/models/wordbreakers/src/main/default/searchForProperty.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { WordBreakProperty } from "./data.inc.js"; - -export function searchForProperty(codePoint: number): WordBreakProperty { - const bucketSize = codePoint <= 0xFFFF ? 2 : 3; - - // SMP chars take a bit more space to encode. - // TODO: encode the strings & import them here. - const encodedArray = bucketSize == 2 ? "" /* BMP string */ : "" /* non-BMP string */; - - return _searchForProperty(encodedArray, codePoint, bucketSize, 0, encodedArray.length / bucketSize - 1); -} - -/** - * Binary search for the word break property of a given CODE POINT. - * - * The auto-generated data.ts master array defines a **character range** - * lookup table. If a character's codepoint is equal to or greater than - * the start-of-range value for an entry and exclusively less than the next - * entry's start-of-range, it falls within the first entry's range bucket - * and is classified accordingly by this method. - */ -function _searchForProperty(encodedArray: string, codePoint: number, bucketSize: number, left: number, right: number): WordBreakProperty { - // All items that are not found in the array are assigned the 'Other' property. - if (right < left) { // May need special handling at end of BMP / start of non-BMP. - return WordBreakProperty.Other; - } - - let midpoint = left + ~~((right - left) / 2); - let candidate = encodedArray.codePointAt(bucketSize * midpoint); - - // If out-of-bounds, gives NaN. - let nextRange = encodedArray.codePointAt(bucketSize * (midpoint + 1)); - let startOfNextRange = isNaN(nextRange) ? Infinity : nextRange; - - if (codePoint < candidate) { - return _searchForProperty(encodedArray, codePoint, bucketSize, left, midpoint - 1); - } else if (codePoint >= startOfNextRange) { - return _searchForProperty(encodedArray, codePoint, bucketSize, midpoint + 1, right); - } - - // We found it! - const propertyCode = encodedArray.charCodeAt(bucketSize * (midpoint + 1) - 1); - return propertyCode as WordBreakProperty; -} \ No newline at end of file diff --git a/common/models/wordbreakers/src/main/test-index.ts b/common/models/wordbreakers/src/main/test-index.ts index da24a4fcf82..6ea7b045323 100644 --- a/common/models/wordbreakers/src/main/test-index.ts +++ b/common/models/wordbreakers/src/main/test-index.ts @@ -3,4 +3,3 @@ export * from './index.js'; // Exposes some internal properties for unit-test accessibility export { WordBreakProperty } from './default/data.inc.js'; -export { searchForProperty } from './default/searchForProperty.js'; From e5387f0947dd226a129384a3b60a4e4d0f5a73ee Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Aug 2024 13:07:56 +0700 Subject: [PATCH 041/262] change(web): Unicode data-fetcher build.sh -> download.sh --- common/models/wordbreakers/build.sh | 3 ++- .../{build.sh => download.sh} | 22 +++++-------------- 2 files changed, 8 insertions(+), 17 deletions(-) rename resources/standards-data/unicode-character-database/{build.sh => download.sh} (77%) diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index 38123ee01c1..e0edda09774 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -12,8 +12,9 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ +# Note: the raw text files used for data.inc.ts are found within +# /resources/standards-data/unicode-character-database. builder_describe "Builds the predictive-text wordbreaker implementation module" \ - "@/resources/standards-data/unicode-character-database configure" \ "clean" \ "configure" \ "build" \ diff --git a/resources/standards-data/unicode-character-database/build.sh b/resources/standards-data/unicode-character-database/download.sh similarity index 77% rename from resources/standards-data/unicode-character-database/build.sh rename to resources/standards-data/unicode-character-database/download.sh index edb3c1e6ba6..4b972e7afa1 100755 --- a/resources/standards-data/unicode-character-database/build.sh +++ b/resources/standards-data/unicode-character-database/download.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -# -# Compile our sourcemap-path remapping module for use by Web builds, releases, etc. -# + ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" @@ -13,12 +11,11 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe \ - "Downloads Unicode data consumed by various Keyman platforms." \ - clean configure build + "Downloads Unicode data files, version $KEYMAN_VERSION_UNICODE (see minimum-versions.inc.sh), to be committed to repo." \ + download+ builder_describe_outputs \ - configure /resources/standards-data/unicode-character-database/UnicodeData.txt \ - build /resources/standards-data/unicode-character-database/UnicodeData.txt + download /resources/standards-data/unicode-character-database/UnicodeData.txt builder_parse "$@" @@ -50,13 +47,7 @@ function downloadPropertyFile() { } } -do_clean() { - mv unicode-copyright.txt unicode-copyright.txt.bak - rm -rf *.txt - mv unicode-copyright.txt.bak unicode-copyright.txt -} - -do_configure() { +do_download() { downloadPropertyFile "${BLOCKS_SRC_HREF}" "${BLOCKS_SRC_LOCAL}" downloadPropertyFile "${UNICODE_DATA_SRC_HREF}" "${UNICODE_DATA_SRC_LOCAL}" @@ -64,5 +55,4 @@ do_configure() { downloadPropertyFile "${EMOJI_DATA_SRC_HREF}" "${EMOJI_DATA_SRC_LOCAL}" } -builder_run_action clean do_clean -builder_run_action configure do_configure \ No newline at end of file +builder_run_action download do_download \ No newline at end of file From 4159bab4586a237b7c846ae9fad8ea27841dc881 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 9 Aug 2024 09:30:50 +0700 Subject: [PATCH 042/262] fix(common/models): fix unit-test reference to renamed file --- common/models/wordbreakers/test/test-search-property.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/test/test-search-property.js b/common/models/wordbreakers/test/test-search-property.js index cdf80c45f45..85996c7286e 100644 --- a/common/models/wordbreakers/test/test-search-property.js +++ b/common/models/wordbreakers/test/test-search-property.js @@ -4,7 +4,7 @@ import { assert } from 'chai'; import { searchForProperty } from '../build/obj/default/searchForProperty.js'; -import { propertyMap } from '../build/obj/default/data.js'; +import { propertyMap } from '../build/obj/default/data.inc.js'; describe('searchForProperty', () => { it('correctly finds character classes for standard ASCII characters', () => { From 1e805949ae762b49f8d96dc340f916b59174d4f2 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 9 Aug 2024 14:49:37 +0700 Subject: [PATCH 043/262] change(mac): store partial path in UserDefaults --- .../Keyman4MacIM/KMDataRepository.h | 1 + .../Keyman4MacIM/KMDataRepository.m | 7 ++- .../Keyman4MacIM/KMSettingsRepository.m | 44 ++++++++++++++----- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index a5af2ccb881..766e9c10e95 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; - (BOOL)migrateData; +- (NSString*)buildFullPathWith:(NSString *)partialPath; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 24495007bd4..0b3b8023e65 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -164,7 +164,7 @@ - (BOOL)migrateData { BOOL didMoveData = NO; NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL dataExistsInOldLocation = [self keyboardsExistInObsoleteDirectory]; - os_log([KMLogs dataLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + os_log_debug([KMLogs dataLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); // only move data if there is something to move if (dataExistsInOldLocation) { @@ -182,4 +182,9 @@ - (BOOL)migrateData { return didMoveData; } +- (NSString*)buildFullPathWith:(NSString *)partialPath { + NSString *fullPath = [self.keymanKeyboardsDirectory.path stringByAppendingString:partialPath]; + os_log_debug([KMLogs dataLog], "createFullPathWith: '%{public}@' with partialPath '%{public}@'", self.keymanKeyboardsDirectory.path, partialPath); + return fullPath; +} @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 489c4e63dc6..3109770067d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -12,12 +12,14 @@ #import "KMSettingsRepository.h" #import "KMLogs.h" +#import "KMDataRepository.h" NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; -NSString *const kObsoletePathComponent = @"/Documents/"; +//NSString *const kObsoletePathComponent = @"/Documents/"; +NSString *const kObsoletePathComponent = @"/Documents/Keyman-Keyboards"; NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; /** @@ -53,7 +55,7 @@ - (void)setDataModelVersionIfNecessary { */ - (BOOL)settingsExist { - return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil; + return ([[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil); } /** @@ -122,13 +124,13 @@ - (void)convertSettingsForMigration { - (void)convertSelectedKeyboardPathForMigration { NSString *selectedKeyboardPath = [self selectedKeyboard]; - if (selectedKeyboardPath != nil) { - NSString *newPathString = [self convertOldKeyboardPath:selectedKeyboardPath]; + NSString *newPathString = [self trimObsoleteKeyboardPath:selectedKeyboardPath]; if ([selectedKeyboardPath isNotEqualTo:newPathString]) { [self saveSelectedKeyboard:newPathString]; - os_log([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); + os_log_debug([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); + os_log_debug([KMLogs dataLog], "full path of selected keyboard from buildFullPathWith = '%{public}@'", [KMDataRepository.shared buildFullPathWith:newPathString]); } } } @@ -137,7 +139,7 @@ - (void)convertSelectedKeyboardPathForMigration { * Convert the path of the keyboard designating the Documents folder to its new location * in the Application Support folder */ - +/* - (NSString *)convertOldKeyboardPath:(NSString *)oldPath { NSString *newPathString = @""; if(oldPath != nil) { @@ -145,18 +147,36 @@ - (NSString *)convertOldKeyboardPath:(NSString *)oldPath { } return newPathString; } +*/ + +/** + * To convert the keyboard path for the new location, just trim the parent directory from the path + * No need to repeatedly store the parent directory with the path of each keyboard + * If the old directory is not found in the string, then return the string unchanged + */ +- (NSString *)trimObsoleteKeyboardPath:(NSString *)oldPath { + NSString *newPath = oldPath; + if(oldPath != nil) { + NSRange range = [oldPath rangeOfString:kObsoletePathComponent]; + if (range.length > 0) { + newPath = [oldPath substringFromIndex:range.location + range.length]; + os_log_debug([KMLogs dataLog], "trimmed keyboard path from '%{public}@' to '%{public}@'", oldPath, newPath); + } + } + return newPath; +} - (void)convertActiveKeyboardArrayForMigration { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - NSMutableArray *activeKeyboards = [self activeKeyboards]; + NSMutableArray *keyboards = [self activeKeyboards]; NSMutableArray *convertedActiveKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; BOOL didConvert = NO; - for (NSString *oldPath in activeKeyboards) { - NSString *newPath = [self convertOldKeyboardPath:oldPath]; + for (NSString *oldPath in keyboards) { + NSString *newPath = [self trimObsoleteKeyboardPath:oldPath]; if ([oldPath isNotEqualTo:newPath]) { [convertedActiveKeyboards addObject:newPath]; - os_log([KMLogs dataLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); + os_log_debug([KMLogs dataLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); // if we have adjusted at least one path, set flag didConvert = YES; } else { @@ -183,7 +203,7 @@ - (void)convertPersistedOptionsPathsForMigration { os_log_info([KMLogs configLog], "persisted options found in UserDefaults with key = %{public}@", key); } for (NSString *key in optionsMap) { - NSString *newPathString = [self convertOldKeyboardPath:key]; + NSString *newPathString = [self trimObsoleteKeyboardPath:key]; NSDictionary *optionsValue = [optionsMap objectForKey:key]; if ([key isNotEqualTo:newPathString]) { @@ -191,7 +211,7 @@ - (void)convertPersistedOptionsPathsForMigration { // insert options into new map with newly converted path as key [mutableOptionsMap setObject:optionsValue forKey:newPathString]; - os_log([KMLogs dataLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); + os_log_debug([KMLogs dataLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); } else { // retain options that did not need converting [mutableOptionsMap setObject:optionsValue forKey:key]; From 786a1063ea93a8dbd9d2fc70c535dadbc04fbaf8 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 9 Aug 2024 14:51:45 +0700 Subject: [PATCH 044/262] fix(android): Add SEND, NEXT, PREVIOUS actions --- .../com/keyman/engine/KMKeyboardJSHandler.java | 15 +++++++++++++++ .../main/java/com/keyman/engine/KMManager.java | 17 ++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java index 66af2ebe07d..3a995560c6f 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java @@ -167,11 +167,26 @@ public void run() { ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH); break; + // Send action + case SEND : + ic.performEditorAction(EditorInfo.IME_ACTION_SEND); + break; + + // Next action + case NEXT : + ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); + break; + // Done action case DONE : ic.performEditorAction(EditorInfo.IME_ACTION_DONE); break; + // Previous action + case PREVIOUS : + ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); + break; + // Messaging apps case NEWLINE : // Send newline without advancing cursor diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index adf9d30737d..0bbe93b9320 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -184,8 +184,11 @@ public String toString() { public enum EnterModeType { GO, // Go action SEARCH, // Search action - NEWLINE, // Send newline character + SEND, // Send action + NEXT, // Next action DONE, // Done action + PREVIOUS, // Previous action + NEWLINE, // Send newline character DEFAULT, // Default ENTER action } @@ -1287,10 +1290,22 @@ public static void setEnterMode(int imeOptions, int inputType) { value = EnterModeType.SEARCH; break; + case EditorInfo.IME_ACTION_SEND: + value = EnterModeType.SEND; + break; + + case EditorInfo.IME_ACTION_NEXT: + value = EnterModeType.NEXT; + break; + case EditorInfo.IME_ACTION_DONE: value = EnterModeType.DONE; break; + case EditorInfo.IME_ACTION_PREVIOUS: + value = EnterModeType.PREVIOUS; + break; + default: value = EnterModeType.DEFAULT; } From b8e961b979088275a08bcbe9f3bedef13c78aa8d Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:52:55 +1000 Subject: [PATCH 045/262] fix(windows): splash screen useses new start keyman string The new string just has %1$s place holder to then hard code in Keyman. xslt substitues %1$s to %0:s the template rule matching is on that. --- windows/src/desktop/kmshell/xml/splash.xsl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/xml/splash.xsl b/windows/src/desktop/kmshell/xml/splash.xsl index 51514e91118..956447d0201 100644 --- a/windows/src/desktop/kmshell/xml/splash.xsl +++ b/windows/src/desktop/kmshell/xml/splash.xsl @@ -30,7 +30,13 @@
-
+
+ + + + + +
From fe289e4133ab211b2bc14868fdf262e9f0dc3f8f Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 9 Aug 2024 07:13:38 +0700 Subject: [PATCH 046/262] feat(android): Add menu to customize longpress delay time --- .../kMAPro/src/main/AndroidManifest.xml | 5 + .../kmapro/AdjustLongpressDelayActivity.java | 150 ++++++++++++++++++ .../kmapro/KeymanSettingsFragment.java | 11 +- .../main/res/drawable/ic_action_timelapse.xml | 5 + .../preference_duration_icon_layout.xml | 5 + .../kMAPro/src/main/res/values/strings.xml | 3 + 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index 7196abdb229..3ba7eaf9fca 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -297,6 +297,11 @@ android:configChanges="orientation" android:label="@string/app_name" android:theme="@style/AppTheme.Base" /> + diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java new file mode 100644 index 00000000000..e18912abe9d --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -0,0 +1,150 @@ +/** + * Copyright (C) SIL International. All rights reserved. + */ +package com.tavultesoft.kmapro; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; + +import com.keyman.engine.BaseActivity; +import com.keyman.engine.KMManager; + +/** + * Settings menu for adjusting the longpress delay time. The value for the current longpress delay time + * is saved in shared preferences. + */ +public class AdjustLongpressDelayActivity extends BaseActivity { + private static final String TAG = "AdjustLongpressDelay"; + public static final String adjustLongpressDelayKey = "AdjustLongpressDelay"; + + private static Button resetButton = null; + private static ImageView sampleKeyboard = null; + + // Keeps track of the adjusted keyboard height for saving + private static SharedPreferences.Editor editor = null; + private static int currentHeight = 0; + private static ViewGroup.LayoutParams layoutParams; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Context context = this; + + setContentView(R.layout.activity_adjust_keyboard_height); + + Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar); + setSupportActionBar(toolbar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(null); + actionBar.setDisplayUseLogoEnabled(false); + actionBar.setDisplayShowHomeEnabled(false); + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setDisplayShowCustomEnabled(true); + actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.keyman_blue))); + } + + TextView adjustKeyboardHeightActivityTitle = (TextView) findViewById(R.id.bar_title); + adjustKeyboardHeightActivityTitle.setWidth((int) getResources().getDimension(R.dimen.package_label_width)); + + String titleStr = getString(R.string.adjust_keyboard_height); + adjustKeyboardHeightActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); + adjustKeyboardHeightActivityTitle.setText(titleStr); + + SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + editor = prefs.edit(); + + sampleKeyboard = (ImageView) findViewById(R.id.sample_keyboard); + layoutParams = sampleKeyboard.getLayoutParams(); + currentHeight = KMManager.getKeyboardHeight(context); + refreshSampleKeyboard(context); + + resetButton = (Button) findViewById(R.id.reset_to_defaults); + resetButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + // Clear the keyboard height preferences to reset + editor.remove(KMManager.KMKey_KeyboardHeightPortrait); + editor.remove(KMManager.KMKey_KeyboardHeightLandscape); + editor.commit(); + + // Restore default height + currentHeight = KMManager.getKeyboardHeight(context); + refreshSampleKeyboard(context); + } + }); + + sampleKeyboard.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + int y = (int)event.getY(); + + switch(event.getAction()) { + case MotionEvent.ACTION_MOVE: + // Update currentHeight as the user drags (moves) + // Increasing the keyboard height is a negative y + currentHeight -= y; + + // Apply lower and upper bounds on currentHeight + int defaultHeight = (int) context.getResources().getDimension(R.dimen.keyboard_height); + currentHeight = Math.max(defaultHeight/2, currentHeight); + currentHeight = Math.min(defaultHeight*2, currentHeight); + + refreshSampleKeyboard(context); + break; + case MotionEvent.ACTION_UP: + // Save the currentHeight when the user releases + int orientation = KMManager.getOrientation(context); + String keyboardHeightKey = (orientation == Configuration.ORIENTATION_LANDSCAPE) ? + KMManager.KMKey_KeyboardHeightLandscape : KMManager.KMKey_KeyboardHeightPortrait; + editor.putInt(keyboardHeightKey, currentHeight); + editor.commit(); + break; + } + return true; + } + }); + } + + /** + * Refresh the layout for the sample keyboard + * @param context + */ + private void refreshSampleKeyboard(Context context) { + layoutParams.height = currentHeight; + sampleKeyboard.setLayoutParams(layoutParams); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + layoutParams = sampleKeyboard.getLayoutParams(); + + // When the user rotates the device, restore currentHeight + currentHeight = KMManager.getKeyboardHeight(this); + refreshSampleKeyboard(this); + } + + @Override + public void onBackPressed() { + // Apply the adjusted height on exit + KMManager.applyKeyboardHeight(this, currentHeight); + + super.onBackPressed(); + } + +} \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index 72c54aa0d02..f825909bd2a 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -28,7 +28,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { private static Context context; private Preference languagesPreference, installKeyboardOrDictionary, displayLanguagePreference, - adjustKeyboardHeight; + adjustKeyboardHeight, adjustLongpressDelay; private ListPreference spacebarTextPreference; private CheckBoxPreference setSystemKeyboardPreference; private CheckBoxPreference setDefaultKeyboardPreference; @@ -99,6 +99,7 @@ public boolean onPreferenceClick(Preference preference) { }); setDefaultKeyboardPreference.setOnPreferenceChangeListener(checkBlocker); + adjustKeyboardHeight = new Preference(context); adjustKeyboardHeight.setKey(AdjustKeyboardHeightActivity.adjustKeyboardHeightKey); adjustKeyboardHeight.setTitle(getString(R.string.adjust_keyboard_height)); @@ -106,6 +107,13 @@ public boolean onPreferenceClick(Preference preference) { Intent adjustKeyboardHeightIntent = new Intent(context, AdjustKeyboardHeightActivity.class); adjustKeyboardHeight.setIntent(adjustKeyboardHeightIntent); + adjustLongpressDelay = new Preference(context); + adjustLongpressDelay.setKey(AdjustLongpressDelayActivity.adjustLongpressDelayKey); + adjustLongpressDelay.setTitle(getString(R.string.adjust_longpress_delay)); + adjustLongpressDelay.setWidgetLayoutResource(R.layout.preference_duration_icon_layout); + Intent adjustLongpressDelayIntent = new Intent(context, AdjustLongpressDelayActivity.class); + adjustLongpressDelay.setIntent(adjustLongpressDelayIntent); + /* Spacebar Caption Preference */ spacebarTextPreference = new ListPreference(context); @@ -197,6 +205,7 @@ as part of the default onClick() used by SwitchPreference. screen.addPreference(setDefaultKeyboardPreference); screen.addPreference(adjustKeyboardHeight); + screen.addPreference(adjustLongpressDelay); screen.addPreference(spacebarTextPreference); screen.addPreference(hapticFeedbackPreference); diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml new file mode 100644 index 00000000000..80dc29e33f9 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml new file mode 100644 index 00000000000..c8fd3a48f98 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_duration_icon_layout.xml @@ -0,0 +1,5 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 3cec4f2558c..9f3db9af116 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -113,6 +113,9 @@ Adjust keyboard height + + Adjust longpress delay + Spacebar caption From 24370848c56d3dfe231c5dd58d3aea40035c762d Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 9 Aug 2024 12:55:35 +0700 Subject: [PATCH 047/262] feat(android): Start menu for adjusting longpress delay --- .../kmapro/AdjustLongpressDelayActivity.java | 161 +++++++++--------- .../kmapro/KeymanSettingsFragment.java | 2 - .../activity_adjust_longpress_delay.xml | 61 +++++++ .../kMAPro/src/main/res/values/dimens.xml | 1 + .../kMAPro/src/main/res/values/strings.xml | 11 ++ .../java/com/keyman/engine/KMManager.java | 13 ++ 6 files changed, 166 insertions(+), 83 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index e18912abe9d..c7a8f59aadb 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -5,14 +5,10 @@ import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; +import android.widget.SeekBar; import android.widget.TextView; import androidx.appcompat.app.ActionBar; @@ -24,19 +20,36 @@ /** * Settings menu for adjusting the longpress delay time. The value for the current longpress delay time - * is saved in shared preferences. + * is saved in shared preferences as an integer (milliseconds). */ public class AdjustLongpressDelayActivity extends BaseActivity { private static final String TAG = "AdjustLongpressDelay"; public static final String adjustLongpressDelayKey = "AdjustLongpressDelay"; + private static SharedPreferences.Editor editor = null; - private static Button resetButton = null; - private static ImageView sampleKeyboard = null; + // Keeps track of the adjusted longpress delay time for saving. + // Internally use milliseconds, but GUI displays seconds + private static int currentDelayTime = 500; // ms + private static int minLongpressTime = 300; // ms + private static int maxLongpressTime = 1500; // ms + private static int delayTimeIncrement = 200; // ms - // Keeps track of the adjusted keyboard height for saving - private static SharedPreferences.Editor editor = null; - private static int currentHeight = 0; - private static ViewGroup.LayoutParams layoutParams; + /** + * Convert currentDelayTime to progress + * @return int + */ + private int delayTimeToProgress() { + return (currentDelayTime / delayTimeIncrement) - 1; + } + + /** + * Convert progress to currentDelayTime + * @param progress + * @return int (milliseconds) + */ + private int progressToDelayTime(int progress) { + return (progress + 1) * delayTimeIncrement + 100; + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -44,107 +57,93 @@ protected void onCreate(Bundle savedInstanceState) { final Context context = this; - setContentView(R.layout.activity_adjust_keyboard_height); - + setContentView(R.layout.activity_adjust_longpress_delay); Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setTitle(null); actionBar.setDisplayUseLogoEnabled(false); - actionBar.setDisplayShowHomeEnabled(false); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowCustomEnabled(true); actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.keyman_blue))); } - TextView adjustKeyboardHeightActivityTitle = (TextView) findViewById(R.id.bar_title); - adjustKeyboardHeightActivityTitle.setWidth((int) getResources().getDimension(R.dimen.package_label_width)); + TextView adjustLongpressDelayActivityTitle = (TextView) findViewById(R.id.bar_title); - String titleStr = getString(R.string.adjust_keyboard_height); - adjustKeyboardHeightActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); - adjustKeyboardHeightActivityTitle.setText(titleStr); + String titleStr = getString(R.string.adjust_longpress_delay); + adjustLongpressDelayActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); + adjustLongpressDelayActivityTitle.setText(titleStr); SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); editor = prefs.edit(); + currentDelayTime = KMManager.getLongpressDelay(this); - sampleKeyboard = (ImageView) findViewById(R.id.sample_keyboard); - layoutParams = sampleKeyboard.getLayoutParams(); - currentHeight = KMManager.getKeyboardHeight(context); - refreshSampleKeyboard(context); + TextView adjustLongpressDelayText = (TextView) findViewById(R.id.delayTimeText); + String longpressDelayText = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTime/1000.0)); + adjustLongpressDelayText.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + adjustLongpressDelayText.setText(longpressDelayText); - resetButton = (Button) findViewById(R.id.reset_to_defaults); - resetButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - // Clear the keyboard height preferences to reset - editor.remove(KMManager.KMKey_KeyboardHeightPortrait); - editor.remove(KMManager.KMKey_KeyboardHeightLandscape); - editor.commit(); + final SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar); + seekBar.setProgress(delayTimeToProgress()); + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing + } - // Restore default height - currentHeight = KMManager.getKeyboardHeight(context); - refreshSampleKeyboard(context); + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + currentDelayTime = progressToDelayTime(progress); + String longpressDelayText = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTime/1000.0)); + adjustLongpressDelayText.setText(longpressDelayText); + + editor.putInt(KMManager.KMKey_LongpressDelay, currentDelayTime); + editor.commit(); } }); - sampleKeyboard.setOnTouchListener(new View.OnTouchListener() { + findViewById(R.id.delayTimeDownButton).setOnClickListener(new View.OnClickListener() { @Override - public boolean onTouch(View view, MotionEvent event) { - int y = (int)event.getY(); - - switch(event.getAction()) { - case MotionEvent.ACTION_MOVE: - // Update currentHeight as the user drags (moves) - // Increasing the keyboard height is a negative y - currentHeight -= y; - - // Apply lower and upper bounds on currentHeight - int defaultHeight = (int) context.getResources().getDimension(R.dimen.keyboard_height); - currentHeight = Math.max(defaultHeight/2, currentHeight); - currentHeight = Math.min(defaultHeight*2, currentHeight); - - refreshSampleKeyboard(context); - break; - case MotionEvent.ACTION_UP: - // Save the currentHeight when the user releases - int orientation = KMManager.getOrientation(context); - String keyboardHeightKey = (orientation == Configuration.ORIENTATION_LANDSCAPE) ? - KMManager.KMKey_KeyboardHeightLandscape : KMManager.KMKey_KeyboardHeightPortrait; - editor.putInt(keyboardHeightKey, currentHeight); - editor.commit(); - break; + public void onClick(View v) { + if (currentDelayTime > minLongpressTime) { + currentDelayTime -= delayTimeIncrement; + seekBar.setProgress(delayTimeToProgress()); } - return true; } }); - } - /** - * Refresh the layout for the sample keyboard - * @param context - */ - private void refreshSampleKeyboard(Context context) { - layoutParams.height = currentHeight; - sampleKeyboard.setLayoutParams(layoutParams); + findViewById(R.id.delayTimeUpButton).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (currentDelayTime < maxLongpressTime) { + currentDelayTime += delayTimeIncrement; + seekBar.setProgress(delayTimeToProgress()); + } + } + }); } @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - layoutParams = sampleKeyboard.getLayoutParams(); + public void onBackPressed() { + // Apply the adjusted longpress delay on exit + KMManager.setLongpressDelay(this, currentDelayTime); - // When the user rotates the device, restore currentHeight - currentHeight = KMManager.getKeyboardHeight(this); - refreshSampleKeyboard(this); + super.onBackPressed(); } @Override - public void onBackPressed() { - // Apply the adjusted height on exit - KMManager.applyKeyboardHeight(this, currentHeight); - - super.onBackPressed(); + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; } -} \ No newline at end of file +} diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index f825909bd2a..95bb7774b0b 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -196,8 +196,6 @@ as part of the default onClick() used by SwitchPreference. sendCrashReportPreference.setSummaryOff(getString(R.string.show_send_crash_report_off)); sendCrashReportPreference.setDefaultValue(true); - - screen.addPreference(languagesPreference); screen.addPreference(installKeyboardOrDictionary); screen.addPreference(displayLanguagePreference); diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml new file mode 100644 index 00000000000..ed0bed54a9b --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml index cfcc2f4917f..60e89def04b 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml @@ -23,4 +23,5 @@ 32dp 16dp 0dp + 24sp diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 9f3db9af116..a1f9e7240d7 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -201,6 +201,17 @@ Reset to Defaults + + Delay Time: %1$.1f seconds + + + Delay time longer + + + Delay time shorter + + + Longpress delay time slider Search or type URL diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index aa1c43626a7..645cf5ee0f9 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -248,6 +248,8 @@ public String toString() { public static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait"; public static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape"; + public static final String KMKey_LongpressDelay = "longpressDelay"; + public static final String KMKey_CustomHelpLink = "CustomHelpLink"; public static final String KMKey_KMPLink = "kmp"; public static final String KMKey_UserKeyboardIndex = "UserKeyboardIndex"; @@ -2010,6 +2012,17 @@ public static int getOrientation(Context context) { return Configuration.ORIENTATION_UNDEFINED; } + public static int getLongpressDelay(Context context) { + int defaultDelay = 500; // default longpress delay in KeymanWeb (ms) + SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + + return prefs.getInt(KMManager.KMKey_LongpressDelay, defaultDelay); + } + + public static void setLongpressDelay(Context context, int longpressDelay) { + + } + public static int getBannerHeight(Context context) { int bannerHeight = 0; if (InAppKeyboard != null && InAppKeyboard.getBanner() != BannerType.BLANK) { From 9d058c052ca32b9589b411877eaac62148855153 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 14 Aug 2024 08:22:38 +0700 Subject: [PATCH 048/262] feat(android): Add hooks for KeymanWeb call --- .../kmapro/AdjustLongpressDelayActivity.java | 2 +- .../KMEA/app/src/main/assets/android-host.js | 5 +++++ .../java/com/keyman/engine/KMManager.java | 19 ++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index c7a8f59aadb..590bbdc2673 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -135,7 +135,7 @@ public void onClick(View v) { @Override public void onBackPressed() { // Apply the adjusted longpress delay on exit - KMManager.setLongpressDelay(this, currentDelayTime); + KMManager.setLongpressDelay(currentDelayTime); super.onBackPressed(); } diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 9c786e17770..93ee0e4b0d3 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -112,6 +112,11 @@ function notifyHost(event, params) { }, 10); } +// Update the KeymanWeb longpress delay +// delay is in milliseconds +function setLongpressDelay(delay) { +} + // Update the KMW banner height // h is in dpi (different from iOS) function setBannerHeight(h) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 645cf5ee0f9..7723dcbc0dd 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2012,6 +2012,11 @@ public static int getOrientation(Context context) { return Configuration.ORIENTATION_UNDEFINED; } + /** + * Get the long-press delay (in milliseconds) + * @param context + * @return int - long-press delay in milliseconds + */ public static int getLongpressDelay(Context context) { int defaultDelay = 500; // default longpress delay in KeymanWeb (ms) SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); @@ -2019,8 +2024,20 @@ public static int getLongpressDelay(Context context) { return prefs.getInt(KMManager.KMKey_LongpressDelay, defaultDelay); } - public static void setLongpressDelay(Context context, int longpressDelay) { + /** + * Update KeymanWeb with the long-press delay (in milliseconds) + * @param longpressDelay - int long-press delay in milliseconds + */ + public static void setLongpressDelay(int longpressDelay) { + if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) { + InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); + } + + if (SystemKeyboard != null) { + SystemKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); + } + Log.d(TAG, "setLongpressDelay(" + longpressDelay + ")"); } public static int getBannerHeight(Context context) { From 6a2c15d6a04b297acaa0cf553bcef8a7b21692f6 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 14 Aug 2024 08:24:23 +0700 Subject: [PATCH 049/262] chore(android): Cleanup logging --- android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 7723dcbc0dd..903620e19f4 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2036,8 +2036,6 @@ public static void setLongpressDelay(int longpressDelay) { if (SystemKeyboard != null) { SystemKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); } - - Log.d(TAG, "setLongpressDelay(" + longpressDelay + ")"); } public static int getBannerHeight(Context context) { From d728b33dc629741805d7a77d0363ff7161397bd5 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 14 Aug 2024 13:08:55 +0700 Subject: [PATCH 050/262] fix(android): Pass longpress delay to KeymanWeb --- .../src/main/java/com/keyman/android/SystemKeyboard.java | 6 ++++++ .../tavultesoft/kmapro/AdjustLongpressDelayActivity.java | 2 +- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 8 +++++++- android/KMEA/app/src/main/assets/android-host.js | 4 ++++ .../app/src/main/java/com/keyman/engine/KMManager.java | 8 ++++++-- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index c6885b2b564..eb44e0a3fa4 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -4,6 +4,7 @@ package com.keyman.android; +import com.tavultesoft.kmapro.AdjustLongpressDelayActivity; import com.tavultesoft.kmapro.BuildConfig; import com.tavultesoft.kmapro.DefaultLanguageResource; import com.tavultesoft.kmapro.KeymanSettingsActivity; @@ -79,6 +80,8 @@ public void onCreate() { // Set the system keyboard HTML banner BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); + int longpressDelay = KMManager.getLongpressDelay(this); + KMManager.setLongpressDelay(longpressDelay); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); @@ -244,6 +247,9 @@ public void onKeyboardLoaded(KeyboardType keyboardType) { if (exText != null) exText = null; } + // Initialize the longpress delay + int longpressDelay = KMManager.getLongpressDelay(this); + KMManager.setLongpressDelay(longpressDelay); } @Override diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index 590bbdc2673..214fd370145 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -29,7 +29,7 @@ public class AdjustLongpressDelayActivity extends BaseActivity { // Keeps track of the adjusted longpress delay time for saving. // Internally use milliseconds, but GUI displays seconds - private static int currentDelayTime = 500; // ms + private static int currentDelayTime = KMManager.KMDefault_LongpressDelay; // ms private static int minLongpressTime = 300; // ms private static int maxLongpressTime = 1500; // ms private static int delayTimeIncrement = 200; // ms diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 1e5231f5215..b337d2315d9 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -472,7 +472,7 @@ public boolean onKeyUp(int keycode, KeyEvent e) { @Override public void onKeyboardLoaded(KeyboardType keyboardType) { - // Do nothing + checkLongpressDelay(); } @Override @@ -750,6 +750,12 @@ private void checkSendCrashReport() { KMManager.setMaySendCrashReport(maySendCrashReport); } + private void checkLongpressDelay() { + // Initialize the longpress delay + int longpressDelay = KMManager.getLongpressDelay(this); + KMManager.setLongpressDelay(longpressDelay); + } + private void checkHapticFeedback() { SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 93ee0e4b0d3..b09dbf8beb8 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -115,6 +115,10 @@ function notifyHost(event, params) { // Update the KeymanWeb longpress delay // delay is in milliseconds function setLongpressDelay(delay) { + if (keyman.osk) { + keyman.osk.gestureParams.longpress.waitLength = delay; + console.debug('setLongpressDelay('+delay+')'); + } } // Update the KMW banner height diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 903620e19f4..d4fe9c404c4 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -295,6 +295,9 @@ public String toString() { public static final String KMDefault_DictionaryVersion = "0.1.4"; public static final String KMDefault_DictionaryKMP = KMDefault_DictionaryPackageID + FileUtils.MODELPACKAGE; + // Default KeymanWeb long-press delay + public static final int KMDefault_LongpressDelay = 500; // ms + // Keyman files protected static final String KMFilename_KeyboardHtml = "keyboard.html"; protected static final String KMFilename_JSEngine = "keymanweb-webview.js"; @@ -2018,10 +2021,11 @@ public static int getOrientation(Context context) { * @return int - long-press delay in milliseconds */ public static int getLongpressDelay(Context context) { - int defaultDelay = 500; // default longpress delay in KeymanWeb (ms) SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - return prefs.getInt(KMManager.KMKey_LongpressDelay, defaultDelay); + return prefs.getInt( + KMKey_LongpressDelay, + KMDefault_LongpressDelay); } /** From 6fad148466a4bfc0ae017b53c687408919087246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:22:58 +0000 Subject: [PATCH 051/262] chore(deps): bump semver from 7.5.4 to 7.6.0 Bumps [semver](https://github.com/npm/node-semver) from 7.5.4 to 7.6.0. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v7.5.4...v7.6.0) --- updated-dependencies: - dependency-name: semver dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- developer/src/common/web/utils/package.json | 2 +- developer/src/kmc-ldml/package.json | 2 +- developer/src/server/package.json | 2 +- resources/build/version/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 488051f8f0e..1ab9daddbf8 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -13,7 +13,7 @@ "@keymanapp/common-types": "*", "eventemitter3": "^5.0.0", "restructure": "^3.0.1", - "semver": "^7.5.2", + "semver": "^7.5.4", "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" }, diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index e423b9cd001..6f65eaf0e0a 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -29,7 +29,7 @@ "@keymanapp/developer-utils": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "semver": "^7.5.2" + "semver": "^7.5.4" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", diff --git a/developer/src/server/package.json b/developer/src/server/package.json index b0e438ad15e..f3be3425f2a 100644 --- a/developer/src/server/package.json +++ b/developer/src/server/package.json @@ -18,7 +18,7 @@ "open": "^8.4.0", "restructure": "^3.0.1", "sax": ">=0.6.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "ws": "^8.17.1", "xmlbuilder": "~11.0.0" }, diff --git a/resources/build/version/package.json b/resources/build/version/package.json index da551fef289..ef27eb0fa1d 100644 --- a/resources/build/version/package.json +++ b/resources/build/version/package.json @@ -11,7 +11,7 @@ "devDependencies": { "@types/semver": "^7.1.0", "@types/yargs": "^17.0.26", - "semver": "^7.5.2" + "semver": "^7.5.4" }, "files": [ "src" From f9e7390991d50df2d973959108a3db63c51e6062 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:48:19 +1000 Subject: [PATCH 052/262] fix(windows): add translatable=false flag --- windows/src/desktop/kmshell/xml/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 4cef9daa9ba..2a9ede67453 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -10,7 +10,7 @@ - Keyman + Keyman @@ -860,7 +860,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman @@ -1026,7 +1026,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman From 2938ee110b7a0be8ee7dd5bd6fcc11e662e30afa Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 15 Aug 2024 10:12:27 +0700 Subject: [PATCH 053/262] refactor(web): remove engine/interfaces dependency on engine/js-processor --- package-lock.json | 24 ++++++------ web/README.md | 17 ++++----- web/build.sh | 3 ++ .../prediction/languageProcessor.interface.ts | 2 +- .../src/prediction/predictionContext.ts | 38 +++---------------- web/src/engine/interfaces/tsconfig.json | 1 - .../engine/js-processor/src/outputTarget.ts | 3 +- web/src/engine/keyboard/src/index.ts | 1 + .../main/src/headless/languageProcessor.ts | 1 + web/src/engine/main/src/keymanEngine.ts | 23 ++++++++++- .../prediction/predictionContext.spec.js | 32 ++++++---------- 11 files changed, 67 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae0082784f2..5a3c53081d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -228,18 +228,6 @@ "typescript": "^5.4.5" } }, - "web/src/tools/testing/recorder-core": { - "name": "@keymanapp/recorder-core", - "license": "MIT", - "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "@keymanapp/web-utils": "*" - }, - "devDependencies": { - "typescript": "^5.4.5" - } - }, "common/web/sentry-manager": { "name": "@keymanapp/web-sentry-manager", "license": "MIT", @@ -14544,6 +14532,18 @@ "jsdom": "^23.0.1", "mocha": "^10.0.0" } + }, + "web/src/tools/testing/recorder-core": { + "name": "@keymanapp/recorder-core", + "license": "MIT", + "dependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/models-types": "*", + "@keymanapp/web-utils": "*" + }, + "devDependencies": { + "typescript": "^5.4.5" + } } } } diff --git a/web/README.md b/web/README.md index 7cd9e02f002..b5f0c7c4c4c 100644 --- a/web/README.md +++ b/web/README.md @@ -81,11 +81,11 @@ title: Dependency Graph %%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; OSK["/web/src/engine/osk"]; - KP["/web/src/engine/keyboard"]; + KeyboardSpec["/web/src/engine/keyboard"]; JSProc["/web/src/engine/js-processor"]; - OSK-->KP; + OSK-->KeyboardSpec; WebUtils["@keymanapp/web-utils
(/common/web/utils)"]; - KP---->WebUtils; + KeyboardSpec---->WebUtils; Wordbreakers["@keymanapp/models-wordbreakers
(/common/models/wordbreakers)"]; Models["@keymanapp/models-templates
(/common/models/templates)"]; Models-->WebUtils; @@ -107,15 +107,15 @@ graph TD; subgraph Headless["`**Headless** Fully headless components`"] direction LR - KP; - JSProc-->KP; + KeyboardSpec; + JSProc-->KeyboardSpec; WebUtils; PredText; Gestures; end subgraph ClassicWeb["`**ClassicWeb** - Previously unmodularized components`"] + Intermediate-level engine modules`"] Device["/web/src/engine/device-detect"]; Device----->WebUtils; Elements["/web/src/engine/element-wrappers"]; @@ -124,12 +124,11 @@ graph TD; KeyboardCache-->Interfaces; DomUtils["/web/src/engine/dom-utils"]; DomUtils-->WebUtils; - DomUtils-->KP; + DomUtils-->KeyboardSpec; OSK-->DomUtils; OSK-->Gestures; Interfaces["/web/src/engine/interfaces"]; - Interfaces-->KP; - Interfaces-->JSProc; + Interfaces-->KeyboardSpec; OSK-->Interfaces; CommonEngine["/web/src/engine/main"]; CommonEngine-->Device; diff --git a/web/build.sh b/web/build.sh index c3e17e9d433..50471db85f0 100755 --- a/web/build.sh +++ b/web/build.sh @@ -141,6 +141,9 @@ coverage_action() { builder_run_child_actions build:engine/device-detect builder_run_child_actions build:engine/dom-utils + +builder_run_child_actions build:engine/keyboard +builder_run_child_actions build:engine/js-processor builder_run_child_actions build:engine/element-wrappers builder_run_child_actions build:engine/events builder_run_child_actions build:engine/interfaces diff --git a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts index df5ebae8968..84e78519879 100644 --- a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts +++ b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts @@ -1,7 +1,7 @@ /// import { EventEmitter } from "eventemitter3"; -import { OutputTarget } from "keyman/engine/js-processor"; +import { OutputTarget } from "keyman/engine/keyboard"; export class ReadySuggestions { suggestions: Suggestion[]; diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index 221875e62cb..cd856153b23 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { type LanguageProcessorSpec , ReadySuggestions, type InvalidateSourceEnum, StateChangeHandler } from './languageProcessor.interface.js'; -import { type KeyboardProcessor, type OutputTarget } from 'keyman/engine/js-processor'; +import { type OutputTarget } from "keyman/engine/keyboard"; interface PredictionContextEventMap { update: (suggestions: Suggestion[]) => void; @@ -32,7 +32,7 @@ export default class PredictionContext extends EventEmitter string; /** * Represents the active context used when requesting and applying predictive-text operations. @@ -59,27 +59,18 @@ export default class PredictionContext extends EventEmitter Promise; private readonly suggestionReverter: (reversion: Reversion) => void; - /** - * Handler for post-processing once a suggestion has been applied: calls - * into the active keyboard's `begin postKeystroke` entry point. - * - * Called after the suggestion is applied but _before_ new predictions are - * requested based on the resulting context. - */ - private readonly postApplicationHandler: () => void; - - public constructor(langProcessor: LanguageProcessorSpec, kbdProcessor: KeyboardProcessor) { + public constructor(langProcessor: LanguageProcessorSpec, getLayerId: () => string) { super(); this.langProcessor = langProcessor; - this.kbdProcessor = kbdProcessor; + this.getLayerId = getLayerId; const validSuggestionState: () => boolean = () => this.currentTarget && langProcessor.state == 'configured'; this.suggestionApplier = (suggestion) => { if(validSuggestionState()) { - return langProcessor.applySuggestion(suggestion, this.currentTarget, () => kbdProcessor.layerId); + return langProcessor.applySuggestion(suggestion, this.currentTarget, getLayerId); } else { return null; } @@ -94,19 +85,6 @@ export default class PredictionContext extends EventEmitter { - // Tell the keyboard that the current layer has not changed - kbdProcessor.newLayerStore.set(''); - kbdProcessor.oldLayerStore.set(''); - // Call the keyboard's entry point. - kbdProcessor.processPostKeystroke(kbdProcessor.contextDevice, this.currentTarget) - // If we have a RuleBehavior as a result, run it on the target. This should - // only change system store and variable store values. - ?.finalize(kbdProcessor, this.currentTarget, true); - }; - this.connect(); } @@ -116,8 +94,6 @@ export default class PredictionContext extends EventEmitter; -export default abstract class OutputTarget { +export default abstract class OutputTarget implements OutputTargetInterface { private _dks: DeadkeyTracker; constructor() { diff --git a/web/src/engine/keyboard/src/index.ts b/web/src/engine/keyboard/src/index.ts index f5a4edea71d..28b6331c219 100644 --- a/web/src/engine/keyboard/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -31,6 +31,7 @@ export * from "./defaultRules.js"; export { default as KeyEvent } from "./keyEvent.js"; export * from "./keyEvent.js"; export { default as KeyMapping } from "./keyMapping.js"; +export { OutputTarget } from "./outputTarget.interface.js"; export * from "@keymanapp/web-utils"; diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index c1c1721484b..cd39afc672b 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -336,6 +336,7 @@ export class LanguageProcessor extends EventEmitter { public shutdown() { this.lmEngine.shutdown(); + this.removeAllListeners(); } public get isActive(): boolean { diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index b91920e70d3..cb4c6305938 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -1,5 +1,5 @@ import { type KeyEvent, type Keyboard, KeyboardKeymanGlobal } from "keyman/engine/keyboard"; -import { ProcessorInitOptions, RuleBehavior } from 'keyman/engine/js-processor'; +import { OutputTarget, ProcessorInitOptions, RuleBehavior } from 'keyman/engine/js-processor'; import { DOMKeyboardLoader as KeyboardLoader } from "keyman/engine/keyboard/dom-keyboard-loader"; import { InputProcessor } from './headless/inputProcessor.js'; import { OSKView } from "keyman/engine/osk"; @@ -241,6 +241,8 @@ export default class KeymanEngine< this.modelCache = new ModelCache(); const kbdCache = this.keyboardRequisitioner.cache; + const keyboardProcessor = this.core.keyboardProcessor; + const predictionContext = new PredictionContext(this.core.languageProcessor, () => keyboardProcessor.layerId); this.contextManager.configure({ resetContext: (target) => { // Could reset the target's deadkeys here, but it's really more of a 'core' task. @@ -253,10 +255,27 @@ export default class KeymanEngine< this.core.resetContext(target); } }, - predictionContext: new PredictionContext(this.core.languageProcessor, this.core.keyboardProcessor), + predictionContext: predictionContext, keyboardCache: this.keyboardRequisitioner.cache }); + /* + * Handler for post-processing once a suggestion has been applied. + * + * This is called after the suggestion is applied but _before_ new + * predictions are requested based on the resulting context. + */ + this.core.languageProcessor.on('suggestionapplied', () => { + // Tell the keyboard that the current layer has not changed + keyboardProcessor.newLayerStore.set(''); + keyboardProcessor.oldLayerStore.set(''); + // Call the keyboard's entry point. + keyboardProcessor.processPostKeystroke(keyboardProcessor.contextDevice, predictionContext.currentTarget as OutputTarget) + // If we have a RuleBehavior as a result, run it on the target. This should + // only change system store and variable store values. + ?.finalize(keyboardProcessor, predictionContext.currentTarget as OutputTarget, true); + }); + // #region Event handler wiring this.config.on('spacebartext', () => { // On change of spacebar-text mode, we currently need a layout refresh to update the diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js index dbc24cad4ff..fe66d0904d7 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js @@ -5,7 +5,7 @@ import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { PredictionContext } from 'keyman/engine/interfaces'; import { Worker as LMWorker } from "@keymanapp/lexical-model-layer/node"; import { DeviceSpec } from 'keyman/engine/keyboard'; -import { KeyboardProcessor, Mock } from 'keyman/engine/js-processor'; +import { Mock } from 'keyman/engine/js-processor'; function compileDummyModel(suggestionSets) { return ` @@ -15,14 +15,6 @@ LMLayerWorker.loadModel(new models.DummyModel({ `; } -// Common spec used for each test's setup. It's actually irrelevant for the tests, -// but KeyboardProcessor needs an instance. -const deviceSpec = new DeviceSpec( - DeviceSpec.Browser.Chrome, - DeviceSpec.FormFactor.Desktop, - DeviceSpec.OperatingSystem.Windows -); - const appleDummySuggestionSets = [[ // Set 1: { @@ -67,6 +59,10 @@ const appleDummyModel = { code: compileDummyModel(appleDummySuggestionSets) }; +function dummiedGetLayer() { + return 'default'; +} + describe("PredictionContext", () => { let worker; @@ -82,8 +78,7 @@ describe("PredictionContext", () => { const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. - const kbdProcessor = new KeyboardProcessor(deviceSpec); - const predictiveContext = new PredictionContext(langProcessor, kbdProcessor); + const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); let updateFake = sinon.fake(); predictiveContext.on('update', updateFake); @@ -108,7 +103,7 @@ describe("PredictionContext", () => { mock.insertTextBeforeCaret('e'); // appl| + e = apple let transcription = mock.buildTranscriptionFrom(initialMock, null, true); - await langProcessor.predict(transcription, kbdProcessor.layerId); + await langProcessor.predict(transcription, dummiedGetLayer()); // First predict call results: our second set of dummy suggestions, the first of which includes // a 'keep' of the original text. @@ -123,8 +118,7 @@ describe("PredictionContext", () => { const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. - const kbdProcessor = new KeyboardProcessor(deviceSpec); - const predictiveContext = new PredictionContext(langProcessor, kbdProcessor); + const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); let mock = new Mock("appl", 4); // "appl|", with '|' as the caret position. const initialSuggestions = await predictiveContext.setCurrentTarget(mock); @@ -149,8 +143,7 @@ describe("PredictionContext", () => { const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. - const kbdProcessor = new KeyboardProcessor(deviceSpec); - const predictiveContext = new PredictionContext(langProcessor, kbdProcessor); + const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); let textState = new Mock("appl", 4); // "appl|", with '|' as the caret position. @@ -164,7 +157,7 @@ describe("PredictionContext", () => { let previousTextState = Mock.from(textState); textState.insertTextBeforeCaret('e'); // appl| + e = apple let transcription = textState.buildTranscriptionFrom(previousTextState, null, true); - await langProcessor.predict(transcription, kbdProcessor.layerId); + await langProcessor.predict(transcription, dummiedGetLayer()); // Verify setup. assert.equal(updateFake.callCount, 1); @@ -217,8 +210,7 @@ describe("PredictionContext", () => { const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. - const kbdProcessor = new KeyboardProcessor(deviceSpec); - const predictiveContext = new PredictionContext(langProcessor, kbdProcessor); + const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); let textState = new Mock("appl", 4); // "appl|", with '|' as the caret position. @@ -233,7 +225,7 @@ describe("PredictionContext", () => { let suggestionCaptureFake = sinon.fake(); predictiveContext.once('update', suggestionCaptureFake); - await langProcessor.predict(transcription, kbdProcessor.layerId); + await langProcessor.predict(transcription, dummiedGetLayer()); // We need to capture the suggestion we wish to apply. We could hardcode a forced // value, but that might become brittle in the long-term. From de0b2b54664f33727fe1fffb55fb364d18010e5c Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:32:50 +1000 Subject: [PATCH 054/262] fix(windows): remove incorrect translatable false tag --- windows/src/desktop/kmshell/xml/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 2a9ede67453..774a4d5e425 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -10,7 +10,7 @@ - Keyman + Keyman From 9c9d10d23e456c5c7b725be067e9ab65f6701b9e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 15 Aug 2024 10:35:04 +0700 Subject: [PATCH 055/262] refactor(android): Split setLongpressDelay and applyLongpressDelay --- .../com/keyman/android/SystemKeyboard.java | 6 +-- .../kmapro/AdjustLongpressDelayActivity.java | 11 ++--- .../com/tavultesoft/kmapro/MainActivity.java | 4 +- .../java/com/keyman/engine/KMManager.java | 41 +++++++++++++------ 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index eb44e0a3fa4..dea132c0fd3 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -80,8 +80,6 @@ public void onCreate() { // Set the system keyboard HTML banner BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); - int longpressDelay = KMManager.getLongpressDelay(this); - KMManager.setLongpressDelay(longpressDelay); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); @@ -248,8 +246,8 @@ public void onKeyboardLoaded(KeyboardType keyboardType) { exText = null; } // Initialize the longpress delay - int longpressDelay = KMManager.getLongpressDelay(this); - KMManager.setLongpressDelay(longpressDelay); + int longpressDelay = KMManager.getLongpressDelay(); + KMManager.applyLongpressDelay(longpressDelay); } @Override diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index 214fd370145..55a663965c5 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -25,7 +25,6 @@ public class AdjustLongpressDelayActivity extends BaseActivity { private static final String TAG = "AdjustLongpressDelay"; public static final String adjustLongpressDelayKey = "AdjustLongpressDelay"; - private static SharedPreferences.Editor editor = null; // Keeps track of the adjusted longpress delay time for saving. // Internally use milliseconds, but GUI displays seconds @@ -77,9 +76,7 @@ protected void onCreate(Bundle savedInstanceState) { adjustLongpressDelayActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); adjustLongpressDelayActivityTitle.setText(titleStr); - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - editor = prefs.edit(); - currentDelayTime = KMManager.getLongpressDelay(this); + currentDelayTime = KMManager.getLongpressDelay(); TextView adjustLongpressDelayText = (TextView) findViewById(R.id.delayTimeText); String longpressDelayText = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTime/1000.0)); @@ -106,8 +103,7 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { String longpressDelayText = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTime/1000.0)); adjustLongpressDelayText.setText(longpressDelayText); - editor.putInt(KMManager.KMKey_LongpressDelay, currentDelayTime); - editor.commit(); + KMManager.setLongpressDelay(currentDelayTime); } }); @@ -134,7 +130,8 @@ public void onClick(View v) { @Override public void onBackPressed() { - // Apply the adjusted longpress delay on exit + // setLongpressDelay stores the longpress delay as a preference + // and then updates KeymanWeb with the longpress delay KMManager.setLongpressDelay(currentDelayTime); super.onBackPressed(); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index b337d2315d9..fe3dc9b6c8a 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -752,8 +752,8 @@ private void checkSendCrashReport() { private void checkLongpressDelay() { // Initialize the longpress delay - int longpressDelay = KMManager.getLongpressDelay(this); - KMManager.setLongpressDelay(longpressDelay); + int longpressDelay = KMManager.getLongpressDelay(); + KMManager.applyLongpressDelay(longpressDelay); } private void checkHapticFeedback() { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index d4fe9c404c4..348e3136dd3 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -295,8 +295,8 @@ public String toString() { public static final String KMDefault_DictionaryVersion = "0.1.4"; public static final String KMDefault_DictionaryKMP = KMDefault_DictionaryPackageID + FileUtils.MODELPACKAGE; - // Default KeymanWeb long-press delay - public static final int KMDefault_LongpressDelay = 500; // ms + // Default KeymanWeb longpress delay in milliseconds + public static final int KMDefault_LongpressDelay = 500; // Keyman files protected static final String KMFilename_KeyboardHtml = "keyboard.html"; @@ -2016,23 +2016,23 @@ public static int getOrientation(Context context) { } /** - * Get the long-press delay (in milliseconds) - * @param context - * @return int - long-press delay in milliseconds + * Get the longpress delay (in milliseconds) from stored preference. Defaults to 500ms + * @return int - longpress delay in milliseconds */ - public static int getLongpressDelay(Context context) { - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + public static int getLongpressDelay() { + SharedPreferences prefs = appContext.getSharedPreferences( + appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - return prefs.getInt( - KMKey_LongpressDelay, - KMDefault_LongpressDelay); + return prefs.getInt(KMKey_LongpressDelay, KMDefault_LongpressDelay); } /** - * Update KeymanWeb with the long-press delay (in milliseconds) - * @param longpressDelay - int long-press delay in milliseconds + * Set the number of milliseconds to trigger a longpress gesture. + * + * This method requires a keyboard to be loaded for the value to take effect. + * @param longpressDelay - int longpress delay in milliseconds */ - public static void setLongpressDelay(int longpressDelay) { + public static void applyLongpressDelay(int longpressDelay) { if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) { InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); } @@ -2042,6 +2042,21 @@ public static void setLongpressDelay(int longpressDelay) { } } + /** + * Store the long-press delay (in milliseconds) as a preference. + * Then update KeymanWeb with the longpress delay + * @param longpressDelay - int longpress delay in milliseconds + */ + public static void setLongpressDelay(int longpressDelay) { + SharedPreferences prefs = appContext.getSharedPreferences( + appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(KMKey_LongpressDelay, longpressDelay); + editor.commit(); + + applyLongpressDelay(longpressDelay); + } + public static int getBannerHeight(Context context) { int bannerHeight = 0; if (InAppKeyboard != null && InAppKeyboard.getBanner() != BannerType.BLANK) { From 2d59ee8a946aa1f4aad818b78b016b01e72d51ab Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 15 Aug 2024 13:18:05 +0700 Subject: [PATCH 056/262] change(ios): defer registration of fonts past initialization --- ios/engine/KMEI/KeymanEngine/Classes/Manager.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 5b77f79307e..d1d134b2494 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -831,12 +831,22 @@ public class Manager: NSObject, UIGestureRecognizerDelegate { shared.copyUserDefaults(to: nonShared, withKeys: keysToCopy, shouldOverwrite: true) do { try shared.copyFiles(to: nonShared) - FontManager.shared.registerCustomFonts() } catch { let message = ("Failed to copy from shared container: \(error)") os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message) SentryManager.capture(error, message:message) } + + // This operation has a surprisingly high cost and isn't critical + // for getting the keyboard loaded and available to the OS. + // + // We certainly want it done _soon_... but we don't want it to cause + // the primary, synchronous initialization call from the OS to run so + // long that the keyboard fails to start due to its enforced time + // constraints. + DispatchQueue.main.async { + FontManager.shared.registerCustomFonts() + } } } From 686cfe04b13e1f0c7de5c9b25ed0c2ade75f2fa5 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 15 Aug 2024 15:00:00 +0700 Subject: [PATCH 057/262] fix(android): Advance cursor on newline --- .../src/main/java/com/keyman/engine/KMKeyboardJSHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java index 3a995560c6f..d403a29235b 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java @@ -189,8 +189,8 @@ public void run() { // Messaging apps case NEWLINE : - // Send newline without advancing cursor - ic.commitText("\n", 0); + // Send newline and advance cursor + ic.commitText("\n", 1); break; // Default ENTER action From b1685f5865e5be975843c92748ad56fdb73e2af5 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Thu, 15 Aug 2024 15:41:38 +0700 Subject: [PATCH 058/262] change(mac): use partial paths in settings converts partial paths to full paths as needed and vice-versa when moving between file system access and referencing keyboards in UserDefaults --- .../KMConfigurationWindowController.m | 25 +++-- .../Keyman4MacIM/KMDataRepository.h | 4 +- .../Keyman4MacIM/KMDataRepository.m | 32 +++++- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 100 +++++++++++------- .../Keyman4MacIM/KMSettingsRepository.m | 1 - 5 files changed, 111 insertions(+), 51 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 3e2ffe24100..efafbed11a3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -8,6 +8,7 @@ #import "KMConfigurationWindowController.h" #import "KMDownloadKBWindowController.h" +#import "KMDataRepository.h" #import "KMLogs.h" @interface KMConfigurationWindowController () @@ -134,8 +135,10 @@ - (NSArray *)tableContents { NSArray *pArray = (NSArray *)obj; NSString *packageFolder = [self packageFolderFromPath:[pArray objectAtIndex:0]]; NSString *packageName = [self.AppDelegate packageNameFromPackageInfo:packageFolder]; + os_log_debug([KMLogs uiLog], "tableContents, packageFolder: %{public}@, packageName: %{public}@", packageFolder, packageName); [_tableContents addObject:[NSDictionary dictionaryWithObjectsAndKeys:packageName, @"HeaderTitle", nil]]; for (NSString *path in pArray) { + os_log_debug([KMLogs uiLog], "tableContents, path = '%{public}@'", path); NSDictionary *info = [KMXFile keyboardInfoFromKmxFile:path]; if (!info) { info = [[NSDictionary alloc] initWithObjectsAndKeys: @@ -150,6 +153,7 @@ - (NSArray *)tableContents { } else { NSString *path = (NSString *)obj; + os_log_debug([KMLogs uiLog], "tableContents, path = '%{public}@'", path); NSDictionary *info = [KMXFile keyboardInfoFromKmxFile:path]; if (!info) { info = [[NSDictionary alloc] initWithObjectsAndKeys: @@ -241,22 +245,27 @@ - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn BOOL isHeader = (headerTitle != nil); BOOL isOthers = NO; NSString *kmxFilePath = [self kmxFilePathAtIndex:row]; - if (kmxFilePath != nil) + if (kmxFilePath != nil) { isOthers = [[self packageFolderFromPath:kmxFilePath] isEqualToString:@"Others"]; - else if (isHeader && [headerTitle isEqualToString:@"Others"]) + } + else if (isHeader && [headerTitle isEqualToString:@"Others"]) { isOthers = YES; - + } if ([identifier isEqualToString:@"Column1"]) { KMConfigColumn1CellView *cellView = [tableView makeViewWithIdentifier:identifier owner:self]; - if (isHeader) + if (isHeader) { [cellView setHidden:YES]; + } else { [cellView setHidden:NO]; cellView.imageView.objectValue = [info objectForKey:kKMKeyboardIconKey]; [cellView.checkBox setTag:row]; [cellView.checkBox setAction:@selector(checkBoxAction:)]; - [cellView.checkBox setState:([self.activeKeyboards containsObject:[self kmxFilePathAtIndex:row]])?NSOnState:NSOffState]; + NSString *kmxFilePath = [self kmxFilePathAtIndex:row]; + NSString *partialPath = [KMDataRepository.shared trimToPartialPath:kmxFilePath]; + os_log_debug([KMLogs uiLog], "tableView:viewForTableColumn, kmxFilePath = %{public}@ for row %li, partialPath = %{public}@", kmxFilePath, (long)row, partialPath); + [cellView.checkBox setState:([self.activeKeyboards containsObject:partialPath])?NSOnState:NSOffState]; } return cellView; @@ -357,14 +366,16 @@ - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id)info r - (void)checkBoxAction:(id)sender { NSButton *checkBox = (NSButton *)sender; NSString *kmxFilePath = [self kmxFilePathAtIndex:checkBox.tag]; + NSString *partialPath = [KMDataRepository.shared trimToPartialPath:kmxFilePath]; + os_log_debug([KMLogs uiLog], "checkBoxAction, kmxFilePath = %{public}@ for checkBox.tag %li, partialPath = %{public}@", kmxFilePath, checkBox.tag, partialPath); if (checkBox.state == NSOnState) { os_log_debug([KMLogs uiLog], "Adding active keyboard: %{public}@", kmxFilePath); - [self.activeKeyboards addObject:kmxFilePath]; + [self.activeKeyboards addObject:partialPath]; [self saveActiveKeyboards]; } else if (checkBox.state == NSOffState) { os_log_debug([KMLogs uiLog], "Disabling active keyboard: %{public}@", kmxFilePath); - [self.activeKeyboards removeObject:kmxFilePath]; + [self.activeKeyboards removeObject:partialPath]; [self saveActiveKeyboards]; } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 766e9c10e95..8f38c282fa5 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -20,7 +20,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; - (BOOL)migrateData; -- (NSString*)buildFullPathWith:(NSString *)partialPath; +- (NSString*)buildFullPath:(NSString *)fromPartialPath; +- (NSString*)trimToPartialPath:(NSString *)fromFullPath; +- (NSString *)buildPartialPathFrom:(NSString *)keyboardSubdirectory keyboardFile:(NSString *)kmxFilename; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 0b3b8023e65..59bac96bb4d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -182,9 +182,35 @@ - (BOOL)migrateData { return didMoveData; } -- (NSString*)buildFullPathWith:(NSString *)partialPath { - NSString *fullPath = [self.keymanKeyboardsDirectory.path stringByAppendingString:partialPath]; - os_log_debug([KMLogs dataLog], "createFullPathWith: '%{public}@' with partialPath '%{public}@'", self.keymanKeyboardsDirectory.path, partialPath); +- (NSString*)buildFullPath:(NSString *)fromPartialPath { + NSString *fullPath = [self.keymanKeyboardsDirectory.path stringByAppendingString:fromPartialPath]; + os_log_debug([KMLogs dataLog], "buildFullPath: '%{public}@' fromPartialPath '%{public}@'", + fullPath, fromPartialPath); return fullPath; } + +- (NSString *)trimToPartialPath:(NSString *)fromFullPath { + NSString *partialPath = fromFullPath; + if(fromFullPath != nil) { + NSRange range = [fromFullPath rangeOfString:kKeyboardsDirectoryName]; + if (range.length > 0) { + partialPath = [fromFullPath substringFromIndex:range.location + range.length]; + os_log_debug([KMLogs dataLog], "trimToPartialPath: fromFullPath: '%{public}@' to partialPath: '%{public}@'", fromFullPath, partialPath); + } + } + return partialPath; +} + +- (NSString *)buildPartialPathFrom:(NSString *)keyboardSubdirectory keyboardFile:(NSString *)kmxFilename { + NSMutableArray *pathComponents = [[NSMutableArray alloc] initWithCapacity:0]; + [pathComponents addObject:@"/"]; + [pathComponents addObject:keyboardSubdirectory]; + [pathComponents addObject:kmxFilename]; + NSString *keyboardPartialPath = [NSString pathWithComponents:pathComponents]; + os_log_debug([KMLogs keyboardLog], "buildPartialPathFrom, keyboardSubdirectory: %{public}@, kmxFileName: %{public}@, keyboardPartialPath : %{public}@", + keyboardSubdirectory, kmxFilename, keyboardPartialPath); + return keyboardPartialPath; +} + + @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 845e1f2140a..947bb803f13 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -169,12 +169,9 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }]; [KMLogs reportLogStatus]; - [self startSentry]; - [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; - [self setPostLaunchKeymanSentryTags]; // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; } @@ -544,12 +541,13 @@ - (NSString *)keyboardsPath { } - (NSArray *)kmxFileList { - os_log_debug([KMLogs dataLog], "kmxFileList"); if (_kmxFileList == nil) { - NSArray *kmxFiles = [self KMXFiles]; + os_log_debug([KMLogs dataLog], "creating kmxFileList"); + NSArray *kmxFiles = [self getKmxFilesInKeyboardsDirectory]; _kmxFileList = [[NSMutableArray alloc] initWithCapacity:0]; NSMutableArray *others = nil; for (NSString *filePath in kmxFiles) { + os_log_debug([KMLogs dataLog], "kmxFileList, filePath: %{public}@", filePath); NSString *packageFolder = [self packageFolderFromPath:filePath]; NSInteger index = [self indexForPackageFolder:packageFolder]; if ([packageFolder isEqualToString:@"Others"]) { @@ -666,7 +664,7 @@ - (NSString *)packageNameFromPackageInfo:(NSString *)packageFolder { - (NSArray *)keyboardNamesFromFolder:(NSString *)packageFolder { os_log_debug([KMLogs dataLog], "keyboardNamesFromFolder, folder = %{public}@", packageFolder); NSMutableArray *kbNames = [[NSMutableArray alloc] initWithCapacity:0];; - for (NSString *kmxFile in [self KMXFilesAtPath:packageFolder]) { + for (NSString *kmxFile in [self getKmxFilesAtPath:packageFolder]) { NSDictionary * infoDict = [KMXFile keyboardInfoFromKmxFile:kmxFile]; if (infoDict != nil) { NSString *name = [infoDict objectForKey:kKMKeyboardNameKey]; @@ -684,7 +682,8 @@ - (NSString *)selectedKeyboard { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; _selectedKeyboard = [userData objectForKey:kKMSelectedKeyboardKey]; } - + os_log_debug([KMLogs dataLog], "selectedKeyboard = %{public}@", _selectedKeyboard); + return _selectedKeyboard; } @@ -697,16 +696,20 @@ - (void)setSelectedKeyboard:(NSString *)selectedKeyboard { - (NSMutableArray *)activeKeyboards { if (!_activeKeyboards) { + os_log_debug([KMLogs dataLog], "initializing activeKeyboards"); NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; _activeKeyboards = [[userData arrayForKey:kKMActiveKeyboardsKey] mutableCopy]; - if (!_activeKeyboards) - _activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + if (!_activeKeyboards) { + os_log_debug([KMLogs dataLog], "KMActiveKeyboardsKey key not found in NSUserDefualts"); + _activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + } } - + return _activeKeyboards; } - (void)saveActiveKeyboards { + os_log_debug([KMLogs dataLog], "saveActiveKeyboards, entering"); NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; [userData setObject:_activeKeyboards forKey:kKMActiveKeyboardsKey]; [userData synchronize]; @@ -725,8 +728,12 @@ - (void)resetActiveKeyboards { // Remove entries with missing files NSMutableArray *pathsToRemove = [[NSMutableArray alloc] initWithCapacity:0]; for (NSString *path in self.activeKeyboards) { - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) + NSString *fullPath = [KMDataRepository.shared buildFullPath:path]; + os_log_debug([KMLogs dataLog], "resetActiveKeyboards, checking fullPath: '%{public}@' for path: '%{public}@'", fullPath, path); + if (![[NSFileManager defaultManager] fileExistsAtPath:fullPath]) { + os_log_debug([KMLogs dataLog], "resetActiveKeyboards, need to remove non-existent path: %{public}@", fullPath); [pathsToRemove addObject:path]; + } } BOOL found = FALSE; @@ -814,6 +821,7 @@ - (void)updateKeyboardMenuItems { } - (int)calculateNumberOfKeyboardMenuItems { + os_log_debug([KMLogs uiLog], "calculateNumberOfKeyboardMenuItems, entered"); if (self.activeKeyboards.count == 0) { // if there are no active keyboards, then we will insert one placeholder menu item 'No Active Keyboards' return 1; @@ -835,6 +843,7 @@ - (void)removeDynamicKeyboardMenuItems { } - (void)addDynamicKeyboardMenuItems { + os_log_debug([KMLogs startupLog], "addDynamicKeyboardMenuItems, entered"); BOOL didSetSelectedKeyboard = NO; NSInteger itag = KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG; NSString *keyboardMenuName = @""; @@ -846,7 +855,9 @@ - (void)addDynamicKeyboardMenuItems { // loop through the active keyboards list and add them to the menu for (NSString *path in self.activeKeyboards) { - NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:path]; + NSString *fullPath = [KMDataRepository.shared buildFullPath:path]; + os_log_debug([KMLogs dataLog], "addDynamicKeyboardMenuItems, path = '%{public}@', full path = '%{public}@'", path, fullPath); + NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:fullPath]; if (!infoDict) { continue; } @@ -876,10 +887,12 @@ - (void)addDynamicKeyboardMenuItems { - (void) setSelectedKeyboard:(NSString*)keyboardName inMenuItem:(NSMenuItem*) menuItem { KVKFile *kvk = nil; + NSString *fullPath = [KMDataRepository.shared buildFullPath:keyboardName]; + os_log_debug([KMLogs dataLog], "setSelectedKeyboard, keyboardName = '%{public}@', full path = '%{public}@'", keyboardName, fullPath); [menuItem setState:NSOnState]; - KMXFile *kmx = [[KMXFile alloc] initWithFilePath:keyboardName]; + KMXFile *kmx = [[KMXFile alloc] initWithFilePath:fullPath]; [self setKmx:kmx]; - NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:keyboardName]; + NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:fullPath]; NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; if (kvkFilename != nil) { NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename]; @@ -931,10 +944,13 @@ - (void)selectKeyboardFromMenu:(NSInteger)tag { } NSString *path = [self.activeKeyboards objectAtIndex:tag-KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG]; - KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path]; + NSString *fullPath = [KMDataRepository.shared buildFullPath:path]; + os_log_debug([KMLogs dataLog], "setSelectedKeyboard, keyboardName = '%{public}@', full path = '%{public}@'", path, fullPath); + + KMXFile *kmx = [[KMXFile alloc] initWithFilePath:fullPath]; [self setKmx:kmx]; KVKFile *kvk = nil; - NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path]; + NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:fullPath]; NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; if (kvkFilename != nil) { NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename]; @@ -953,12 +969,12 @@ - (void)selectKeyboardFromMenu:(NSInteger)tag { [self showOSK]; } -- (NSArray *)KMXFiles { - return [self KMXFilesAtPath:self.keyboardsPath]; +- (NSArray *)getKmxFilesInKeyboardsDirectory { + return [self getKmxFilesAtPath:self.keyboardsPath]; } -- (NSArray *)KMXFilesAtPath:(NSString *)path { - os_log_debug([KMLogs dataLog], "Reading KMXFiles at path %{public}@", path); +- (NSArray *)getKmxFilesAtPath:(NSString *)path { + os_log_debug([KMLogs dataLog], "getKmxFilesAtPath, path: '%{public}@'", path); NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path]; NSMutableArray *kmxFiles = [[NSMutableArray alloc] initWithCapacity:0]; NSString *filePath; @@ -973,23 +989,23 @@ - (NSArray *)KMXFilesAtPath:(NSString *)path { return kmxFiles; } -- (NSArray *)KVKFiles { +- (NSArray *)getKvkFilesArray { NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:self.keyboardsPath]; - NSMutableArray *kvkFiles = [[NSMutableArray alloc] initWithCapacity:0]; + NSMutableArray *kvkFilesArray = [[NSMutableArray alloc] initWithCapacity:0]; NSString *filePath; while (filePath = (NSString *)[dirEnum nextObject]) { NSString *extension = [[filePath pathExtension] lowercaseString]; if ([extension isEqualToString:@"kvk"]) - [kvkFiles addObject:[self.keyboardsPath stringByAppendingPathComponent:filePath]]; + [kvkFilesArray addObject:[self.keyboardsPath stringByAppendingPathComponent:filePath]]; } - return kvkFiles; + return kvkFilesArray; } - (NSString *)kvkFilePathFromFilename:(NSString *)kvkFilename { NSString *kvkFilePath = nil; - NSArray *kvkFiles = [self KVKFiles]; - for (NSString *filePath in kvkFiles) { + NSArray *kvkFilesArray = [self getKvkFilesArray]; + for (NSString *filePath in kvkFilesArray) { if ([[filePath lastPathComponent] isEqualToString:kvkFilename]) { kvkFilePath = filePath; break; @@ -1338,7 +1354,8 @@ - (BOOL)unzipFile:(NSString *)filePath { NSError *error = nil; NSString *fileName = filePath.lastPathComponent; NSString *folderName = [fileName stringByDeletingPathExtension]; - + os_log_debug([KMLogs keyboardLog], "unzipFile, folderName: %{public}@, fileName: %{public}@", folderName, fileName); + // First we unzip into a temp folder, and check kmp.json for the fileVersion // before we continue installation. We don't want to overwrite existing // package if it is there if the files are not compatible with the installed @@ -1349,9 +1366,9 @@ - (BOOL)unzipFile:(NSString *)filePath { ZipArchive *za = [[ZipArchive alloc] init]; if ([za UnzipOpenFile:filePath]) { - os_log_debug([KMLogs keyboardLog], "Unzipping %{public}@ to %{public}@", filePath, tempDestFolder); + os_log_debug([KMLogs keyboardLog], "unzipFile, Unzipping %{public}@ to %{public}@", filePath, tempDestFolder); if ([[NSFileManager defaultManager] fileExistsAtPath:tempDestFolder]) { - os_log_debug([KMLogs keyboardLog], "The temp destination folder already exists. Overwriting..."); + os_log_debug([KMLogs keyboardLog], "unzipFile, The temp destination folder already exists. Overwriting..."); } didUnzip = [za UnzipFileTo:tempDestFolder overWrite:YES]; @@ -1359,11 +1376,11 @@ - (BOOL)unzipFile:(NSString *)filePath { } if (!didUnzip) { - os_log_error([KMLogs keyboardLog], "Failed to unzip file: %{public}@", filePath); + os_log_error([KMLogs keyboardLog], "unzipFile, Failed to unzip file: %{public}@", filePath); return NO; } - os_log_debug([KMLogs keyboardLog], "Unzipped file: %{public}@", filePath); + os_log_debug([KMLogs keyboardLog], "unzipFile, Unzipped file: %{public}@", filePath); BOOL didInstall = [self verifyPackageVersionInTempFolder:tempDestFolder filePath:filePath]; @@ -1371,10 +1388,10 @@ - (BOOL)unzipFile:(NSString *)filePath { // Remove existing package if it exists if (didInstall && [[NSFileManager defaultManager] fileExistsAtPath:destFolder]) { - os_log_debug([KMLogs keyboardLog], "The destination folder already exists. Overwriting..."); + os_log_debug([KMLogs keyboardLog], "unzipFile, The destination folder already exists. Overwriting..."); [[NSFileManager defaultManager] removeItemAtPath:destFolder error:&error]; if (error != nil) { - os_log_error([KMLogs keyboardLog], "Unable to remove destination folder %{public}@", destFolder); + os_log_error([KMLogs keyboardLog], "unzipFile, Unable to remove destination folder %{public}@", destFolder); didInstall = NO; } } @@ -1387,7 +1404,7 @@ - (BOOL)unzipFile:(NSString *)filePath { if(didInstall) { [[NSFileManager defaultManager] moveItemAtPath:tempDestFolder toPath:destFolder error:&error]; if (error != nil) { - os_log_error([KMLogs keyboardLog], "Unable to move temp folder %{public}@ to dest folder %{public}@", tempDestFolder, destFolder); + os_log_error([KMLogs keyboardLog], "unzipFile, Unable to move temp folder %{public}@ to dest folder %{public}@", tempDestFolder, destFolder); didInstall = NO; } } @@ -1395,7 +1412,7 @@ - (BOOL)unzipFile:(NSString *)filePath { if(!didInstall) { [[NSFileManager defaultManager] removeItemAtPath:tempDestFolder error:&error]; if (error != nil) { - os_log_error([KMLogs keyboardLog], "Unable to remove temp folder %{public}@", tempDestFolder); + os_log_error([KMLogs keyboardLog], "unzipFile, Unable to remove temp folder %{public}@", tempDestFolder); } return NO; @@ -1404,11 +1421,16 @@ - (BOOL)unzipFile:(NSString *)filePath { // Package has installed, now scan for keyboards and fonts // TODO: we need to be reading the kmp.json data to determine keyboards to install NSString * keyboardFolderPath = [self.keyboardsPath stringByAppendingPathComponent:folderName]; + os_log_debug([KMLogs keyboardLog], "unzipFile, folderName: %{public}@, keyboardFolderPath: %{public}@", folderName, keyboardFolderPath); [self installFontsAtPath:keyboardFolderPath]; - for (NSString *kmxFile in [self KMXFilesAtPath:keyboardFolderPath]) { - os_log_debug([KMLogs keyboardLog], "Adding keyboard to list of active keyboards: %{public}@", kmxFile); - if (![self.activeKeyboards containsObject:kmxFile]) - [self.activeKeyboards addObject:kmxFile]; + + for (NSString *kmxFile in [self getKmxFilesAtPath:keyboardFolderPath]) { + NSString *partialPath = [KMDataRepository.shared buildPartialPathFrom:folderName keyboardFile:[kmxFile lastPathComponent]]; + // TODO: encapsulate this in KMSettingsRepository, insertIfNotExists + if (![self.activeKeyboards containsObject:partialPath]) { + os_log_debug([KMLogs keyboardLog], "unzipFile, adding keyboard to list of active keyboards: %{public}@", partialPath); + [self.activeKeyboards addObject:partialPath]; + } } [self saveActiveKeyboards]; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 3109770067d..54c779c00d7 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -130,7 +130,6 @@ - (void)convertSelectedKeyboardPathForMigration { if ([selectedKeyboardPath isNotEqualTo:newPathString]) { [self saveSelectedKeyboard:newPathString]; os_log_debug([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); - os_log_debug([KMLogs dataLog], "full path of selected keyboard from buildFullPathWith = '%{public}@'", [KMDataRepository.shared buildFullPathWith:newPathString]); } } } From c4340542f77648d7072ab2d43d83990456439b34 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 15 Aug 2024 15:54:15 +0700 Subject: [PATCH 059/262] fix(web): patch up newly-merged-in automated test --- .../engine/interfaces/prediction/predictionContext.spec.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js index dc91c512136..4217aeb69b6 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js @@ -118,8 +118,7 @@ describe("PredictionContext", () => { const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. - const kbdProcessor = new KeyboardProcessor(deviceSpec); - const predictiveContext = new PredictionContext(langProcessor, kbdProcessor); + const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer); let updateFake = sinon.fake(); predictiveContext.on('update', updateFake); @@ -146,13 +145,13 @@ describe("PredictionContext", () => { // Mocking: corresponds to the second set of mocked predictions - round 2 of // 'apple', 'apply', 'apples'. - const skippedPromise = langProcessor.predict(baseTranscription, kbdProcessor.layerId); + const skippedPromise = langProcessor.predict(baseTranscription, dummiedGetLayer()); mock.insertTextBeforeCaret('e'); // appl| + e = apple const finalTranscription = mock.buildTranscriptionFrom(initialMock, null, true); // Mocking: corresponds to the third set of mocked predictions - 'applied'. - const expectedPromise = langProcessor.predict(finalTranscription, kbdProcessor.layerId); + const expectedPromise = langProcessor.predict(finalTranscription, dummiedGetLayer()); await Promise.all([skippedPromise, expectedPromise]); const expected = await expectedPromise; From c3e3cbaaee37fcfebc0370250149f50882fa0dd7 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 16 Aug 2024 10:07:49 +0700 Subject: [PATCH 060/262] fix(web): fix malformed reversion display strings Fixes: #12199 --- common/web/lm-worker/src/main/model-compositor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/web/lm-worker/src/main/model-compositor.ts b/common/web/lm-worker/src/main/model-compositor.ts index 39a6add834a..c6b8367b782 100644 --- a/common/web/lm-worker/src/main/model-compositor.ts +++ b/common/web/lm-worker/src/main/model-compositor.ts @@ -225,7 +225,7 @@ export class ModelCompositor { // Handles display string for reversions triggered by accepting a suggestion mid-token. const preCaretToken = postContextTokenization.left[postContextTokenization.left.length - 1]; revertedPrefix = (preCaretToken && !preCaretToken.isWhitespace) ? preCaretToken.text : ''; - revertedPrefix += postContextTokenization.caretSplitsToken ? postContextTokenization.right[0] : ''; + revertedPrefix += postContextTokenization.caretSplitsToken ? postContextTokenization.right[0].text : ''; } else { revertedPrefix = this.wordbreak(postContext); } From 50d0107f38aad117939f7a225186557331816119 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 16 Aug 2024 08:04:18 +0200 Subject: [PATCH 061/262] fix(developer): enforce presence of kps Info.Description field in info compilers The Description field should be required for published keyboards and models, so this change means kmc-keyboard-info and kmc-model-info will report an error if it is missing. Relates-to: keymanapp/keyboards#3037 Relates-to: keymanapp/lexical-models#262 Fixes: #12202 --- .../src/keyboard-info-compiler-messages.ts | 10 ++++++++-- .../kmc-keyboard-info/src/keyboard-info-compiler.ts | 3 +++ .../kmc-model-info/src/model-info-compiler-messages.ts | 5 +++++ .../src/kmc-model-info/src/model-info-compiler.ts | 3 +++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts index 285e272da5b..bf5d989718b 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts @@ -52,8 +52,14 @@ export class KeyboardInfoCompilerMessages { static Error_FontFileCannotBeRead = (o:{filename: string}) => m(this.ERROR_FontFileCannotBeRead, `Font ${def(o.filename)} could not be parsed to extract a font family.`); -static ERROR_FontFileMetaDataIsInvalid = SevError | 0x000F; -static Error_FontFileMetaDataIsInvalid = (o:{filename: string,message:string}) => m(this.ERROR_FontFileMetaDataIsInvalid, + static ERROR_FontFileMetaDataIsInvalid = SevError | 0x000F; + static Error_FontFileMetaDataIsInvalid = (o:{filename: string,message:string}) => m( + this.ERROR_FontFileMetaDataIsInvalid, `Font ${def(o.filename)} meta data invalid: ${def(o.message)}.`); + + static ERROR_DescriptionIsMissing = SevError | 0x0010; + static Error_DescriptionIsMissing = (o:{filename:string}) => m( + this.ERROR_DescriptionIsMissing, + `The Info.Description field in the package ${def(o.filename)} is required, but is missing or empty.`); } diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts index 74699556988..8e89bb81646 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts @@ -251,6 +251,9 @@ export class KeyboardInfoCompiler implements KeymanCompiler { if(kmpJsonData.info.description?.description) { keyboard_info.description = kmpJsonData.info.description.description.trim(); + } else { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_DescriptionIsMissing({filename:sources.kpsFilename})); + return null; } // extract the language identifiers from the language metadata arrays for diff --git a/developer/src/kmc-model-info/src/model-info-compiler-messages.ts b/developer/src/kmc-model-info/src/model-info-compiler-messages.ts index e033d6c021c..cbf6b57eb90 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler-messages.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler-messages.ts @@ -44,5 +44,10 @@ export class ModelInfoCompilerMessages { static ERROR_NoLicenseFound = SevError | 0x0009; static Error_NoLicenseFound = () => m(this.ERROR_NoLicenseFound, `No license for the model was found. MIT license is required for publication to Keyman lexical-models repository.`); + + static ERROR_DescriptionIsMissing = SevError | 0x000A; + static Error_DescriptionIsMissing = (o:{filename:string}) => m( + this.ERROR_DescriptionIsMissing, + `The Info.Description field in the package ${def(o.filename)} is required, but is missing or empty.`); } diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts index d4f3d10d435..a5fb62cbcda 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -204,6 +204,9 @@ export class ModelInfoCompiler implements KeymanCompiler { if(sources.kmpJsonData.info.description?.description) { model_info.description = sources.kmpJsonData.info.description.description.trim(); + } else { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_DescriptionIsMissing({filename:sources.kpsFilename})); + return null; } // isRTL -- this is a little bit of a heuristic from a compiled .js From ba96d26858a257b8fe22b7a2c5b3e3e8591db0cc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 16 Aug 2024 08:24:37 +0200 Subject: [PATCH 062/262] fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler Fixes: #12193 --- .../src/compiler/package-compiler-messages.ts | 6 ++++ .../src/compiler/package-version-validator.ts | 5 +++ .../absolute_path/source/absolute_path.kps | 1 + .../error_package_file_has_empty_version.kps | 32 +++++++++++++++++++ .../src/kmc-package/test/test-messages.ts | 4 +++ .../kmc-package/test/test-package-compiler.ts | 2 ++ 6 files changed, 50 insertions(+) create mode 100644 developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps diff --git a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts index 154cb2143e7..00d8a380b5e 100644 --- a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts +++ b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts @@ -138,5 +138,11 @@ export class PackageCompilerMessages { static ERROR_InvalidAuthorEmail = SevError | 0x0020; static Error_InvalidAuthorEmail = (o:{email:string}) => m(this.ERROR_InvalidAuthorEmail, `Invalid author email: ${def(o.email)}`); + + static ERROR_PackageFileHasEmptyVersion = SevError | 0x0021; + static Error_PackageFileHasEmptyVersion = () => m( + this.ERROR_PackageFileHasEmptyVersion, + `Package version is not following keyboard version, but the package version field is blank.` + ); } diff --git a/developer/src/kmc-package/src/compiler/package-version-validator.ts b/developer/src/kmc-package/src/compiler/package-version-validator.ts index 1aad375d400..b20e086aec7 100644 --- a/developer/src/kmc-package/src/compiler/package-version-validator.ts +++ b/developer/src/kmc-package/src/compiler/package-version-validator.ts @@ -42,6 +42,11 @@ export class PackageVersionValidator { if(!this.checkFollowKeyboardVersion(kmp)) { return false; } + } else { + if(!kmp.info.version) { + this.callbacks.reportMessage(PackageCompilerMessages.Error_PackageFileHasEmptyVersion()); + return false; + } } if(!kmp.keyboards) { diff --git a/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps b/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps index e7c4a8b8512..b5a74c06ee1 100644 --- a/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps +++ b/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps @@ -17,6 +17,7 @@ Absolute Path + 1.0 diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps b/developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps new file mode 100644 index 00000000000..386b4e2e03e --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps @@ -0,0 +1,32 @@ + + + + 15.0.266.0 + 7.0 + + + + Invalid Email Address + © 2019 National Research Council Canada + Eddie Antonio Santos + + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + + Basic + basic + 1.0 + + Central Khmer (Khmer, Cambodia) + + + + diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index e8fdfa842a5..2d8df44eb50 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -236,4 +236,8 @@ describe('PackageCompilerMessages', function () { PackageCompilerMessages.ERROR_InvalidAuthorEmail); }); + it('should generate ERROR_PackageFileHasEmptyVersion if FollowKeyboardVersion is not present and Version is empty', async function() { + await testForMessage(this, ['invalid', 'error_package_file_has_empty_version.kps'], + PackageCompilerMessages.ERROR_PackageFileHasEmptyVersion); + }); }); diff --git a/developer/src/kmc-package/test/test-package-compiler.ts b/developer/src/kmc-package/test/test-package-compiler.ts index 3f0e8d28fd2..7952e2145a2 100644 --- a/developer/src/kmc-package/test/test-package-compiler.ts +++ b/developer/src/kmc-package/test/test-package-compiler.ts @@ -209,6 +209,8 @@ describe('KmpCompiler', function () { kmpJson = kmpCompiler.transformKpsToKmpObject(kpsPath); }); + assert.isNotNull(kmpJson); + await assert.isNull(kmpCompiler.buildKmpFile(kpsPath, kmpJson)); if(debug) callbacks.printMessages(); From 78857d96c9cb5c521d0bb912db99e2d636343dca Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 9 Aug 2024 17:23:55 +0200 Subject: [PATCH 063/262] =?UTF-8?q?refactor(web):=20move=20`lm-message-typ?= =?UTF-8?q?es`=20=E2=86=92=20`predictive-text/types`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves `common/web/lm-message-types/` → `web/src/engine/predictive-text/types/` Fixes: #12147 --- common/web/build.sh | 1 - common/web/lm-message-types/tsconfig.json | 12 ---------- common/web/lm-worker/tsconfig.json | 2 +- package-lock.json | 4 ++-- tsconfig.base.json | 2 +- tsconfig.json | 2 +- .../predictive-text/types}/message.d.ts | 0 .../predictive-text/types}/package.json | 0 .../predictive-text/types/tsconfig.json | 11 +++++++++ .../worker-main/src/tsconfig.json | 2 +- .../worker-main/tsconfig.all.json | 2 +- .../predictive-text/{index.js => index.mjs} | 23 ++++++++----------- .../tools/testing/recorder-core/tsconfig.json | 2 +- web/src/tools/testing/recorder/tsconfig.json | 4 ++-- 14 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 common/web/lm-message-types/tsconfig.json rename {common/web/lm-message-types => web/src/engine/predictive-text/types}/message.d.ts (100%) rename {common/web/lm-message-types => web/src/engine/predictive-text/types}/package.json (100%) create mode 100644 web/src/engine/predictive-text/types/tsconfig.json rename web/src/test/manual/predictive-text/{index.js => index.mjs} (96%) diff --git a/common/web/build.sh b/common/web/build.sh index 554b1e8ebb3..fbc015613fd 100755 --- a/common/web/build.sh +++ b/common/web/build.sh @@ -10,7 +10,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # # TODO: future modules may include -# :lm-message-types \ # :sentry-manager \ # diff --git a/common/web/lm-message-types/tsconfig.json b/common/web/lm-message-types/tsconfig.json deleted file mode 100644 index 559b764980e..00000000000 --- a/common/web/lm-message-types/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "outDir": "build/", - "sourceMap": true, - "lib": ["es6"], - "target": "es6" - }, - "include": ["./*.ts"], - "exclude": ["test.ts"] -} diff --git a/common/web/lm-worker/tsconfig.json b/common/web/lm-worker/tsconfig.json index f439e969409..95b403c768b 100644 --- a/common/web/lm-worker/tsconfig.json +++ b/common/web/lm-worker/tsconfig.json @@ -13,7 +13,7 @@ "references": [ // types { "path": "../../models/types" }, - { "path": "../lm-message-types" }, + { "path": "../../../web/src/engine/predictive-text/types" }, // modules { "path": "../keyman-version" }, { "path": "../utils" }, diff --git a/package-lock.json b/package-lock.json index ad53581202a..99a3d2f934b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -198,7 +198,7 @@ "typescript": "^5.4.5" } }, - "common/web/lm-message-types": { + "web/src/engine/predictive-text/types": { "name": "@keymanapp/lm-message-types", "license": "MIT", "devDependencies": { @@ -2896,7 +2896,7 @@ "link": true }, "node_modules/@keymanapp/lm-message-types": { - "resolved": "common/web/lm-message-types", + "resolved": "web/src/engine/predictive-text/types", "link": true }, "node_modules/@keymanapp/lm-worker": { diff --git a/tsconfig.base.json b/tsconfig.base.json index c4e0599eef6..7f69376372d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,7 +31,7 @@ "@keymanapp/models-templates": ["./common/models/templates"], "@keymanapp/models-wordbreakers": ["./common/models/wordbreakers"], "@keymanapp/web-utils": ["./common/web/utils"], - "@keymanapp/lm-message-types": ["./common/web/lm-message-types"], + "@keymanapp/lm-message-types": ["./web/src/engine/predictive-text/types"], "@keymanapp/keyman-version": ["./common/web/keyman-version"], "@keymanapp/ldml-keyboard-constants": [ "./core/include/ldml" ], } diff --git a/tsconfig.json b/tsconfig.json index 42b3fd1a993..252fd738dc3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,6 @@ { "path": "./common/web/gesture-recognizer/tsconfig.json" }, { "path": "./common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json" }, { "path": "./common/web/keyman-version" }, - { "path": "./common/web/lm-message-types/" }, { "path": "./common/web/lm-worker/" }, { "path": "./common/web/recorder/tsconfig.json" }, { "path": "./common/web/sentry-manager/src/tsconfig.json" }, @@ -42,6 +41,7 @@ { "path": "./resources/build/version/" }, { "path": "./web/src/tsconfig.all.json" }, + { "path": "./web/src/engine/predictive-text/types/" }, { "path": "./web/src/engine/predictive-text/worker-main/tsconfig.all.json" }, // { "path": "./web/tools/recorder/tsconfig.json" }, // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, diff --git a/common/web/lm-message-types/message.d.ts b/web/src/engine/predictive-text/types/message.d.ts similarity index 100% rename from common/web/lm-message-types/message.d.ts rename to web/src/engine/predictive-text/types/message.d.ts diff --git a/common/web/lm-message-types/package.json b/web/src/engine/predictive-text/types/package.json similarity index 100% rename from common/web/lm-message-types/package.json rename to web/src/engine/predictive-text/types/package.json diff --git a/web/src/engine/predictive-text/types/tsconfig.json b/web/src/engine/predictive-text/types/tsconfig.json new file mode 100644 index 00000000000..8b87ca26672 --- /dev/null +++ b/web/src/engine/predictive-text/types/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "build/", + "tsBuildInfoFile": "build/tsconfig.tsbuildinfo", + "rootDir": "./src" + }, + "include": ["./*.ts"], + "exclude": ["test.ts"] +} diff --git a/web/src/engine/predictive-text/worker-main/src/tsconfig.json b/web/src/engine/predictive-text/worker-main/src/tsconfig.json index 9c7a1f47ee8..edf6195b3ab 100644 --- a/web/src/engine/predictive-text/worker-main/src/tsconfig.json +++ b/web/src/engine/predictive-text/worker-main/src/tsconfig.json @@ -10,7 +10,7 @@ "references": [ { "path": "../../../../../../common/web/utils" }, { "path": "../../../../../../common/models/types"}, - { "path": "../../../../../../common/web/lm-message-types"} + { "path": "../../types"} ], "include" : [ "./*.ts" ], "exclude" : [ diff --git a/web/src/engine/predictive-text/worker-main/tsconfig.all.json b/web/src/engine/predictive-text/worker-main/tsconfig.all.json index f86c0815422..722ce0114bd 100644 --- a/web/src/engine/predictive-text/worker-main/tsconfig.all.json +++ b/web/src/engine/predictive-text/worker-main/tsconfig.all.json @@ -15,7 +15,7 @@ "references": [ { "path": "../../../../../common/web/utils" }, { "path": "../../../../../common/models/types"}, - { "path": "../../../../../common/web/lm-message-types"}, + { "path": "../types"}, { "path": "src/node" }, { "path": "src/web" } ], diff --git a/web/src/test/manual/predictive-text/index.js b/web/src/test/manual/predictive-text/index.mjs similarity index 96% rename from web/src/test/manual/predictive-text/index.js rename to web/src/test/manual/predictive-text/index.mjs index 605ed2939ce..33398a69577 100755 --- a/web/src/test/manual/predictive-text/index.js +++ b/web/src/test/manual/predictive-text/index.mjs @@ -5,16 +5,17 @@ * model. */ -const fs = require('fs'); -const path = require('path'); -const readline = require('readline'); +import fs from 'fs'; +import path from 'path'; +import readline from 'readline'; +import vm from 'vm'; -const {EventIterator} = require('event-iterator'); -const program = require('commander'); +import {EventIterator} from 'event-iterator'; +import program from 'commander'; // Load the most recent LMLayer code locally. -const LMLayer = require('../../../engine/predictive-text/worker-main'); - +import { LMLayer } from 'keyman/engine/predictive-text/worker-main'; +import { LMLayerWorker } from '@keymanapp/lm-worker'; ///////////////////////////////// Constants ///////////////////////////////// @@ -22,7 +23,7 @@ const WORKER_DEBUG = false; const EXIT_USAGE = 1; /** "Control sequence introducer" for ANSI escape codes: */ -const CSI = '\033['; +const CSI = '\u001b['; /** * ANSI escape codes to deal with the screen and the cursor. * See https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences @@ -47,8 +48,7 @@ main(); function main() { // Command line options: program - .name(require('./package.json').name) - .version(require('./package.json').version) + .name('lmlayer-cli') .usage('[-i | -p [-p ...]] (-f | )') .description('CLI for trying lexical models.') .arguments('[model-id]') @@ -330,9 +330,6 @@ async function asyncRepl(modelFile) { function createAsyncWorker() { - // XXX: import the LMLayerWorker directly -- I know where it is built. - const LMLayerWorker = require('../../../engine/predictive-text/build/intermediate'); - const vm = require('vm'); let worker = { postMessage(message) { diff --git a/web/src/tools/testing/recorder-core/tsconfig.json b/web/src/tools/testing/recorder-core/tsconfig.json index 920536eeee2..01e14319451 100644 --- a/web/src/tools/testing/recorder-core/tsconfig.json +++ b/web/src/tools/testing/recorder-core/tsconfig.json @@ -16,7 +16,7 @@ "references": [ { "path": "../../../../../common/web/keyman-version" }, { "path": "../../../../../common/web/utils/" }, - { "path": "../../../../../common/web/lm-message-types" }, + { "path": "../../../engine/predictive-text/types" }, { "path": "../../../engine/js-processor" }, { "path": "../../../engine/keyboard" }, ], diff --git a/web/src/tools/testing/recorder/tsconfig.json b/web/src/tools/testing/recorder/tsconfig.json index f5974b5738b..e25a4f60341 100644 --- a/web/src/tools/testing/recorder/tsconfig.json +++ b/web/src/tools/testing/recorder/tsconfig.json @@ -12,8 +12,8 @@ "references": [ { "path": "../../../../../common/web/keyman-version" }, { "path": "../../../../../common/web/utils" }, - { "path": "../../../../../common/web/lm-message-types" }, - { "path": "../../../../../web/src/app/browser" }, + { "path": "../../../engine/predictive-text/types" }, + { "path": "../../../app/browser" }, { "path": "../recorder-core" }, { "path": "../../../engine/keyboard" }, ] From ea62ac23e792b7c16d0377a39de13468724cb7d2 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 9 Aug 2024 18:18:53 +0200 Subject: [PATCH 064/262] =?UTF-8?q?refactor(web):=20move=20`lm-worker`=20?= =?UTF-8?q?=E2=86=92=20`worker-thread`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move `common/web/lm-worker/` → `web/src/engine/predictive-text/worker-thread/`. Fixes: #12148 --- common/web/lm-worker/tsconfig.json | 26 --------------- package-lock.json | 4 +-- tsconfig.json | 2 +- web/README.md | 2 +- web/src/app/browser/build.sh | 2 +- .../predictive-text/worker-main/build.sh | 2 +- .../worker-main/unit_tests/test.sh | 4 +-- .../predictive-text/worker-thread}/.c8rc.json | 0 .../worker-thread}/build-polyfiller.js | 2 +- .../worker-thread}/build-wrapper.js | 0 .../predictive-text/worker-thread}/build.sh | 10 +++--- .../worker-thread}/package.json | 0 .../main/correction/classical-calculation.ts | 0 .../src/main/correction/context-tracker.ts | 0 .../src/main/correction/distance-modeler.ts | 0 .../src/main/correction/execution-timer.ts | 0 .../src/main/correction/index.ts | 0 .../main/correction/transform-tokenization.ts | 0 .../worker-thread}/src/main/index.ts | 0 .../src/main/model-compositor.ts | 0 .../worker-thread}/src/main/model-helpers.ts | 0 .../src/main/models/dummy-model.ts | 0 .../worker-thread}/src/main/models/index.ts | 0 .../src/main/predict-helpers.ts | 0 .../worker-thread}/src/main/transformUtils.ts | 0 .../src/main/worker-interfaces.ts | 0 .../worker-thread}/src/main/worker-main.ts | 0 .../src/polyfills/array.fill.js | 0 .../src/polyfills/array.findIndex.js | 0 .../src/polyfills/array.from.js | 0 .../src/polyfills/array.includes.js | 0 .../src/polyfills/object.values.js | 0 .../src/polyfills/symbol-es6.min.js | 0 .../src/test/mocha/cases/auto-correct.js | 0 .../src/test/mocha/cases/casing-detection.js | 0 .../cases/early-correction-search-stopping.js | 0 .../edit-distance/classical-calculation.js | 0 .../cases/edit-distance/context-tracker.js | 0 .../cases/edit-distance/distance-modeler.js | 0 .../cases/edit-distance/execution-timer.js | 0 .../mocha/cases/predict-from-corrections.js | 0 .../mocha/cases/suggestion-deduplication.js | 0 .../mocha/cases/suggestion-finalization.js | 0 .../test/mocha/cases/suggestion-similarity.js | 0 .../mocha/cases/transform-tokenization.js | 0 .../src/test/mocha/cases/transform-utils.js | 0 .../mocha/cases/worker-custom-punctuation.js | 0 .../test/mocha/cases/worker-initialization.js | 0 .../mocha/cases/worker-model-compositor.js | 0 .../test/mocha/cases/worker-predict-dummy.js | 0 .../src/test/mocha/cases/worker-predict.js | 0 .../src/test/test-runner/cases/worker.spec.ts | 0 .../test-runner/web-test-runner.CI.config.mjs | 0 .../test-runner/web-test-runner.config.mjs | 0 .../worker-thread/tsconfig.json | 32 +++++++++++++++++++ 55 files changed, 46 insertions(+), 40 deletions(-) delete mode 100644 common/web/lm-worker/tsconfig.json rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/.c8rc.json (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/build-polyfiller.js (98%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/build-wrapper.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/build.sh (87%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/package.json (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/correction/classical-calculation.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/correction/context-tracker.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/correction/distance-modeler.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/correction/execution-timer.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/correction/index.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/correction/transform-tokenization.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/index.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/model-compositor.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/model-helpers.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/models/dummy-model.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/models/index.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/predict-helpers.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/transformUtils.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/worker-interfaces.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/main/worker-main.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/polyfills/array.fill.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/polyfills/array.findIndex.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/polyfills/array.from.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/polyfills/array.includes.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/polyfills/object.values.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/polyfills/symbol-es6.min.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/auto-correct.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/casing-detection.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/early-correction-search-stopping.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/edit-distance/classical-calculation.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/edit-distance/context-tracker.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/edit-distance/distance-modeler.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/edit-distance/execution-timer.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/predict-from-corrections.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/suggestion-deduplication.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/suggestion-finalization.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/suggestion-similarity.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/transform-tokenization.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/transform-utils.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/worker-custom-punctuation.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/worker-initialization.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/worker-model-compositor.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/worker-predict-dummy.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/mocha/cases/worker-predict.js (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/test-runner/cases/worker.spec.ts (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/test-runner/web-test-runner.CI.config.mjs (100%) rename {common/web/lm-worker => web/src/engine/predictive-text/worker-thread}/src/test/test-runner/web-test-runner.config.mjs (100%) create mode 100644 web/src/engine/predictive-text/worker-thread/tsconfig.json diff --git a/common/web/lm-worker/tsconfig.json b/common/web/lm-worker/tsconfig.json deleted file mode 100644 index 95b403c768b..00000000000 --- a/common/web/lm-worker/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "extends": "../../models/tsconfig.kmw-worker-base.json", - - "compilerOptions": { - "baseUrl": "./", - "outDir": "build/obj", - "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo", - "rootDir": "./src/main", - - // As this one is the one that directly interfaces with the worker (from the inside) - "lib": ["webworker", "es6"], - }, - "references": [ - // types - { "path": "../../models/types" }, - { "path": "../../../web/src/engine/predictive-text/types" }, - // modules - { "path": "../keyman-version" }, - { "path": "../utils" }, - { "path": "../../models/templates" }, - { "path": "../../models/wordbreakers" }, - ], - "include": [ - "src/main/**/*.ts" - ] -} diff --git a/package-lock.json b/package-lock.json index 99a3d2f934b..8df9c941c25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -205,7 +205,7 @@ "typescript": "^5.4.5" } }, - "common/web/lm-worker": { + "web/src/engine/predictive-text/worker-thread": { "name": "@keymanapp/lm-worker", "license": "MIT", "dependencies": { @@ -2900,7 +2900,7 @@ "link": true }, "node_modules/@keymanapp/lm-worker": { - "resolved": "common/web/lm-worker", + "resolved": "web/src/engine/predictive-text/worker-thread", "link": true }, "node_modules/@keymanapp/models-templates": { diff --git a/tsconfig.json b/tsconfig.json index 252fd738dc3..1a02e20ff15 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,6 @@ { "path": "./common/web/gesture-recognizer/tsconfig.json" }, { "path": "./common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json" }, { "path": "./common/web/keyman-version" }, - { "path": "./common/web/lm-worker/" }, { "path": "./common/web/recorder/tsconfig.json" }, { "path": "./common/web/sentry-manager/src/tsconfig.json" }, { "path": "./common/web/types/" }, @@ -43,6 +42,7 @@ { "path": "./web/src/tsconfig.all.json" }, { "path": "./web/src/engine/predictive-text/types/" }, { "path": "./web/src/engine/predictive-text/worker-main/tsconfig.all.json" }, + { "path": "./web/src/engine/predictive-text/worker-thread" }, // { "path": "./web/tools/recorder/tsconfig.json" }, // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, ] diff --git a/web/README.md b/web/README.md index 1e41497657c..bc2938bae0a 100644 --- a/web/README.md +++ b/web/README.md @@ -89,7 +89,7 @@ graph TD; Wordbreakers["@keymanapp/models-wordbreakers
(/common/models/wordbreakers)"]; Models["@keymanapp/models-templates
(/common/models/templates)"]; Models-->WebUtils; - LMWorker["@keymanapp/lm-worker
(/common/web/lm-worker)"]; + LMWorker["@keymanapp/lm-worker
(/web/src/engine/predictive-text/worker-thread)"]; LMWorker-->Models; LMWorker-->Wordbreakers; LMLayer["@keymanapp/lexical-model-layer
(/web/src/engine/predictive-text/worker-main)"]; diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index 049bc056b5e..1d757e264a2 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -79,7 +79,7 @@ compile_and_copy() { local PROFILE_DEST="$KEYMAN_ROOT/web/build/profiling/" mkdir -p "$PROFILE_DEST" cp "${BUILD_ROOT}/filesize-profile.log" "$PROFILE_DEST/web-engine-filesize.log" - cp "$KEYMAN_ROOT/common/web/lm-worker/build/filesize-profile.log" "$PROFILE_DEST/lm-worker-filesize.log" + cp "$KEYMAN_ROOT/web/src/engine/predictive-text/worker-thread/build/filesize-profile.log" "$PROFILE_DEST/lm-worker-filesize.log" } builder_run_action configure verify_npm_setup diff --git a/web/src/engine/predictive-text/worker-main/build.sh b/web/src/engine/predictive-text/worker-main/build.sh index 4120fce53b1..33e5eaebeb6 100755 --- a/web/src/engine/predictive-text/worker-main/build.sh +++ b/web/src/engine/predictive-text/worker-main/build.sh @@ -22,7 +22,7 @@ BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" builder_describe "Builds the lm-layer module" \ "@/common/web/keyman-version" \ "@/common/web/es-bundling" \ - "@/common/web/lm-worker" \ + "@/web/src/engine/predictive-text/worker-thread" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/test.sh b/web/src/engine/predictive-text/worker-main/unit_tests/test.sh index 007bf363576..dda8d578640 100755 --- a/web/src/engine/predictive-text/worker-main/unit_tests/test.sh +++ b/web/src/engine/predictive-text/worker-main/unit_tests/test.sh @@ -57,9 +57,9 @@ if builder_start_action test:libraries; then "$KEYMAN_ROOT/common/models/templates/build.sh" test $TEST_OPTS popd - pushd "$KEYMAN_ROOT/common/web/lm-worker" + pushd "$KEYMAN_ROOT/web/src/engine/predictive-text/worker-thread" echo - echo "### Running ${BUILDER_TERM_START}common/web/lm-worker${BUILDER_TERM_END} tests" + echo "### Running ${BUILDER_TERM_START}web/src/engine/predictive-text/worker-thread${BUILDER_TERM_END} tests" ./build.sh test $TEST_OPTS popd diff --git a/common/web/lm-worker/.c8rc.json b/web/src/engine/predictive-text/worker-thread/.c8rc.json similarity index 100% rename from common/web/lm-worker/.c8rc.json rename to web/src/engine/predictive-text/worker-thread/.c8rc.json diff --git a/common/web/lm-worker/build-polyfiller.js b/web/src/engine/predictive-text/worker-thread/build-polyfiller.js similarity index 98% rename from common/web/lm-worker/build-polyfiller.js rename to web/src/engine/predictive-text/worker-thread/build-polyfiller.js index 41cec75ce7d..eab268e9867 100644 --- a/common/web/lm-worker/build-polyfiller.js +++ b/web/src/engine/predictive-text/worker-thread/build-polyfiller.js @@ -166,7 +166,7 @@ let fullWorkerConcatenation = concatScriptsAndSourcemaps(sourceFileSet, destFile // New stage: cleaning the sourcemaps // Sources are being passed into the sourcemap concatenator via our working directory. -let sourceRoot = '@keymanapp/keyman/common/web/lm-worker/'; +let sourceRoot = '@keymanapp/keyman/web/src/engine/predictive-text/worker-thread/'; fullWorkerConcatenation.sourcemapJSON.sourceRoot = sourceRoot; // End "cleaning the sourcemaps" diff --git a/common/web/lm-worker/build-wrapper.js b/web/src/engine/predictive-text/worker-thread/build-wrapper.js similarity index 100% rename from common/web/lm-worker/build-wrapper.js rename to web/src/engine/predictive-text/worker-thread/build-wrapper.js diff --git a/common/web/lm-worker/build.sh b/web/src/engine/predictive-text/worker-thread/build.sh similarity index 87% rename from common/web/lm-worker/build.sh rename to web/src/engine/predictive-text/worker-thread/build.sh index 0343d151c8f..8c6a4ca0012 100755 --- a/common/web/lm-worker/build.sh +++ b/web/src/engine/predictive-text/worker-thread/build.sh @@ -6,7 +6,7 @@ ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" @@ -17,7 +17,7 @@ WORKER_OUTPUT_FILENAME=build/lib/worker-main.js INTERMEDIATE=./build/intermediate LIB=./build/lib -bundle_cmd="node ../es-bundling/build/common-bundle.mjs" +bundle_cmd="node ${KEYMAN_ROOT}/common/web/es-bundling/build/common-bundle.mjs" SRCMAP_CLEANER="node $KEYMAN_ROOT/web/build/tools/building/sourcemap-root/index.js" @@ -34,7 +34,7 @@ builder_describe \ builder_describe_outputs \ configure /node_modules \ - build /common/web/lm-worker/$LIB/worker-main.wrapped.min.js + build "/web/src/engine/predictive-text/worker-thread/${LIB}/worker-main.wrapped.min.js" builder_parse "$@" @@ -65,7 +65,7 @@ function do_build() { $bundle_cmd src/main/worker-main.ts \ --out $INTERMEDIATE/worker-main.js \ --target "es6" \ - --sourceRoot '@keymanapp/keyman/common/web/lm-worker/src/main' + --sourceRoot '@keymanapp/keyman/web/src/engine/predictive-text/worker-thread/src/main' $SRCMAP_CLEANER \ $INTERMEDIATE/worker-main.js.map \ @@ -77,7 +77,7 @@ function do_build() { --minify \ --profile build/filesize-profile.log \ --target "es6" \ - --sourceRoot '@keymanapp/keyman/common/web/lm-worker/src/main' + --sourceRoot '@keymanapp/keyman/web/src/engine/predictive-text/worker-thread/src/main' $SRCMAP_CLEANER \ $INTERMEDIATE/worker-main.min.js.map \ diff --git a/common/web/lm-worker/package.json b/web/src/engine/predictive-text/worker-thread/package.json similarity index 100% rename from common/web/lm-worker/package.json rename to web/src/engine/predictive-text/worker-thread/package.json diff --git a/common/web/lm-worker/src/main/correction/classical-calculation.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts similarity index 100% rename from common/web/lm-worker/src/main/correction/classical-calculation.ts rename to web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts diff --git a/common/web/lm-worker/src/main/correction/context-tracker.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts similarity index 100% rename from common/web/lm-worker/src/main/correction/context-tracker.ts rename to web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts diff --git a/common/web/lm-worker/src/main/correction/distance-modeler.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts similarity index 100% rename from common/web/lm-worker/src/main/correction/distance-modeler.ts rename to web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts diff --git a/common/web/lm-worker/src/main/correction/execution-timer.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/execution-timer.ts similarity index 100% rename from common/web/lm-worker/src/main/correction/execution-timer.ts rename to web/src/engine/predictive-text/worker-thread/src/main/correction/execution-timer.ts diff --git a/common/web/lm-worker/src/main/correction/index.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/index.ts similarity index 100% rename from common/web/lm-worker/src/main/correction/index.ts rename to web/src/engine/predictive-text/worker-thread/src/main/correction/index.ts diff --git a/common/web/lm-worker/src/main/correction/transform-tokenization.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/transform-tokenization.ts similarity index 100% rename from common/web/lm-worker/src/main/correction/transform-tokenization.ts rename to web/src/engine/predictive-text/worker-thread/src/main/correction/transform-tokenization.ts diff --git a/common/web/lm-worker/src/main/index.ts b/web/src/engine/predictive-text/worker-thread/src/main/index.ts similarity index 100% rename from common/web/lm-worker/src/main/index.ts rename to web/src/engine/predictive-text/worker-thread/src/main/index.ts diff --git a/common/web/lm-worker/src/main/model-compositor.ts b/web/src/engine/predictive-text/worker-thread/src/main/model-compositor.ts similarity index 100% rename from common/web/lm-worker/src/main/model-compositor.ts rename to web/src/engine/predictive-text/worker-thread/src/main/model-compositor.ts diff --git a/common/web/lm-worker/src/main/model-helpers.ts b/web/src/engine/predictive-text/worker-thread/src/main/model-helpers.ts similarity index 100% rename from common/web/lm-worker/src/main/model-helpers.ts rename to web/src/engine/predictive-text/worker-thread/src/main/model-helpers.ts diff --git a/common/web/lm-worker/src/main/models/dummy-model.ts b/web/src/engine/predictive-text/worker-thread/src/main/models/dummy-model.ts similarity index 100% rename from common/web/lm-worker/src/main/models/dummy-model.ts rename to web/src/engine/predictive-text/worker-thread/src/main/models/dummy-model.ts diff --git a/common/web/lm-worker/src/main/models/index.ts b/web/src/engine/predictive-text/worker-thread/src/main/models/index.ts similarity index 100% rename from common/web/lm-worker/src/main/models/index.ts rename to web/src/engine/predictive-text/worker-thread/src/main/models/index.ts diff --git a/common/web/lm-worker/src/main/predict-helpers.ts b/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts similarity index 100% rename from common/web/lm-worker/src/main/predict-helpers.ts rename to web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts diff --git a/common/web/lm-worker/src/main/transformUtils.ts b/web/src/engine/predictive-text/worker-thread/src/main/transformUtils.ts similarity index 100% rename from common/web/lm-worker/src/main/transformUtils.ts rename to web/src/engine/predictive-text/worker-thread/src/main/transformUtils.ts diff --git a/common/web/lm-worker/src/main/worker-interfaces.ts b/web/src/engine/predictive-text/worker-thread/src/main/worker-interfaces.ts similarity index 100% rename from common/web/lm-worker/src/main/worker-interfaces.ts rename to web/src/engine/predictive-text/worker-thread/src/main/worker-interfaces.ts diff --git a/common/web/lm-worker/src/main/worker-main.ts b/web/src/engine/predictive-text/worker-thread/src/main/worker-main.ts similarity index 100% rename from common/web/lm-worker/src/main/worker-main.ts rename to web/src/engine/predictive-text/worker-thread/src/main/worker-main.ts diff --git a/common/web/lm-worker/src/polyfills/array.fill.js b/web/src/engine/predictive-text/worker-thread/src/polyfills/array.fill.js similarity index 100% rename from common/web/lm-worker/src/polyfills/array.fill.js rename to web/src/engine/predictive-text/worker-thread/src/polyfills/array.fill.js diff --git a/common/web/lm-worker/src/polyfills/array.findIndex.js b/web/src/engine/predictive-text/worker-thread/src/polyfills/array.findIndex.js similarity index 100% rename from common/web/lm-worker/src/polyfills/array.findIndex.js rename to web/src/engine/predictive-text/worker-thread/src/polyfills/array.findIndex.js diff --git a/common/web/lm-worker/src/polyfills/array.from.js b/web/src/engine/predictive-text/worker-thread/src/polyfills/array.from.js similarity index 100% rename from common/web/lm-worker/src/polyfills/array.from.js rename to web/src/engine/predictive-text/worker-thread/src/polyfills/array.from.js diff --git a/common/web/lm-worker/src/polyfills/array.includes.js b/web/src/engine/predictive-text/worker-thread/src/polyfills/array.includes.js similarity index 100% rename from common/web/lm-worker/src/polyfills/array.includes.js rename to web/src/engine/predictive-text/worker-thread/src/polyfills/array.includes.js diff --git a/common/web/lm-worker/src/polyfills/object.values.js b/web/src/engine/predictive-text/worker-thread/src/polyfills/object.values.js similarity index 100% rename from common/web/lm-worker/src/polyfills/object.values.js rename to web/src/engine/predictive-text/worker-thread/src/polyfills/object.values.js diff --git a/common/web/lm-worker/src/polyfills/symbol-es6.min.js b/web/src/engine/predictive-text/worker-thread/src/polyfills/symbol-es6.min.js similarity index 100% rename from common/web/lm-worker/src/polyfills/symbol-es6.min.js rename to web/src/engine/predictive-text/worker-thread/src/polyfills/symbol-es6.min.js diff --git a/common/web/lm-worker/src/test/mocha/cases/auto-correct.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/auto-correct.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/auto-correct.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/auto-correct.js diff --git a/common/web/lm-worker/src/test/mocha/cases/casing-detection.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/casing-detection.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/casing-detection.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/casing-detection.js diff --git a/common/web/lm-worker/src/test/mocha/cases/early-correction-search-stopping.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/early-correction-search-stopping.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/early-correction-search-stopping.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/early-correction-search-stopping.js diff --git a/common/web/lm-worker/src/test/mocha/cases/edit-distance/classical-calculation.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/classical-calculation.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/edit-distance/classical-calculation.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/classical-calculation.js diff --git a/common/web/lm-worker/src/test/mocha/cases/edit-distance/context-tracker.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/context-tracker.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/edit-distance/context-tracker.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/context-tracker.js diff --git a/common/web/lm-worker/src/test/mocha/cases/edit-distance/distance-modeler.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/distance-modeler.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/edit-distance/distance-modeler.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/distance-modeler.js diff --git a/common/web/lm-worker/src/test/mocha/cases/edit-distance/execution-timer.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/execution-timer.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/edit-distance/execution-timer.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/edit-distance/execution-timer.js diff --git a/common/web/lm-worker/src/test/mocha/cases/predict-from-corrections.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/predict-from-corrections.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/predict-from-corrections.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/predict-from-corrections.js diff --git a/common/web/lm-worker/src/test/mocha/cases/suggestion-deduplication.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/suggestion-deduplication.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/suggestion-deduplication.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/suggestion-deduplication.js diff --git a/common/web/lm-worker/src/test/mocha/cases/suggestion-finalization.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/suggestion-finalization.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/suggestion-finalization.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/suggestion-finalization.js diff --git a/common/web/lm-worker/src/test/mocha/cases/suggestion-similarity.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/suggestion-similarity.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/suggestion-similarity.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/suggestion-similarity.js diff --git a/common/web/lm-worker/src/test/mocha/cases/transform-tokenization.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/transform-tokenization.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/transform-tokenization.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/transform-tokenization.js diff --git a/common/web/lm-worker/src/test/mocha/cases/transform-utils.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/transform-utils.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/transform-utils.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/transform-utils.js diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-custom-punctuation.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-custom-punctuation.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/worker-custom-punctuation.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-custom-punctuation.js diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-initialization.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-initialization.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/worker-initialization.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-initialization.js diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-model-compositor.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-model-compositor.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/worker-model-compositor.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-model-compositor.js diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-predict-dummy.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-predict-dummy.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/worker-predict-dummy.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-predict-dummy.js diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-predict.js b/web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-predict.js similarity index 100% rename from common/web/lm-worker/src/test/mocha/cases/worker-predict.js rename to web/src/engine/predictive-text/worker-thread/src/test/mocha/cases/worker-predict.js diff --git a/common/web/lm-worker/src/test/test-runner/cases/worker.spec.ts b/web/src/engine/predictive-text/worker-thread/src/test/test-runner/cases/worker.spec.ts similarity index 100% rename from common/web/lm-worker/src/test/test-runner/cases/worker.spec.ts rename to web/src/engine/predictive-text/worker-thread/src/test/test-runner/cases/worker.spec.ts diff --git a/common/web/lm-worker/src/test/test-runner/web-test-runner.CI.config.mjs b/web/src/engine/predictive-text/worker-thread/src/test/test-runner/web-test-runner.CI.config.mjs similarity index 100% rename from common/web/lm-worker/src/test/test-runner/web-test-runner.CI.config.mjs rename to web/src/engine/predictive-text/worker-thread/src/test/test-runner/web-test-runner.CI.config.mjs diff --git a/common/web/lm-worker/src/test/test-runner/web-test-runner.config.mjs b/web/src/engine/predictive-text/worker-thread/src/test/test-runner/web-test-runner.config.mjs similarity index 100% rename from common/web/lm-worker/src/test/test-runner/web-test-runner.config.mjs rename to web/src/engine/predictive-text/worker-thread/src/test/test-runner/web-test-runner.config.mjs diff --git a/web/src/engine/predictive-text/worker-thread/tsconfig.json b/web/src/engine/predictive-text/worker-thread/tsconfig.json new file mode 100644 index 00000000000..bfc8498730b --- /dev/null +++ b/web/src/engine/predictive-text/worker-thread/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../../tsconfig.base.json", + + "compilerOptions": { + "baseUrl": "./", + "outDir": "build/obj", + "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo", + "rootDir": "./src/main", + // To help better support legacy Android devices + "downlevelIteration": true, + // Facilitates & simplifies stitching together the worker sourcemaps during the polyfill-concatenation step. + "inlineSourceMap": true, + // May not be set at the same time as the prior setting. + "sourceMap": false, + + // As this one is the one that directly interfaces with the worker (from the inside) + "lib": ["webworker", "es6"], + }, + "references": [ + // types + { "path": "../../../../../common/models/types" }, + { "path": "../types" }, + // modules + { "path": "../../../../../common/web/keyman-version" }, + { "path": "../../../../../common/web/utils" }, + { "path": "../../../../../common/models/templates" }, + { "path": "../../../../../common/models/wordbreakers" }, + ], + "include": [ + "src/main/**/*.ts" + ] +} From 1cbd061c05b7a64cf3c688a8a0399bcb06ee90b5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 16 Aug 2024 08:54:27 +0200 Subject: [PATCH 065/262] chore(developer): remove impossible test for missing version --- .../test/test-keyboard-info-compiler.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index 9b61b8e93d9..99c244be2dd 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -835,17 +835,4 @@ describe('keyboard-info-compiler', function () { const result = await compiler['fontSourceToKeyboardInfoFont'](KHMER_ANGKOR_KPS, kmpJsonData, fonts); assert.deepEqual(result, KHMER_ANGKOR_DISPLAY_FONT_INFO); }); - - it('handles missing info.version in a package file', async function() { - const sources = { - ...KHMER_ANGKOR_SOURCES, - kpsFilename: makePathToFixture('missing-info-version-in-kps-11856', 'khmer_angkor.kps') - }; - const compiler = new KeyboardInfoCompiler(); - assert.isTrue(await compiler.init(callbacks, {sources})); - const kpjFilename = KHMER_ANGKOR_KPJ; - const result = await compiler.run(kpjFilename); - const actual = JSON.parse(new TextDecoder().decode(result.artifacts.keyboard_info.data)); - assert.equal(actual.version, '1.0'); - }); }); From 72da3d8099d2e890f3941acfa13001684b9da7a4 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 16 Aug 2024 14:59:28 +0700 Subject: [PATCH 066/262] refactor(ios): optimize font registration --- .../Classes/Resource Data/FontManager.swift | 151 ++++++++++++------ 1 file changed, 102 insertions(+), 49 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift index d64019cc27c..4a506501000 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift @@ -26,6 +26,17 @@ public class FontManager { fonts[url] = RegisteredFont(name: name, isRegistered: false) return name } + + private func cachedFont(at url: URL) -> RegisteredFont? { + if let font = fonts[url] { + return font + } + guard let name = readFontName(at: url) else { + return nil + } + fonts[url] = RegisteredFont(name: name, isRegistered: false) + return fonts[url] + } /// Registers all new fonts found in the font path. Call this after you have preloaded all your font files /// with `preloadFontFile(atPath:shouldOverwrite:)` @@ -33,8 +44,45 @@ public class FontManager { guard let keyboardDirs = Storage.active.keyboardDirs else { return } + + var fontSet: Set = [] for dir in keyboardDirs { - registerFonts(in: dir) + fontSet = fontSet.union(listFonts(in: dir)) + } + + registerListedFonts(fontSet) + } + + /** + * Iterates across all listed fonts, registering those not yet registered. + * Checks for, and filters out, any fonts already registered on the system. + */ + private func registerListedFonts(_ initialFontSet: Set) { + // If we are unable to read the font file's properties sufficiently, + // skip it. We also don't need to register anything already registered. + var fontSet = initialFontSet.filter { !(cachedFont(at: $0)?.isRegistered ?? true) } + + // The prior line filters out any entries where cachedFont(at: $0) would be nil. + // Batch-lookups all fonts lacking cache-confirmation of prior registration. + var fontNamesToRegister = missingFonts(from: Set(fontSet.map { cachedFont(at: $0)!.name })) + + for fontUrl in fontSet { + let fontName = cachedFont(at: fontUrl)!.name + + guard fontNamesToRegister.contains(fontName) else { + let message = "Did not register font at \(fontUrl) because font name \(fontName) is already registered" + os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message) + continue + } + + let didRegister = _registerFont(at: fontUrl) + fonts[fontUrl] = RegisteredFont(name: fontName, isRegistered: didRegister) + + // We no longer need to register a font with this name, so drop it from + // the set to register. + if didRegister { + fontNamesToRegister.remove(fontName) + } } } @@ -43,6 +91,8 @@ public class FontManager { guard let keyboardDirs = Storage.active.keyboardDirs else { return } + // This doesn't use the expensive looped lookup operation seen in missingFonts, + // so there's no need to batch similar operations here. for dir in keyboardDirs { unregisterFonts(in: dir) } @@ -63,44 +113,29 @@ public class FontManager { } return name as String } + + private func _registerFont(at url: URL) -> Bool { + var errorRef: Unmanaged? + let fontName = fontName(at: url)! + let didRegister = CTFontManagerRegisterFontsForURL(url as CFURL, .none, &errorRef) + let error = errorRef?.takeRetainedValue() // Releases errorRef + if !didRegister { + let message = "Failed to register font \(fontName) at \(url) reason: \(String(describing: error))" + os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message) + } else { + let message = "Registered font \(fontName) at \(url)" + os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message) + } + + return didRegister + } /// - Parameters: /// - url: URL of the font to register /// - Returns: Font is registered. public func registerFont(at url: URL) -> Bool { - let fontName: String - if let font = fonts[url] { - if font.isRegistered { - return true - } - fontName = font.name - } else { - guard let name = readFontName(at: url) else { - return false - } - fontName = name - } - - let didRegister: Bool - if !fontExists(fontName) { - var errorRef: Unmanaged? - didRegister = CTFontManagerRegisterFontsForURL(url as CFURL, .none, &errorRef) - let error = errorRef?.takeRetainedValue() // Releases errorRef - if !didRegister { - let message = "Failed to register font \(fontName) at \(url) reason: \(String(describing: error))" - os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message) - } else { - let message = "Registered font \(fontName) at \(url)" - os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message) - } - } else { - didRegister = false - let message = "Did not register font at \(url) because font name \(fontName) is already registered" - os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message) - } - let font = RegisteredFont(name: fontName, isRegistered: didRegister) - fonts[url] = font - return didRegister + registerListedFonts([url]) + return fonts[url]?.isRegistered ?? false } /// - Parameters: @@ -133,32 +168,50 @@ public class FontManager { return font.isRegistered } - - public func registerFonts(in directory: URL) { + + private func listFonts(in directory: URL) -> [URL] { guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else { let message = "Could not list contents of directory \(directory)" os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message) - return - } - for url in urls where url.lastPathComponent.hasFontExtension { - _ = registerFont(at: url) + return [] } + return urls.filter { $0.lastPathComponent.hasFontExtension } + } + + public func registerFonts(in directory: URL) { + let fontsToRegister = listFonts(in: directory) + registerListedFonts(Set(fontsToRegister)) } public func unregisterFonts(in directory: URL, fromSystemOnly: Bool = true) { - guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else { - let message = "Could not list contents of directory \(directory)" - os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message) - return - } - for url in urls where url.lastPathComponent.hasFontExtension { + let fontsToUnregister = listFonts(in: directory) + for url in fontsToUnregister { _ = unregisterFont(at: url, fromSystemOnly: fromSystemOnly) } } - private func fontExists(_ fontName: String) -> Bool { - return UIFont.familyNames.contains { familyName in - UIFont.fontNames(forFamilyName: familyName).contains(fontName) + /** + * Queries the system for existing registrations for the specified fonts with a single batch run. + * Only fonts that could not be found will be returned within the result set. + */ + private func missingFonts(from fontNames: Set) -> Set { + // Arrays are treated 'by value'; it's already a shallow copy. + var fontsToFind = fontNames + + UIFont.familyNames.forEach { familyName in + let familyFonts = UIFont.fontNames(forFamilyName: familyName) + + for font in familyFonts { + if let index = fontsToFind.firstIndex(where: { $0 == font }) { + fontsToFind.remove(at: index) + } + + if fontsToFind.count == 0 { + break + } + } } + + return fontsToFind } } From e28d8a341a02477cab1b7e841187679917d5048c Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 16 Aug 2024 15:15:32 +0700 Subject: [PATCH 067/262] change(ios): cleaner set-based fonts-to-find maintenance --- .../KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift index 4a506501000..6dd114d375c 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift @@ -202,8 +202,8 @@ public class FontManager { let familyFonts = UIFont.fontNames(forFamilyName: familyName) for font in familyFonts { - if let index = fontsToFind.firstIndex(where: { $0 == font }) { - fontsToFind.remove(at: index) + if fontsToFind.contains(font) { + fontsToFind.remove(font) } if fontsToFind.count == 0 { From 289dda0fcab73ae94938e9a69ec599f2c7efb0cf Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 16 Aug 2024 15:17:05 +0700 Subject: [PATCH 068/262] change(ios): better early-exit if all font data found --- .../KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift index 6dd114d375c..9f0047d7f7b 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift @@ -199,6 +199,10 @@ public class FontManager { var fontsToFind = fontNames UIFont.familyNames.forEach { familyName in + if fontsToFind.count == 0 { + return + } + let familyFonts = UIFont.fontNames(forFamilyName: familyName) for font in familyFonts { From a4159de1d16fd2bc62a3f4f8ab6b6681cdb23be3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 16 Aug 2024 11:02:22 +0200 Subject: [PATCH 069/262] chore(linux): add mcompile to linux build.sh @keymanapp-test-bot skip --- linux/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/linux/build.sh b/linux/build.sh index 1012b7eb1f0..ef4848fb0d3 100755 --- a/linux/build.sh +++ b/linux/build.sh @@ -13,6 +13,7 @@ builder_describe \ ":config=keyman-config keyman-config" \ ":engine=ibus-keyman ibus-keyman" \ ":service=keyman-system-service keyman-system-service" \ + ":mcompile=mcompile/keymap mnemonic layout recompiler for Linux" \ "clean" \ "configure" \ "build" \ From 5364a741dadd7a75bb5137c08110bd9664113753 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 16 Aug 2024 16:34:19 +0700 Subject: [PATCH 070/262] change(mac): some refactoring Mostly taking advantage of KMSettingsRepository so that more of the UserDefaults changes happen through it Still some refactoring remains for active keyboards list --- .../Keyman4MacIM/KMInputMethodAppDelegate.h | 3 +- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 98 +++------------ .../Keyman4MacIM/KMInputMethodEventHandler.m | 2 +- .../Keyman4MacIM/KMPackageReader.h | 2 - .../Keyman4MacIM/KMPackageReader.m | 3 - .../Keyman4MacIM/KMSettingsRepository.h | 8 ++ .../Keyman4MacIM/KMSettingsRepository.m | 115 ++++++++++++++---- 7 files changed, 114 insertions(+), 117 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 7eddf31cb51..376e531f7f2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -23,7 +23,6 @@ typedef void(^PostEventCallback)(CGEventRef eventToPost); -extern NSString *const kKMSelectedKeyboardKey; extern NSString *const kKMActiveKeyboardsKey; extern NSString *const kKeymanKeyboardDownloadCompletedNotification; @@ -99,7 +98,7 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; - (NSMenu *)menu; - (void)saveActiveKeyboards; -- (void)readPersistedOptions; +- (void)applyPersistedOptions; - (void)writePersistedOptions:(NSString *)storeKey withValue:(NSString* )value; - (void)showAboutWindow; - (void)showOSK; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 947bb803f13..fddecc55fa3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -6,15 +6,6 @@ // Copyright (c) 2017 SIL International. All rights reserved. // -// *** TO INVESTIGATE *** -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (activateServerWithReply:) block performed very slowly (0.00 secs) -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (deactivateServerWithReply:) block performed very slowly (0.00 secs) -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (menusDictionaryWithClientAsync:reply:) block performed very slowly (0.00 secs) -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (modesWithClientAsync:reply:) block performed very slowly (0.00 secs) -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (commitCompositionWithReply:) block performed very slowly (0.00 secs) -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (hidePalettes) block performed very slowly (0.00 secs) -// Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (sessionFinished) block performed very slowly (0.00 secs) - #import "KMInputMethodAppDelegate.h" #import "KMSettingsRepository.h" #import "KMDataRepository.h" @@ -28,20 +19,9 @@ #import "KMLogs.h" @import Sentry; +// TODO: move Active Keyboards UserDefaults code to KMSettingsRepository /** NSUserDefaults keys */ -NSString *const kKMSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kKMActiveKeyboardsKey = @"KMActiveKeyboardsKey"; -/** - The following constant "KMSavedStoresKey" is left here for documentation - though we have abandoned stores written to UserDefaults with this key because - they used a less-reliable numeric key prior to integration with Keyman Core. - It is replaced by the renamed "KMPersistedOptionsKey" which directly - represents what it is saving. - */ -NSString *const kKMDeprecatedPersistedOptionsKey = @"KMSavedStoresKey"; -NSString *const kKMPersistedOptionsKey = @"KMPersistedOptionsKey"; -NSString *const kKMAlwaysShowOSKKey = @"KMAlwaysShowOSKKey"; -NSString *const kKMUseVerboseLogging = @"KMUseVerboseLogging"; NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification"; @@ -409,7 +389,6 @@ - (KMEngine *)kme { - (KMPackageReader *)packageReader { if (_packageReader == nil) { _packageReader = [[KMPackageReader alloc] init]; - [_packageReader setDebugMode:self.debugMode]; } return _packageReader; @@ -440,55 +419,17 @@ - (void)setKeyboardName:(NSString *)keyboardName { [_oskWindow.window setTitle:self.oskWindowTitle]; } -- (void)readPersistedOptions { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - NSDictionary *allPersistedOptions = [userData dictionaryForKey:kKMPersistedOptionsKey]; - if (!allPersistedOptions) { - return; - } - NSDictionary *persistedOptionsForSelectedKeyboard = [allPersistedOptions objectForKey:_selectedKeyboard]; - if (!persistedOptionsForSelectedKeyboard) { - os_log_info([KMLogs configLog], "no persisted options found in UserDefaults for keyboard %{public}@ ", _selectedKeyboard); - return; - } +- (void)applyPersistedOptions { + NSDictionary *selectedPersistedOptions = [[KMSettingsRepository shared] readOptionsForSelectedKeyboard]; // TODO: pass array instead of making repeated calls - for (NSString *key in persistedOptionsForSelectedKeyboard) { - NSString *value = [persistedOptionsForSelectedKeyboard objectForKey:key]; + for (NSString *key in selectedPersistedOptions) { + NSString *value = [selectedPersistedOptions objectForKey:key]; os_log_info([KMLogs configLog], "persisted options found in UserDefaults for keyboard %{public}@, key: %{public}@, value: %{public}@", _selectedKeyboard, key, value); [self.kme setCoreOptions:key withValue:value]; } } -- (void)writePersistedOptions:(NSString *)storeKey withValue:(NSString* )value { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - NSDictionary *allPersistedOptions = [userData dictionaryForKey:kKMPersistedOptionsKey]; - NSDictionary *persistedOptionsForSelectedKeyboard; - - if (allPersistedOptions) { - persistedOptionsForSelectedKeyboard = [allPersistedOptions objectForKey:_selectedKeyboard]; - } - - if (persistedOptionsForSelectedKeyboard) { - NSMutableDictionary *newSavedStores = [persistedOptionsForSelectedKeyboard mutableCopy]; - [newSavedStores setObject:value forKey:storeKey]; - persistedOptionsForSelectedKeyboard = newSavedStores; - } else { - persistedOptionsForSelectedKeyboard = [[NSDictionary alloc] initWithObjectsAndKeys:value, storeKey, nil]; - } - - if (allPersistedOptions) { - NSMutableDictionary *newAllSavedStores = [allPersistedOptions mutableCopy]; - [newAllSavedStores setObject:persistedOptionsForSelectedKeyboard forKey:_selectedKeyboard]; - allPersistedOptions = newAllSavedStores; - } else { - allPersistedOptions = [[NSDictionary alloc] initWithObjectsAndKeys:persistedOptionsForSelectedKeyboard, _selectedKeyboard, nil]; - } - - [userData setObject:allPersistedOptions forKey:kKMPersistedOptionsKey]; - [userData synchronize]; -} - - (NSString *)oskWindowTitle { if (_keyboardName == nil || !_keyboardName.length) return [NSString stringWithFormat:@"Keyman"]; @@ -498,9 +439,7 @@ - (NSString *)oskWindowTitle { - (void)setAlwaysShowOSK:(BOOL)alwaysShowOSK { _alwaysShowOSK = alwaysShowOSK; - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setBool:alwaysShowOSK forKey:kKMAlwaysShowOSKKey]; - [userData synchronize]; + [[KMSettingsRepository shared] writeAlwaysShowOsk:alwaysShowOSK]; } - (void)setUseVerboseLogging:(BOOL)useVerboseLogging { @@ -508,23 +447,17 @@ - (void)setUseVerboseLogging:(BOOL)useVerboseLogging { _debugMode = useVerboseLogging; if (_kme != nil) [_kme setUseVerboseLogging:useVerboseLogging]; - if (_packageReader != nil) { - [_packageReader setDebugMode:useVerboseLogging]; - } - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setBool:useVerboseLogging forKey:kKMUseVerboseLogging]; - [userData synchronize]; + + [[KMSettingsRepository shared] writeUseVerboseLogging:useVerboseLogging]; } - (BOOL)alwaysShowOSK { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - _alwaysShowOSK = [userData boolForKey:kKMAlwaysShowOSKKey]; + _alwaysShowOSK = [[KMSettingsRepository shared] readAlwaysShowOsk]; return _alwaysShowOSK; } - (BOOL)useVerboseLogging { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - return [userData boolForKey:kKMUseVerboseLogging]; + return [[KMSettingsRepository shared] readUseVerboseLogging]; } #pragma mark - Keyman Data @@ -679,8 +612,7 @@ - (NSArray *)keyboardNamesFromFolder:(NSString *)packageFolder { - (NSString *)selectedKeyboard { if (_selectedKeyboard == nil) { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - _selectedKeyboard = [userData objectForKey:kKMSelectedKeyboardKey]; + _selectedKeyboard = [[KMSettingsRepository shared] readSelectedKeyboard]; } os_log_debug([KMLogs dataLog], "selectedKeyboard = %{public}@", _selectedKeyboard); @@ -688,10 +620,8 @@ - (NSString *)selectedKeyboard { } - (void)setSelectedKeyboard:(NSString *)selectedKeyboard { + [[KMSettingsRepository shared] writeSelectedKeyboard:selectedKeyboard]; _selectedKeyboard = selectedKeyboard; - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:_selectedKeyboard forKey:kKMSelectedKeyboardKey]; - [userData synchronize]; } - (NSMutableArray *)activeKeyboards { @@ -903,7 +833,7 @@ - (void) setSelectedKeyboard:(NSString*)keyboardName inMenuItem:(NSMenuItem*) me [self setKvk:kvk]; [self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]]; [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; - [self readPersistedOptions]; + [self applyPersistedOptions]; } // defaults to the whatever keyboard happens to be first in the list @@ -964,7 +894,7 @@ - (void)selectKeyboardFromMenu:(NSInteger)tag { [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; [self setContextBuffer:nil]; [self setSelectedKeyboard:path]; - [self readPersistedOptions]; + [self applyPersistedOptions]; if (kvk != nil && self.alwaysShowOSK) [self showOSK]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index efe628c43a3..da1a3192238 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -429,7 +429,7 @@ -(void) persistOptions:(NSDictionary*)options{ NSString *value = [options objectForKey:key]; if(key && value) { os_log_debug([KMLogs keyLog], "persistOptions, key: %{public}@, value: %{public}@", key, value); - [self.appDelegate writePersistedOptions:key withValue:value]; + [[KMSettingsRepository shared] writeOptionForSelectedKeyboard:key withValue:value]; } else { os_log_debug([KMLogs keyLog], "invalid values in persistOptions, not writing to UserDefaults, key: %{public}@, value: %{public}@", key, value); diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.h b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.h index 2c138d8215a..008f0c025e6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.h @@ -19,8 +19,6 @@ NS_ASSUME_NONNULL_BEGIN @interface KMPackageReader : NSObject -@property (assign, nonatomic) BOOL debugMode; - - (instancetype)init; - (KMPackageInfo *)loadPackageInfo:(NSString *)path; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m index 186ec8ab5a0..e2cf1757a82 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -44,9 +44,6 @@ @implementation KMPackageReader - (instancetype)init { self = [super init]; - if (self) { - _debugMode = NO; - } return self; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index e6fe4dca38a..8b74ce23d21 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -17,6 +17,14 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)dataMigrationNeeded; - (void)convertSettingsForMigration; - (void)setDataModelVersionIfNecessary; +- (NSString *)readSelectedKeyboard; +- (void)writeSelectedKeyboard:(NSString *)selectedKeyboard; +- (NSDictionary *)readOptionsForSelectedKeyboard; +- (void)writeOptionForSelectedKeyboard:(NSString *)key withValue:(NSString*)value; +- (BOOL)readAlwaysShowOsk; +- (void)writeAlwaysShowOsk:(BOOL)alwaysShowOsk; +- (BOOL)readUseVerboseLogging; +- (void)writeUseVerboseLogging:(BOOL)verboseLogging; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 54c779c00d7..83b16a7e8bb 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -17,6 +17,17 @@ NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; +NSString *const kAlwaysShowOSKKey = @"KMAlwaysShowOSKKey"; +NSString *const kUseVerboseLogging = @"KMUseVerboseLogging"; + +/** + The following constant "KMSavedStoresKey" is left here for documentation + though we have abandoned stores written to UserDefaults with this key because + they used a less-reliable numeric key prior to integration with Keyman Core. + It is replaced by the renamed "KMPersistedOptionsKey" which directly + represents what it is saving. + */ +NSString *const kKMDeprecatedPersistedOptionsKey = @"KMSavedStoresKey"; //NSString *const kObsoletePathComponent = @"/Documents/"; NSString *const kObsoletePathComponent = @"/Documents/Keyman-Keyboards"; @@ -58,6 +69,32 @@ - (BOOL)settingsExist return ([[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil); } +- (void)writeOptionForSelectedKeyboard:(NSString *)key withValue:(NSString*)value { + NSDictionary *optionsMap = [self readOptionsForSelectedKeyboard]; + NSDictionary *newOptionsMap = nil; + // if we can read an existing options map, then add the specified key-value pair + if (optionsMap != nil) { + NSMutableDictionary *mutableOptionsMap = [optionsMap mutableCopy]; + [mutableOptionsMap setObject:value forKey:key]; + os_log_info([KMLogs dataLog], "writeOptionsForSelectedKeyboard, setting key: %{public}@, value %{public}@", key, value); + newOptionsMap = mutableOptionsMap; + } else { + newOptionsMap = [[NSDictionary alloc] initWithObjectsAndKeys:value, key, nil]; + } + + // write the fully built dictionary to the dictionary of options + NSString *selectedKeyboard = [self readSelectedKeyboard]; + [self writeKeyboardOptionsMap: selectedKeyboard withOptions:newOptionsMap]; +} + +- (void)writeKeyboardOptionsMap:(NSString *)keyboardName withOptions:(NSDictionary*) optionsMap { + NSDictionary *fullOptionsMap = [self readOptions]; + NSMutableDictionary *newFullOptionsMap = [fullOptionsMap mutableCopy]; + + [newFullOptionsMap setObject:optionsMap forKey:keyboardName]; + [self writeOptions:newFullOptionsMap]; +} + /** * For the first numbered version of the data model, the app stores the keyboards under the /Library directory * For versions before version 1, the keyboards were stored under the /Documents directory. @@ -83,11 +120,11 @@ - (BOOL)dataMigrationNeeded { return !(keymanSettingsExist && dataInLibrary); } -- (NSString *)selectedKeyboard { +- (NSString *)readSelectedKeyboard { return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; } -- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { +- (void)writeSelectedKeyboard:(NSString *)selectedKeyboard { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; } @@ -101,53 +138,61 @@ - (NSMutableArray *)activeKeyboards { return activeKeyboards; } -- (NSDictionary *)persistedOptions { +/* + * returns dictionary of persisted options for the single selected keyboard + */ +- (NSDictionary *)readOptionsForSelectedKeyboard { + NSDictionary *optionsMap = [self readOptions]; + NSString *selectedKeyboard = [self readSelectedKeyboard]; + NSDictionary *selectedOptionsMap = [optionsMap objectForKey: selectedKeyboard]; + if (selectedOptionsMap == nil) { + os_log_info([KMLogs dataLog], "no persisted options found in UserDefaults for keyboard %{public}@ ", selectedKeyboard); + } else { + for (NSString *key in optionsMap) { + NSString *value = [optionsMap objectForKey:key]; + os_log_info([KMLogs dataLog], "option for keyboard %{public}@ key: %{public}@, value %{public}@", selectedKeyboard, key, value); + } + } + return selectedOptionsMap; +} + +/* + * returns dictionary of all persisted options for all keyboards + */ +- (NSDictionary *)readOptions { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; return [userData dictionaryForKey:kPersistedOptionsKey]; } -- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { +- (void)writeOptions:(NSDictionary *) optionsDictionary { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; } -- (void)removePersistedOptions { +- (void)removeAllOptions { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; return [userData removeObjectForKey:kPersistedOptionsKey]; } - (void)convertSettingsForMigration { + os_log_debug([KMLogs dataLog], "converting settings in UserDefaults for migration"); [self convertSelectedKeyboardPathForMigration]; [self convertActiveKeyboardArrayForMigration]; - [self convertPersistedOptionsPathsForMigration]; + [self convertOptionsPathsForMigration]; } - (void)convertSelectedKeyboardPathForMigration { - NSString *selectedKeyboardPath = [self selectedKeyboard]; + NSString *selectedKeyboardPath = [self readSelectedKeyboard]; if (selectedKeyboardPath != nil) { NSString *newPathString = [self trimObsoleteKeyboardPath:selectedKeyboardPath]; if ([selectedKeyboardPath isNotEqualTo:newPathString]) { - [self saveSelectedKeyboard:newPathString]; + [self writeSelectedKeyboard:newPathString]; os_log_debug([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); } } } -/** - * Convert the path of the keyboard designating the Documents folder to its new location - * in the Application Support folder - */ -/* -- (NSString *)convertOldKeyboardPath:(NSString *)oldPath { - NSString *newPathString = @""; - if(oldPath != nil) { - newPathString = [oldPath stringByReplacingOccurrencesOfString:kObsoletePathComponent withString:kNewPathComponent]; - } - return newPathString; -} -*/ - /** * To convert the keyboard path for the new location, just trim the parent directory from the path * No need to repeatedly store the parent directory with the path of each keyboard @@ -190,8 +235,8 @@ - (void)convertActiveKeyboardArrayForMigration { } } -- (void)convertPersistedOptionsPathsForMigration { - NSDictionary * optionsMap = [self persistedOptions]; +- (void)convertOptionsPathsForMigration { + NSDictionary * optionsMap = [self readOptions]; NSMutableDictionary *mutableOptionsMap = nil; BOOL optionsChanged = NO; @@ -217,9 +262,29 @@ - (void)convertPersistedOptionsPathsForMigration { } } if (optionsChanged) { - [self savePersistedOptions:mutableOptionsMap]; + [self writeOptions:mutableOptionsMap]; } } } +- (BOOL)readAlwaysShowOsk { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData boolForKey:kAlwaysShowOSKKey]; +} + +- (void)writeAlwaysShowOsk:(BOOL)alwaysShowOsk { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setBool:alwaysShowOsk forKey:kAlwaysShowOSKKey]; +} + +- (BOOL)readUseVerboseLogging { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData boolForKey:kUseVerboseLogging]; +} + +- (void)writeUseVerboseLogging:(BOOL)verboseLogging { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setBool:verboseLogging forKey:kUseVerboseLogging]; +} + @end From b10bba7b254a6ea81e5ca73a8764ba016884847f Mon Sep 17 00:00:00 2001 From: Sabine Date: Fri, 16 Aug 2024 11:36:47 +0200 Subject: [PATCH 071/262] chore(linux): remove unused libxklavier dep --- linux/mcompile/keymap/meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/linux/mcompile/keymap/meson.build b/linux/mcompile/keymap/meson.build index 3866ca34f10..8107a3656a7 100644 --- a/linux/mcompile/keymap/meson.build +++ b/linux/mcompile/keymap/meson.build @@ -4,9 +4,8 @@ project('mcompile', 'c', 'cpp', gtk = dependency('gtk+-3.0', version: '>= 2.4') xkb = dependency('xkbcommon') -libxklavier = dependency('libxklavier') -deps = [gtk, xkb,libxklavier] +deps = [gtk, xkb] subdir('resources') From be55dabdc4c5dab3aba888de1c9248d459529117 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 16 Aug 2024 22:15:42 +1000 Subject: [PATCH 072/262] feat(windows): add right modifier key option pt1 --- common/windows/cpp/include/registry.h | 6 +- .../windows/delphi/general/RegistryKeys.pas | 3 +- .../delphi/tools/test-klog/test_klog.dpr | 8 +- .../delphi/tools/test-klog/test_klog.dproj | 4 + windows/src/desktop/kmshell/xml/strings.xml | 5 + windows/src/engine/keyman/UfrmKeyman7Main.pas | 4 +- .../keyman32/k32_lowlevelkeyboardhook.cpp | 96 +++++++------------ .../delphi/general/Keyman.System.Settings.pas | 22 +---- 8 files changed, 53 insertions(+), 95 deletions(-) diff --git a/common/windows/cpp/include/registry.h b/common/windows/cpp/include/registry.h index 67cdfc2f69e..ebaca81c9cf 100644 --- a/common/windows/cpp/include/registry.h +++ b/common/windows/cpp/include/registry.h @@ -123,11 +123,7 @@ /* REGSZ_Keyman_Debug DWORD: Use old non-chiral Win32 API RegisterHotkey instead of left-only hotkeys */ -#define REGSZ_Flag_UseRegisterHotkey "Flag_UseRegisterHotkey" - -/* REGSZ_Flag_UseCachedHotkeyModifierState DWORD: Use old cached modifier state when checking hotkeys; ignores UseRegisterHotkey if FALSE */ - -#define REGSZ_Flag_UseCachedHotkeyModifierState "Flag_UseCachedHotkeyModifierState" +#define REGSZ_Flag_UseRightModifierHotKey "Flag_UseRightModifierHotKey" /* DWORD: Enable/disable deep TSF integration, default enabled; 0 = disabled, 1 = enabled, 2 = default */ diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 47e7df754a0..8cbd6d7d0a3 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -379,8 +379,7 @@ interface SRegKey_KeymanEngineDebug_CU = SRegKey_KeymanEngineRoot_CU + '\Debug'; - SRegValue_Flag_UseRegisterHotkey = 'Flag_UseRegisterHotkey'; - SRegValue_Flag_UseCachedHotkeyModifierState = 'Flag_UseCachedHotkeyModifierState'; + SRegValue_Flag_UseRightModifierHotKey = 'Flag_UseRightModifierHotKey'; SRegValue_Flag_ShouldSerializeInput = 'Flag_ShouldSerializeInput'; SRegValue_Flag_UseAutoStartTask = 'Flag_UseAutoStartTask'; SRegValue_Flag_SyncLanguagesToCloud = 'Flag_SyncLanguagesToCloud'; diff --git a/common/windows/delphi/tools/test-klog/test_klog.dpr b/common/windows/delphi/tools/test-klog/test_klog.dpr index 072042334a0..7f9d0e213aa 100644 --- a/common/windows/delphi/tools/test-klog/test_klog.dpr +++ b/common/windows/delphi/tools/test-klog/test_klog.dpr @@ -7,13 +7,17 @@ uses klog in '..\..\..\..\..\common\windows\delphi\general\klog.pas', VersionInfo in '..\..\..\..\..\common\windows\delphi\general\VersionInfo.pas', ErrorControlledRegistry in '..\..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - Unicode in '..\..\..\..\..\common\windows\delphi\general\Unicode.pas'; + Unicode in '..\..\..\..\..\common\windows\delphi\general\Unicode.pas', + DebugPaths in '..\..\..\..\..\common\windows\delphi\general\DebugPaths.pas', + RegistryKeys in '..\..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', + KeymanVersion in '..\..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', + KeymanPaths in '..\..\..\..\..\common\windows\delphi\general\KeymanPaths.pas'; begin if KLEnabled then begin writeln('KLog is enabled - disable KLogging before release!'); - ExitCode := 1; + ExitCode := 0; end else begin diff --git a/common/windows/delphi/tools/test-klog/test_klog.dproj b/common/windows/delphi/tools/test-klog/test_klog.dproj index 27d8e8d7d5d..3a7cd7fa050 100644 --- a/common/windows/delphi/tools/test-klog/test_klog.dproj +++ b/common/windows/delphi/tools/test-klog/test_klog.dproj @@ -214,6 +214,10 @@ + + + + Cfg_2 Base diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 1e1df09ce00..8bdb8b0221e 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -377,6 +377,11 @@ Simulate AltGr with Ctrl+Alt + + + + Simulate AltGr with Ctrl+Alt + diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 5034c0e664f..4c14c375ed3 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -2050,7 +2050,7 @@ procedure TfrmKeyman7Main.RegisterHotkeys; language: IKeymanLanguage; id: Integer; begin - if not Reg_GetDebugFlag(SRegValue_Flag_UseRegisterHotkey) then Exit; + if not Reg_GetDebugFlag(SRegValue_Flag_UseRightModifierHotKey) then Exit; TDebugLogClient.Instance.WriteMessage('Enter RegisterHotkeys', []); @@ -2104,7 +2104,7 @@ procedure TfrmKeyman7Main.UnregisterHotkeys; var i, hk: Integer; begin - if not Reg_GetDebugFlag(SRegValue_Flag_UseRegisterHotkey) then Exit; + if not Reg_GetDebugFlag(SRegValue_Flag_UseRightModifierHotKey) then Exit; TDebugLogClient.Instance.WriteMessage('Enter UnregisterHotkeys', []); diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 019f88b0737..ce2ab3480e6 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -130,26 +130,16 @@ BOOL IsTouchPanelVisible() { } /* - Cache UseRegisterHotkey debug flag for this session + Cache UseRightModifierHotKey debug flag for this session */ -BOOL UseRegisterHotkey() { - static BOOL flag_UseRegisterHotkey = FALSE; +BOOL UseRightModifierHotKey() { + static BOOL flag_UseRightModifierHotKey = FALSE; static BOOL loaded = FALSE; if (!loaded) { loaded = TRUE; - flag_UseRegisterHotkey = Reg_GetDebugFlag(REGSZ_Flag_UseRegisterHotkey, FALSE); + flag_UseRightModifierHotKey = Reg_GetDebugFlag(REGSZ_Flag_UseRightModifierHotKey, FALSE); } - return flag_UseRegisterHotkey; -} - -BOOL UseCachedHotkeyModifierState() { - static BOOL flag_UseCachedHotkeyModifierState = FALSE; - static BOOL loaded = FALSE; - if (!loaded) { - loaded = TRUE; - flag_UseCachedHotkeyModifierState = Reg_GetDebugFlag(REGSZ_Flag_UseCachedHotkeyModifierState, FALSE); - } - return flag_UseCachedHotkeyModifierState; + return flag_UseRightModifierHotKey; } LRESULT _kmnLowLevelKeyboardProc( @@ -171,58 +161,38 @@ LRESULT _kmnLowLevelKeyboardProc( SendDebugMessageFormat("wparam: %x lparam: %x [vk:%s scan:%x flags:%x extra:%x]", wParam, lParam, Debug_VirtualKey((WORD) hs->vkCode), hs->scanCode, hs->flags, hs->dwExtraInfo); // I4674 - DWORD Flag = 0; - if (!UseCachedHotkeyModifierState()) { - // #5190: Don't cache modifier state because sometimes we won't receive - // modifier change events (e.g. on lock screen) - FHotkeyShiftState = 0; - if (GetKeyState(VK_LCONTROL) < 0) FHotkeyShiftState |= HK_CTRL; - if (GetKeyState(VK_RCONTROL) < 0) FHotkeyShiftState |= HK_RCTRL_INVALID; - if (GetKeyState(VK_LMENU) < 0) FHotkeyShiftState |= HK_ALT; - if (GetKeyState(VK_RMENU) < 0) FHotkeyShiftState |= HK_RALT_INVALID; - if (GetKeyState(VK_LSHIFT) < 0) FHotkeyShiftState |= HK_SHIFT; - if (GetKeyState(VK_RSHIFT) < 0) FHotkeyShiftState |= HK_RSHIFT_INVALID; - //TODO: #8064. Can remove debug message once issue #8064 is resolved - SendDebugMessageFormat("!UseCachedHotkeyModifierState [FHotkeyShiftState:%x Flag:%x]", FHotkeyShiftState, Flag); + if (GetKeyState(VK_LCONTROL) < 0) { + FHotkeyShiftState |= HK_CTRL; } - else if (UseRegisterHotkey()) { - // The old RegisterHotkey pattern does not support chiral modifier keys - switch (hs->vkCode) { - case VK_LCONTROL: - case VK_RCONTROL: - case VK_CONTROL: Flag = HK_CTRL; break; - case VK_LMENU: - case VK_RMENU: - case VK_MENU: Flag = HK_ALT; break; - case VK_LSHIFT: - case VK_RSHIFT: - case VK_SHIFT: Flag = HK_SHIFT; break; - } - //TODO: #8064. Can remove debug message once issue #8064 is resolved - SendDebugMessageFormat("UseRegisterHotkey [FHotkeyShiftState:%x Flag:%x]", FHotkeyShiftState, Flag); + if (GetKeyState(VK_RCONTROL) < 0) { + FHotkeyShiftState |= UseRightModifierHotKey() ? HK_CTRL : HK_RCTRL_INVALID; } - else { - // #4619: We differentiate between Left and Right Ctrl/Shift/Alt. The right modifiers are - // not used for hotkeys, leaving them available for use as keystroke modifiers within a - // Keyman keyboard. But we need to track them anyway, so that a user doesn't press them - // and receive a hotkey event when they shouldn't (e.g. RALT+F4 if the hotkey is F4). - switch (hs->vkCode) { - case VK_LCONTROL: Flag = HK_CTRL; break; - case VK_RCONTROL: Flag = HK_RCTRL_INVALID; break; - case VK_CONTROL: Flag = extended ? HK_RCTRL_INVALID : HK_CTRL; break; - case VK_LMENU: Flag = HK_ALT; break; - case VK_RMENU: Flag = HK_RALT_INVALID; break; - case VK_MENU: Flag = extended ? HK_RALT_INVALID : HK_ALT; break; - case VK_LSHIFT: Flag = HK_SHIFT; break; - case VK_RSHIFT: Flag = HK_RSHIFT_INVALID; break; - case VK_SHIFT: Flag = hs->scanCode == SCANCODE_RSHIFT ? HK_RSHIFT_INVALID : HK_SHIFT; break; - } + if (GetKeyState(VK_CONTROL) < 0) { + FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RCTRL_INVALID : HK_CTRL; + } + + if (GetKeyState(VK_LMENU) < 0) { + FHotkeyShiftState |= HK_ALT; + } + + if (GetKeyState(VK_RMENU) < 0) { + FHotkeyShiftState |= UseRightModifierHotKey() ? HK_ALT : HK_RALT_INVALID; + } + + if (GetKeyState(VK_MENU) < 0) { + FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RALT_INVALID : HK_ALT; + } + + if (GetKeyState(VK_LSHIFT) < 0) { + FHotkeyShiftState |= HK_SHIFT; + } + if (GetKeyState(VK_RSHIFT) < 0) { + FHotkeyShiftState |= UseRightModifierHotKey() ? HK_SHIFT : HK_RSHIFT_INVALID; } - if(Flag != 0) { - if(isUp) FHotkeyShiftState &= ~Flag; - else FHotkeyShiftState |= Flag; + if (GetKeyState(VK_SHIFT) < 0) { + FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RSHIFT_INVALID : HK_SHIFT; } // #7337 Post the modifier state ensuring the serialized queue is in sync @@ -304,7 +274,7 @@ LRESULT _kmnLowLevelKeyboardProc( } BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState) { - if (UseRegisterHotkey()) { + if (UseRightModifierHotKey()) { return FALSE; } diff --git a/windows/src/global/delphi/general/Keyman.System.Settings.pas b/windows/src/global/delphi/general/Keyman.System.Settings.pas index b021e56cddf..d8acd9d25a6 100644 --- a/windows/src/global/delphi/general/Keyman.System.Settings.pas +++ b/windows/src/global/delphi/general/Keyman.System.Settings.pas @@ -95,7 +95,7 @@ TKeymanSettings = class(TObjectList) ValueType: kstInteger ); - BaseKeymanSettings: array[0..33] of TKeymanSettingBase = ( + BaseKeymanSettings: array[0..31] of TKeymanSettingBase = ( // TIKE:UTikeDebugMode.TikeDebugMode ( @@ -370,14 +370,6 @@ TKeymanSettings = class(TObjectList) // keyman:TfrmKeyman7Main.RegisterHotkeys, // keyman:TfrmKeyman7Main.UnregisterHotkeys // keyman32:k32_lowlevelkeyboardhook - ( - ID: 'engine.compatibility.old_hotkey_registration'; - Name: SRegValue_Flag_UseRegisterHotkey; - RootKey: HKCU; - Key: SRegKey_KeymanEngineDebug_CU; - Description: 'Set to 1 for old RegisterHotkey pathway'; - ValueType: kstInteger - ), ( ID: 'engine.compatibility.use_keyman_core'; @@ -391,18 +383,6 @@ TKeymanSettings = class(TObjectList) ValueType: kstInteger ), - // keyman32:k32_lowlevelkeyboardhook - ( - ID: 'engine.compatibility.old_cached_hotkey_modifier_state'; - Name: SRegValue_Flag_UseCachedHotkeyModifierState; - RootKey: HKCU; - Key: SRegKey_KeymanEngineDebug_CU; - Description: 'Set to 1 to use a cached modifer state when checking hotkeys; if '+ - 'set to 0 then engine.compatibility.old_hotkey_registration may not '+ - 'work'; - ValueType: kstInteger - ), - // keyman32:keyman32.Initialise_Flag_ShouldSerializeInput ( ID: 'engine.compatibility.serialize_input'; From 4c9d44851a3061a34cea2a784077851d2096253b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 16 Aug 2024 15:09:23 +0200 Subject: [PATCH 073/262] chore(linux): add utfcodec.cpp to mcompile --- linux/mcompile/keymap/meson.build | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/linux/mcompile/keymap/meson.build b/linux/mcompile/keymap/meson.build index 8107a3656a7..c2ac9acc763 100644 --- a/linux/mcompile/keymap/meson.build +++ b/linux/mcompile/keymap/meson.build @@ -1,30 +1,33 @@ -project('mcompile', 'c', 'cpp', - license: 'MIT', - meson_version: '>=1.0') +project( + 'mcompile', 'c', 'cpp', + license: 'MIT', + meson_version: '>=1.0', +) -gtk = dependency('gtk+-3.0', version: '>= 2.4') -xkb = dependency('xkbcommon') +gtk = dependency('gtk+-3.0', version: '>= 2.4') +xkb = dependency('xkbcommon') -deps = [gtk, xkb] +deps = [gtk, xkb] subdir('resources') cpp_files = files( - 'keymap.cpp', - 'deadkey.cpp', - 'mcompile.cpp', - 'mc_kmxfile.cpp', - 'mc_import_rules.cpp', - '../../../common/cpp/km_u16.cpp', - ) + 'keymap.cpp', + 'deadkey.cpp', + 'mcompile.cpp', + 'mc_kmxfile.cpp', + 'mc_import_rules.cpp', + '../../../common/cpp/km_u16.cpp', + '../../../common/cpp/utfcodec.cpp', +) comon_include_dir = [ include_directories('../../../common/include') ] mcompile = executable( - 'mcompile', - sources: [cpp_files], - dependencies: deps, - include_directories : comon_include_dir - ) + 'mcompile', + sources: [cpp_files], + dependencies: deps, + include_directories : comon_include_dir +) From 1550db1eb8f5286dcf51df064393694fdc227a67 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 16 Aug 2024 14:05:09 -0400 Subject: [PATCH 074/262] auto: increment master version to 18.0.92 --- HISTORY.md | 8 ++++++++ VERSION.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f9117038345..b58c086466b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # Keyman Version History +## 18.0.91 alpha 2024-08-16 + +* refactor(web): move `predictive-text` → `worker-main` (#12146) +* fix(web): restore flick functionality (#12187) +* refactor(web): move `lm-message-types` → `predictive-text/types` (#12149) +* fix(developer): enforce presence of kps Info.Description field in info compilers (#12204) +* fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler (#12205) + ## 18.0.90 alpha 2024-08-15 * refactor(web): move parts of `keyboard-processor` → `js-processor` (#12111) diff --git a/VERSION.md b/VERSION.md index 8f233fffc08..ce8cfb73ead 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.91 \ No newline at end of file +18.0.92 \ No newline at end of file From 8cc506ea186cd8bc9364b1b0996122240ea8634e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 19 Aug 2024 09:44:19 +0700 Subject: [PATCH 075/262] fix(android): Address review comments * Have KMManager control the longpress delay * Rename applyLongpressDelay to sendOptionsToKeyboard --- .../com/keyman/android/SystemKeyboard.java | 5 +-- .../kmapro/AdjustLongpressDelayActivity.java | 39 ++++++++++--------- .../com/tavultesoft/kmapro/MainActivity.java | 9 +---- .../KMEA/app/src/main/assets/android-host.js | 2 + .../java/com/keyman/engine/KMManager.java | 35 ++++++++--------- 5 files changed, 42 insertions(+), 48 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index dea132c0fd3..df71220cfc2 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -245,9 +245,8 @@ public void onKeyboardLoaded(KeyboardType keyboardType) { if (exText != null) exText = null; } - // Initialize the longpress delay - int longpressDelay = KMManager.getLongpressDelay(); - KMManager.applyLongpressDelay(longpressDelay); + // Initialize keyboard options + KMManager.sendOptionsToKeyboard(); } @Override diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index 55a663965c5..e3f2dc3d926 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -28,21 +28,21 @@ public class AdjustLongpressDelayActivity extends BaseActivity { // Keeps track of the adjusted longpress delay time for saving. // Internally use milliseconds, but GUI displays seconds - private static int currentDelayTime = KMManager.KMDefault_LongpressDelay; // ms + private static int currentDelayTimeMS = KMManager.KMDefault_LongpressDelay; // ms private static int minLongpressTime = 300; // ms private static int maxLongpressTime = 1500; // ms private static int delayTimeIncrement = 200; // ms /** - * Convert currentDelayTime to progress + * Convert currentDelayTimeMS to progress * @return int */ private int delayTimeToProgress() { - return (currentDelayTime / delayTimeIncrement) - 1; + return (currentDelayTimeMS / delayTimeIncrement) - 1; } /** - * Convert progress to currentDelayTime + * Convert progress to currentDelayTimeMS * @param progress * @return int (milliseconds) */ @@ -76,12 +76,12 @@ protected void onCreate(Bundle savedInstanceState) { adjustLongpressDelayActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); adjustLongpressDelayActivityTitle.setText(titleStr); - currentDelayTime = KMManager.getLongpressDelay(); + currentDelayTimeMS = KMManager.getLongpressDelay(); TextView adjustLongpressDelayText = (TextView) findViewById(R.id.delayTimeText); - String longpressDelayText = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTime/1000.0)); + String longpressDelayTextSeconds = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTimeMS/1000.0)); adjustLongpressDelayText.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - adjustLongpressDelayText.setText(longpressDelayText); + adjustLongpressDelayText.setText(longpressDelayTextSeconds); final SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar); seekBar.setProgress(delayTimeToProgress()); @@ -99,19 +99,19 @@ public void onStartTrackingTouch(SeekBar seekBar) { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - currentDelayTime = progressToDelayTime(progress); - String longpressDelayText = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTime/1000.0)); - adjustLongpressDelayText.setText(longpressDelayText); - - KMManager.setLongpressDelay(currentDelayTime); + // Update the text field. + // The keyboard options will be saved and sent to KeymanWeb when exiting the menu + currentDelayTimeMS = progressToDelayTime(progress); + String longpressDelayTextSeconds = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTimeMS/1000.0)); + adjustLongpressDelayText.setText(longpressDelayTextSeconds); } }); findViewById(R.id.delayTimeDownButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (currentDelayTime > minLongpressTime) { - currentDelayTime -= delayTimeIncrement; + if (currentDelayTimeMS > minLongpressTime) { + currentDelayTimeMS -= delayTimeIncrement; seekBar.setProgress(delayTimeToProgress()); } } @@ -120,8 +120,8 @@ public void onClick(View v) { findViewById(R.id.delayTimeUpButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (currentDelayTime < maxLongpressTime) { - currentDelayTime += delayTimeIncrement; + if (currentDelayTimeMS < maxLongpressTime) { + currentDelayTimeMS += delayTimeIncrement; seekBar.setProgress(delayTimeToProgress()); } } @@ -130,9 +130,10 @@ public void onClick(View v) { @Override public void onBackPressed() { - // setLongpressDelay stores the longpress delay as a preference - // and then updates KeymanWeb with the longpress delay - KMManager.setLongpressDelay(currentDelayTime); + // Store the longpress delay as a reference + // and then update KeymanWeb with the longpress delay + KMManager.setLongpressDelay(currentDelayTimeMS); + KMManager.sendOptionsToKeyboard(); super.onBackPressed(); } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index fe3dc9b6c8a..bcc133ea819 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -472,7 +472,8 @@ public boolean onKeyUp(int keycode, KeyEvent e) { @Override public void onKeyboardLoaded(KeyboardType keyboardType) { - checkLongpressDelay(); + // Initialize keyboard options + KMManager.sendOptionsToKeyboard(); } @Override @@ -750,12 +751,6 @@ private void checkSendCrashReport() { KMManager.setMaySendCrashReport(maySendCrashReport); } - private void checkLongpressDelay() { - // Initialize the longpress delay - int longpressDelay = KMManager.getLongpressDelay(); - KMManager.applyLongpressDelay(longpressDelay); - } - private void checkHapticFeedback() { SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index b09dbf8beb8..62d396aae0b 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -118,6 +118,8 @@ function setLongpressDelay(delay) { if (keyman.osk) { keyman.osk.gestureParams.longpress.waitLength = delay; console.debug('setLongpressDelay('+delay+')'); + } else { + window.console.log('setLongpressDelay error: keyman.osk undefined'); } } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 348e3136dd3..68e686497f3 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2027,24 +2027,7 @@ public static int getLongpressDelay() { } /** - * Set the number of milliseconds to trigger a longpress gesture. - * - * This method requires a keyboard to be loaded for the value to take effect. - * @param longpressDelay - int longpress delay in milliseconds - */ - public static void applyLongpressDelay(int longpressDelay) { - if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) { - InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); - } - - if (SystemKeyboard != null) { - SystemKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); - } - } - - /** - * Store the long-press delay (in milliseconds) as a preference. - * Then update KeymanWeb with the longpress delay + * Set the longpress delay (in milliseconds) as a stored preference. * @param longpressDelay - int longpress delay in milliseconds */ public static void setLongpressDelay(int longpressDelay) { @@ -2053,8 +2036,22 @@ public static void setLongpressDelay(int longpressDelay) { SharedPreferences.Editor editor = prefs.edit(); editor.putInt(KMKey_LongpressDelay, longpressDelay); editor.commit(); + } + + /** + * Sends options to the KeymanWeb keyboard. + * 1. number of milliseconds to trigger a longpress gesture. + * This method requires a keyboard to be loaded for the value to take effect. + */ + public static void sendOptionsToKeyboard() { + int longpressDelay = getLongpressDelay(); + if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) { + InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); + } - applyLongpressDelay(longpressDelay); + if (SystemKeyboard != null) { + SystemKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay)); + } } public static int getBannerHeight(Context context) { From b7b17686ece2b29c43889131011aa89205401f11 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:01:43 +1000 Subject: [PATCH 076/262] feat(windows): add rmodifier hk to keyman config UI --- common/windows/cpp/include/registry.h | 6 ++++-- common/windows/delphi/general/RegistryKeys.pas | 2 +- windows/src/desktop/kmshell/xml/strings.xml | 2 +- .../src/engine/keyman32/k32_lowlevelkeyboardhook.cpp | 10 ++++++++-- windows/src/engine/kmcomapi/util/utilkeymanoption.pas | 1 + .../src/global/delphi/general/KeymanOptionNames.pas | 5 ++++- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/common/windows/cpp/include/registry.h b/common/windows/cpp/include/registry.h index ebaca81c9cf..c6d1bf85ec1 100644 --- a/common/windows/cpp/include/registry.h +++ b/common/windows/cpp/include/registry.h @@ -110,6 +110,10 @@ #define REGSZ_KeyboardHotkeysAreToggle "hotkeys are toggles" #define REGSZ_DeadkeyConversionMode "deadkey conversion mode" // CU // I4552 #define REGSZ_ZapVirtualKeyCode "zap virtual key code" // LM, defaults to 0x0E (_VK_PREFIX_DEFAULT) +/* Non-chiral use of hotkeys instead of left-only hotkeys */ +#define REGSZ_UseRightModifierHotKey "use right modifier for hotkey" + + /* Debug flags These are all stored in HKCU\Software\Keyman\Debug @@ -121,9 +125,7 @@ #define REGSZ_Flag_ShouldSerializeInput "Flag_ShouldSerializeInput" -/* REGSZ_Keyman_Debug DWORD: Use old non-chiral Win32 API RegisterHotkey instead of left-only hotkeys */ -#define REGSZ_Flag_UseRightModifierHotKey "Flag_UseRightModifierHotKey" /* DWORD: Enable/disable deep TSF integration, default enabled; 0 = disabled, 1 = enabled, 2 = default */ diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 8cbd6d7d0a3..0553b823065 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -115,6 +115,7 @@ interface SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU + SRegValue_UseRightModifierHotKey = 'use right modifier for hotkey'; // CU SRegValue_ReleaseShiftKeysAfterKeyPress = 'release shift keys after key press'; // CU SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true @@ -379,7 +380,6 @@ interface SRegKey_KeymanEngineDebug_CU = SRegKey_KeymanEngineRoot_CU + '\Debug'; - SRegValue_Flag_UseRightModifierHotKey = 'Flag_UseRightModifierHotKey'; SRegValue_Flag_ShouldSerializeInput = 'Flag_ShouldSerializeInput'; SRegValue_Flag_UseAutoStartTask = 'Flag_UseAutoStartTask'; SRegValue_Flag_SyncLanguagesToCloud = 'Flag_SyncLanguagesToCloud'; diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 8bdb8b0221e..e15ba789453 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -380,7 +380,7 @@ - Simulate AltGr with Ctrl+Alt + Right Modifier keys work with Hotkeys diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index ce2ab3480e6..66d7d943aba 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -135,9 +135,15 @@ BOOL IsTouchPanelVisible() { BOOL UseRightModifierHotKey() { static BOOL flag_UseRightModifierHotKey = FALSE; static BOOL loaded = FALSE; + if (!loaded) { - loaded = TRUE; - flag_UseRightModifierHotKey = Reg_GetDebugFlag(REGSZ_Flag_UseRightModifierHotKey, FALSE); + RegistryReadOnly reg(HKEY_CURRENT_USER); + if (reg.OpenKeyReadOnly(REGSZ_KeymanCU)) { + if (reg.ValueExists(REGSZ_UseRightModifierHotKey)) { + flag_UseRightModifierHotKey = !!reg.ReadInteger(REGSZ_UseRightModifierHotKey); + } + } + loaded = TRUE; // Set loaded to TRUE whether or not the key exists } return flag_UseRightModifierHotKey; } diff --git a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas index ce1b8af33a8..9d9f95465d4 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -127,6 +127,7 @@ TKeymanOptionInfo = record (opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koSwitchLanguageForAllApplications; RegistryName: SRegValue_SwitchLanguageForAllApplications; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I2277 // I4393 (opt: koAltGrCtrlAlt; RegistryName: SRegValue_AltGrCtrlAlt; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), + (opt: koRightModifierHK; RegistryName: SRegValue_UseRightModifierHotKey; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koShowHints; RegistryName: SRegValue_EnableHints; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), (opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'), diff --git a/windows/src/global/delphi/general/KeymanOptionNames.pas b/windows/src/global/delphi/general/KeymanOptionNames.pas index 27aef82ab55..73356d19cb3 100644 --- a/windows/src/global/delphi/general/KeymanOptionNames.pas +++ b/windows/src/global/delphi/general/KeymanOptionNames.pas @@ -5,7 +5,10 @@ interface type TUtilKeymanOption = ( // General options - koKeyboardHotkeysAreToggle, koAltGrCtrlAlt, koReleaseShiftKeysAfterKeyPress, + koKeyboardHotkeysAreToggle, + koAltGrCtrlAlt, + koRightModifierHK, + koReleaseShiftKeysAfterKeyPress, koShowHints, // I1256 // Startup options koTestKeymanFunctioning, From c712d35984d4bad2d515a2852953af17d804ffe5 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Mon, 19 Aug 2024 12:16:04 +0700 Subject: [PATCH 077/262] chore(ios): adjustments per review --- .../KeymanEngine/Classes/Resource Data/FontManager.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift index 9f0047d7f7b..6070de2c96f 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift @@ -56,10 +56,15 @@ public class FontManager { /** * Iterates across all listed fonts, registering those not yet registered. * Checks for, and filters out, any fonts already registered on the system. + * Also initializes the registration cache per URL if needed. */ private func registerListedFonts(_ initialFontSet: Set) { // If we are unable to read the font file's properties sufficiently, - // skip it. We also don't need to register anything already registered. + // skip it. We also don't need to register anything already registered or + // that cannot be registered due to loading/parsing errors. + // + // Calls to `cachedFont` after the `.filter` below may be assumed to have + // non-nil return values. var fontSet = initialFontSet.filter { !(cachedFont(at: $0)?.isRegistered ?? true) } // The prior line filters out any entries where cachedFont(at: $0) would be nil. @@ -195,7 +200,6 @@ public class FontManager { * Only fonts that could not be found will be returned within the result set. */ private func missingFonts(from fontNames: Set) -> Set { - // Arrays are treated 'by value'; it's already a shallow copy. var fontsToFind = fontNames UIFont.familyNames.forEach { familyName in From 49bfd08fb3617386dbf3b7d93dbc52811cedac93 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Mon, 19 Aug 2024 12:49:46 +0700 Subject: [PATCH 078/262] change(mac): persist options fix when dictionary exists testing showed that when an option already existed, it would be deleted when attempting to update it to a new value --- .../Keyman4MacIM/KMSettingsRepository.m | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 83b16a7e8bb..752ea86b660 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -72,27 +72,38 @@ - (BOOL)settingsExist - (void)writeOptionForSelectedKeyboard:(NSString *)key withValue:(NSString*)value { NSDictionary *optionsMap = [self readOptionsForSelectedKeyboard]; NSDictionary *newOptionsMap = nil; + // if we can read an existing options map, then add the specified key-value pair if (optionsMap != nil) { NSMutableDictionary *mutableOptionsMap = [optionsMap mutableCopy]; [mutableOptionsMap setObject:value forKey:key]; - os_log_info([KMLogs dataLog], "writeOptionsForSelectedKeyboard, setting key: %{public}@, value %{public}@", key, value); newOptionsMap = mutableOptionsMap; } else { + // if no options map exists, create a new one add the specified key-value pair newOptionsMap = [[NSDictionary alloc] initWithObjectsAndKeys:value, key, nil]; } - // write the fully built dictionary to the dictionary of options + // write the options map for the selected keyboard to the dictionary of options NSString *selectedKeyboard = [self readSelectedKeyboard]; + os_log_info([KMLogs dataLog], "writeOptionForSelectedKeyboard, adding options map: %{public}@, to keyboard %{public}@", newOptionsMap, selectedKeyboard); [self writeKeyboardOptionsMap: selectedKeyboard withOptions:newOptionsMap]; } - (void)writeKeyboardOptionsMap:(NSString *)keyboardName withOptions:(NSDictionary*) optionsMap { - NSDictionary *fullOptionsMap = [self readOptions]; - NSMutableDictionary *newFullOptionsMap = [fullOptionsMap mutableCopy]; - - [newFullOptionsMap setObject:optionsMap forKey:keyboardName]; - [self writeOptions:newFullOptionsMap]; + NSMutableDictionary *newFullOptionsMap = nil; + os_log_debug([KMLogs dataLog], "writeKeyboardOptionsMap, adding options map: %{public}@, to keyboard %{public}@", optionsMap, keyboardName); + + NSDictionary *fullOptionsMap = [self readFullOptionsMap]; + // if we can read the existing full options map, then add for the specified keyboard + if (fullOptionsMap != nil) { + newFullOptionsMap = [fullOptionsMap mutableCopy]; + [newFullOptionsMap setObject:optionsMap forKey:keyboardName]; + } else { + // otherwise, create the full options map and add for the specified keyboard + newFullOptionsMap = [[NSMutableDictionary alloc] initWithObjectsAndKeys:optionsMap, keyboardName, nil]; + } + + [self writeFullOptionsMap:newFullOptionsMap]; } /** @@ -142,14 +153,14 @@ - (NSMutableArray *)activeKeyboards { * returns dictionary of persisted options for the single selected keyboard */ - (NSDictionary *)readOptionsForSelectedKeyboard { - NSDictionary *optionsMap = [self readOptions]; + NSDictionary *optionsMap = [self readFullOptionsMap]; NSString *selectedKeyboard = [self readSelectedKeyboard]; NSDictionary *selectedOptionsMap = [optionsMap objectForKey: selectedKeyboard]; if (selectedOptionsMap == nil) { os_log_info([KMLogs dataLog], "no persisted options found in UserDefaults for keyboard %{public}@ ", selectedKeyboard); } else { - for (NSString *key in optionsMap) { - NSString *value = [optionsMap objectForKey:key]; + for (NSString *key in selectedOptionsMap) { + NSString *value = [selectedOptionsMap objectForKey:key]; os_log_info([KMLogs dataLog], "option for keyboard %{public}@ key: %{public}@, value %{public}@", selectedKeyboard, key, value); } } @@ -158,15 +169,16 @@ - (NSDictionary *)readOptionsForSelectedKeyboard { /* * returns dictionary of all persisted options for all keyboards + * (options are stored in UserDefaults as a map of maps) */ -- (NSDictionary *)readOptions { +- (NSDictionary *)readFullOptionsMap { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; return [userData dictionaryForKey:kPersistedOptionsKey]; } -- (void)writeOptions:(NSDictionary *) optionsDictionary { +- (void)writeFullOptionsMap:(NSDictionary *) fullOptionsMap { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; + [userData setObject:fullOptionsMap forKey:kPersistedOptionsKey]; } - (void)removeAllOptions { @@ -236,7 +248,7 @@ - (void)convertActiveKeyboardArrayForMigration { } - (void)convertOptionsPathsForMigration { - NSDictionary * optionsMap = [self readOptions]; + NSDictionary * optionsMap = [self readFullOptionsMap]; NSMutableDictionary *mutableOptionsMap = nil; BOOL optionsChanged = NO; @@ -262,7 +274,7 @@ - (void)convertOptionsPathsForMigration { } } if (optionsChanged) { - [self writeOptions:mutableOptionsMap]; + [self writeFullOptionsMap:mutableOptionsMap]; } } } From 98d5322e51d473b5ce14e294a890a3c4c4284910 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 19 Aug 2024 12:53:51 +0700 Subject: [PATCH 079/262] fix(web): disable fat-finger data use when mayCorrect = false --- web/src/engine/main/src/keymanEngine.ts | 4 ++++ web/src/engine/osk/src/visualKeyboard.ts | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index 30160e683c8..c2426560663 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -61,6 +61,10 @@ export default class KeymanEngine< return; } + if(!this.core.languageProcessor.mayCorrect) { + event.keyDistribution = []; + } + if(this.keyEventRefocus) { // Do anything needed to guarantee that the outputTarget stays active (`app/browser`: maintains focus). // (Interaction with the OSK may have de-focused the element providing active context; diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 26b269d7f26..013ad76241e 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -929,13 +929,6 @@ export default class VisualKeyboard extends EventEmitter implements Ke * @returns */ getSimpleTapCorrectionDistances(input: InputSample, keySpec?: ActiveKey): Map { - // TODO: It'd be nice to optimize by keeping these off when unused, but the wiring - // necessary would get in the way of modularization at the moment. - // let keyman = com.keyman.singleton; - // if (!keyman.core.languageProcessor.mayCorrect) { - // return null; - // } - // Note: if subkeys are active, they will still be displayed at this time. let touchKbdPos = this.getTouchCoordinatesOnKeyboard(input); let layerGroup = this.layerGroup.element; // Always has proper dimensions, unlike kbdDiv itself. From ac0f4f6a83297f6bd2cac53590427a6dc73c8b90 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 19 Aug 2024 10:08:36 +0200 Subject: [PATCH 080/262] refactor(web): run npm install Addresses code review comments --- package-lock.json | 99 +++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8df9c941c25..d87ca37496c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,25 +108,6 @@ "typescript": "^5.4.5" } }, - "web/src/engine/predictive-text/worker-main": { - "name": "@keymanapp/lexical-model-layer", - "license": "MIT", - "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-templates": "*", - "@keymanapp/models-wordbreakers": "*", - "@keymanapp/web-utils": "*", - "es6-shim": "^0.35.5", - "string.prototype.codepointat": "^0.2.1" - }, - "devDependencies": { - "@keymanapp/models-types": "*", - "@keymanapp/resources-gosh": "*", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^5.4.5" - } - }, "common/test/resources": { "name": "@keymanapp/common-test-resources", "license": "MIT", @@ -198,36 +179,6 @@ "typescript": "^5.4.5" } }, - "web/src/engine/predictive-text/types": { - "name": "@keymanapp/lm-message-types", - "license": "MIT", - "devDependencies": { - "typescript": "^5.4.5" - } - }, - "web/src/engine/predictive-text/worker-thread": { - "name": "@keymanapp/lm-worker", - "license": "MIT", - "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-templates": "*", - "@keymanapp/models-wordbreakers": "*", - "@keymanapp/web-utils": "*", - "es6-shim": "^0.35.5", - "string.prototype.codepointat": "^0.2.1", - "string.prototype.startswith": "^0.2.0" - }, - "devDependencies": { - "@keymanapp/common-test-resources": "*", - "@keymanapp/models-types": "*", - "@keymanapp/resources-gosh": "*", - "c8": "^7.12.0", - "combine-source-map": "^0.8.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^5.4.5" - } - }, "common/web/sentry-manager": { "name": "@keymanapp/web-sentry-manager", "license": "MIT", @@ -14819,11 +14770,59 @@ "mocha": "^10.0.0" } }, + "web/src/engine/predictive-text/types": { + "name": "@keymanapp/lm-message-types", + "license": "MIT", + "devDependencies": { + "typescript": "^5.4.5" + } + }, + "web/src/engine/predictive-text/worker-main": { + "name": "@keymanapp/lexical-model-layer", + "license": "MIT", + "dependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/models-templates": "*", + "@keymanapp/models-wordbreakers": "*", + "@keymanapp/web-utils": "*", + "es6-shim": "^0.35.5", + "string.prototype.codepointat": "^0.2.1" + }, + "devDependencies": { + "@keymanapp/models-types": "*", + "@keymanapp/resources-gosh": "*", + "mocha": "^10.0.0", + "mocha-teamcity-reporter": "^4.0.0", + "typescript": "^5.4.5" + } + }, + "web/src/engine/predictive-text/worker-thread": { + "name": "@keymanapp/lm-worker", + "license": "MIT", + "dependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/models-templates": "*", + "@keymanapp/models-wordbreakers": "*", + "@keymanapp/web-utils": "*", + "es6-shim": "^0.35.5", + "string.prototype.codepointat": "^0.2.1", + "string.prototype.startswith": "^0.2.0" + }, + "devDependencies": { + "@keymanapp/common-test-resources": "*", + "@keymanapp/models-types": "*", + "@keymanapp/resources-gosh": "*", + "c8": "^7.12.0", + "combine-source-map": "^0.8.0", + "mocha": "^10.0.0", + "mocha-teamcity-reporter": "^4.0.0", + "typescript": "^5.4.5" + } + }, "web/src/tools/testing/recorder-core": { "name": "@keymanapp/recorder-core", "license": "MIT", "dependencies": { - "@keymanapp/keyboard-processor": "*", "@keymanapp/keyman-version": "*", "@keymanapp/models-types": "*", "@keymanapp/web-utils": "*" From 65b9e49bac88976beedb9667ab7c010ede2e6cc3 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:48:06 +1000 Subject: [PATCH 081/262] feat(windows): remove old UseRegisterHotkey --- windows/src/engine/keyman/UfrmKeyman7Main.pas | 2 -- windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp | 3 --- 2 files changed, 5 deletions(-) diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 4c14c375ed3..155ab9208c0 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -2050,7 +2050,6 @@ procedure TfrmKeyman7Main.RegisterHotkeys; language: IKeymanLanguage; id: Integer; begin - if not Reg_GetDebugFlag(SRegValue_Flag_UseRightModifierHotKey) then Exit; TDebugLogClient.Instance.WriteMessage('Enter RegisterHotkeys', []); @@ -2104,7 +2103,6 @@ procedure TfrmKeyman7Main.UnregisterHotkeys; var i, hk: Integer; begin - if not Reg_GetDebugFlag(SRegValue_Flag_UseRightModifierHotKey) then Exit; TDebugLogClient.Instance.WriteMessage('Enter UnregisterHotkeys', []); diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 66d7d943aba..6d5067516e9 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -280,9 +280,6 @@ LRESULT _kmnLowLevelKeyboardProc( } BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState) { - if (UseRightModifierHotKey()) { - return FALSE; - } Hotkeys *hotkeys = Hotkeys::Instance(); // I4641 if (!hotkeys) { From 7c0f51c486c79e89f5f8b5e87f58df2965b804e4 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 19 Aug 2024 10:22:10 -0500 Subject: [PATCH 082/262] fix(core): set mac build version for meson cli build to 10.13 - add as option to compiler - Match version in #11302 Fixes: #11423 --- core/cross-mac-arm64.build | 6 +++--- core/cross-mac-x86_64.build | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/cross-mac-arm64.build b/core/cross-mac-arm64.build index 720e9f31007..faf3e6e9c15 100644 --- a/core/cross-mac-arm64.build +++ b/core/cross-mac-arm64.build @@ -5,9 +5,9 @@ cpu = 'arm64' endian = 'little' [binaries] -c = ['clang', '-arch', 'arm64'] -objc = ['clang', '-arch', 'arm64'] -cpp = ['clang++', '-arch', 'arm64'] +c = ['clang', '-arch', 'arm64', '-mmacosx-version-min=10.13'] +objc = ['clang', '-arch', 'arm64', '-mmacosx-version-min=10.13'] +cpp = ['clang++', '-arch', 'arm64', '-mmacosx-version-min=10.13'] ar = 'ar' ld = 'ld' strip = 'strip' diff --git a/core/cross-mac-x86_64.build b/core/cross-mac-x86_64.build index 2ab2c62a600..3378fe11a40 100644 --- a/core/cross-mac-x86_64.build +++ b/core/cross-mac-x86_64.build @@ -5,9 +5,9 @@ cpu = 'x86_64' endian = 'little' [binaries] -c = ['clang', '-arch', 'x86_64'] -objc = ['clang', '-arch', 'x86_64'] -cpp = ['clang++', '-arch', 'x86_64'] +c = ['clang', '-arch', 'x86_64', '-mmacosx-version-min=10.13'] +objc = ['clang', '-arch', 'x86_64', '-mmacosx-version-min=10.13'] +cpp = ['clang++', '-arch', 'x86_64', '-mmacosx-version-min=10.13'] ar = 'ar' ld = 'ld' strip = 'strip' From 6e7fcba318bd392e9ac9f54f4edd6e496832f959 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 19 Aug 2024 14:03:29 -0400 Subject: [PATCH 083/262] auto: increment master version to 18.0.93 --- HISTORY.md | 9 +++++++++ VERSION.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index b58c086466b..5d82f22e40a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Keyman Version History +## 18.0.92 alpha 2024-08-19 + +* chore(deps-dev): bump @75lb/deep-merge from 1.1.1 to 1.1.2 (#12118) +* chore(deps): bump semver from 7.5.4 to 7.6.0 (#12119) +* fix(windows): "Keyman" is not localized in UI strings (#12162) +* feat(android): Enhance how ENTER key is handled in apps (#12125) +* refactor(web): move `lm-worker` → `worker-thread` (#12150) +* fix(developer): remove redundant check in LdmlKeyboardCompiler.validate() (#11858) + ## 18.0.91 alpha 2024-08-16 * refactor(web): move `predictive-text` → `worker-main` (#12146) diff --git a/VERSION.md b/VERSION.md index ce8cfb73ead..f74f9c87c90 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.92 \ No newline at end of file +18.0.93 \ No newline at end of file From e303cdf8018482c750c4b51f79450686240c1daf Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 20 Aug 2024 06:08:31 +0700 Subject: [PATCH 084/262] chore(android): Replace paddingLeft/Right with Start/End --- .../kMAPro/src/main/res/layout/web_browser_bar_layout.xml | 2 -- android/KMEA/app/src/main/res/layout/help_bubble_layout.xml | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml index 2e9b059c55f..749412e0676 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml @@ -15,7 +15,6 @@ android:layout_height="match_parent" android:layout_margin="3dp" android:padding="4dp" - android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_above="@+id/progressBar" android:weightSum="2" @@ -76,7 +75,6 @@ style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="@dimen/keyman_bar_height" - android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:visibility="visible" diff --git a/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml b/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml index 953c74e6924..16e2c3d47a0 100644 --- a/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml +++ b/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml @@ -15,8 +15,8 @@ android:layout_gravity="center" android:gravity="center" android:paddingBottom="10dp" - android:paddingLeft="2dp" - android:paddingRight="2dp" + android:paddingStart="2dp" + android:paddingEnd="2dp" android:text="@string/help_bubble_text" android:textColor="#404040" android:textSize="12sp" /> From 977d2948e6400e1c3524629818d61ed39ac07581 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 20 Aug 2024 10:06:04 +1000 Subject: [PATCH 085/262] feat(windows): fix out of bounds --- windows/src/engine/kmcomapi/util/utilkeymanoption.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas index 9d9f95465d4..2df38b5c825 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,7 +121,7 @@ TKeymanOptionInfo = record GroupName: string; end; -const KeymanOptionInfo: array[0..15] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 +const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 // Global options (opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), From 98787afbdcacf7a8e1a4f9e64e929fd3948f7007 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:15:50 +1000 Subject: [PATCH 086/262] feat(windows): restore removal of klogging --- common/windows/delphi/tools/test-klog/test_klog.dpr | 8 ++------ common/windows/delphi/tools/test-klog/test_klog.dproj | 4 ---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/common/windows/delphi/tools/test-klog/test_klog.dpr b/common/windows/delphi/tools/test-klog/test_klog.dpr index 7f9d0e213aa..072042334a0 100644 --- a/common/windows/delphi/tools/test-klog/test_klog.dpr +++ b/common/windows/delphi/tools/test-klog/test_klog.dpr @@ -7,17 +7,13 @@ uses klog in '..\..\..\..\..\common\windows\delphi\general\klog.pas', VersionInfo in '..\..\..\..\..\common\windows\delphi\general\VersionInfo.pas', ErrorControlledRegistry in '..\..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - Unicode in '..\..\..\..\..\common\windows\delphi\general\Unicode.pas', - DebugPaths in '..\..\..\..\..\common\windows\delphi\general\DebugPaths.pas', - RegistryKeys in '..\..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', - KeymanVersion in '..\..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', - KeymanPaths in '..\..\..\..\..\common\windows\delphi\general\KeymanPaths.pas'; + Unicode in '..\..\..\..\..\common\windows\delphi\general\Unicode.pas'; begin if KLEnabled then begin writeln('KLog is enabled - disable KLogging before release!'); - ExitCode := 0; + ExitCode := 1; end else begin diff --git a/common/windows/delphi/tools/test-klog/test_klog.dproj b/common/windows/delphi/tools/test-klog/test_klog.dproj index 3a7cd7fa050..27d8e8d7d5d 100644 --- a/common/windows/delphi/tools/test-klog/test_klog.dproj +++ b/common/windows/delphi/tools/test-klog/test_klog.dproj @@ -214,10 +214,6 @@ - - - - Cfg_2 Base From c9bc396e91752e9c5636673219c9dd4c560f8eda Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 20 Aug 2024 08:08:49 +0700 Subject: [PATCH 087/262] fix(android): Auto-mirror back and forward arrows for RTL support --- .../kmapro/KeyboardSettingsActivity.java | 2 +- .../kmapro/LanguageSettingsActivity.java | 6 +++--- .../kmapro/LanguagesSettingsActivity.java | 2 +- .../tavultesoft/kmapro/SelectPackageActivity.java | 2 +- .../src/main/res/drawable/ic_action_back.xml | 4 ---- .../kMAPro/src/main/res/layout/activity_info.xml | 5 +++-- .../src/main/res/layout/activity_web_browser.xml | 4 ++-- .../main/res/layout/preference_icon_layout.xml | 2 +- .../com/keyman/engine/KeyboardInfoActivity.java | 2 +- .../java/com/keyman/engine/ModelInfoActivity.java | 2 +- .../com/keyman/engine/ModelPickerActivity.java | 2 +- .../src/main/res/drawable-hdpi/ic_arrow_back.png | Bin 0 -> 321 bytes .../src/main/res/drawable-mdpi/ic_arrow_back.png | Bin 0 -> 270 bytes .../src/main/res/drawable-xhdpi/ic_arrow_back.png | Bin 0 -> 330 bytes .../main/res/drawable-xxhdpi/ic_arrow_back.png | Bin 0 -> 352 bytes .../main/res/drawable-xxxhdpi/ic_arrow_back.png | Bin 0 -> 413 bytes .../app/src/main/res/drawable/ic_action_back.xml | 5 +++++ .../src/main/res/drawable/ic_action_forward.xml | 3 ++- .../main/res/layout/models_list_row_layout.xml | 2 +- 19 files changed, 23 insertions(+), 20 deletions(-) delete mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml create mode 100644 android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png create mode 100644 android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png create mode 100644 android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_back.png create mode 100644 android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png create mode 100644 android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png create mode 100644 android/KMEA/app/src/main/res/drawable/ic_action_back.xml rename android/{KMAPro/kMAPro => KMEA/app}/src/main/res/drawable/ic_action_forward.xml (67%) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java index 0989bf0d08b..689548a56ec 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java @@ -105,7 +105,7 @@ public void onCreate(Bundle savedInstanceState) { hashMap = new HashMap<>(); final String customHelpLink = kbd.getHelpLink(); // Check if app declared FileProvider - icon = String.valueOf(R.drawable.ic_arrow_forward); + icon = String.valueOf(R.drawable.ic_action_forward); // Don't show help link arrow if File Provider unavailable, or custom help doesn't exist if ( (customHelpLink != null && !FileProviderUtils.exists(context)) || (customHelpLink == null && !packageID.equals(KMManager.KMDefault_UndefinedPackageID)) ) { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java index faa3e0efbf1..75116bad8cc 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java @@ -174,7 +174,7 @@ public void onCreate(Bundle savedInstanceState) { updateActiveLexicalModel(); ImageView imageView = (ImageView) layout.findViewById(R.id.image1); - imageView.setImageResource(R.drawable.ic_arrow_forward); + imageView.setImageResource(R.drawable.ic_action_forward); layout.setEnabled(true); layout.setOnClickListener(new View.OnClickListener() { @Override @@ -198,7 +198,7 @@ public void onClick(View v) { * textView = (TextView) layout.findViewById(R.id.text1); * textView.setText(getString(R.string.manage_dictionary)); * imageView = (ImageView) layout.findViewById(R.id.image1); - * imageView.setImageResource(R.drawable.ic_arrow_forward); + * imageView.setImageResource(R.drawable.ic_action_forward); */ listView.setAdapter(adapter); @@ -346,7 +346,7 @@ public View getView(int position, View convertView, ViewGroup parent) { } holder.text.setText(kbd.getResourceName()); - holder.img.setImageResource(R.drawable.ic_arrow_forward); + holder.img.setImageResource(R.drawable.ic_action_forward); return convertView; } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java index 7b621da4b7a..6e21d04f7bf 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java @@ -203,7 +203,7 @@ public View getView(int position, View convertView, ViewGroup parent) { } holder.textLang.setText(data.name); - holder.img.setImageResource(R.drawable.ic_arrow_forward); + holder.img.setImageResource(R.drawable.ic_action_forward); holder.textCount.setText(getContext().getResources().getQuantityString(R.plurals.keyboard_count, data.keyboards.size(), data.keyboards.size())); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java index 46bc68ca1a5..9b053321824 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java @@ -81,7 +81,7 @@ public void onCreate(Bundle savedInstanceState) { HashMap hashMap = new HashMap<>(); hashMap.put(titleKey, keyboardName); hashMap.put(subtitleKey, pkgID); - String icon = String.valueOf(R.drawable.ic_arrow_forward); + String icon = String.valueOf(R.drawable.ic_action_forward); hashMap.put(iconKey, icon); list.add(hashMap); } diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml deleted file mode 100644 index 165d535d3d0..00000000000 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml index 59de5185356..3b71e871785 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml @@ -19,11 +19,12 @@ + android:src="@drawable/ic_action_back" /> + android:src="@drawable/ic_action_forward" /> + android:src="@drawable/ic_action_back" /> + android:src="@drawable/ic_action_forward" /> + android:src="@drawable/ic_action_forward" /> diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java index c2daaea3030..1897920b731 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java @@ -88,7 +88,7 @@ public void onCreate(Bundle savedInstanceState) { hashMap = new HashMap(); final String customHelpLink = kbd.getHelpLink(); // Check if app declared FileProvider - String icon = String.valueOf(R.drawable.ic_arrow_forward); + String icon = String.valueOf(R.drawable.ic_action_forward); // Don't show help link arrow if it's a local help file and File Provider unavailable, // or custom help doesn't exist if ( (customHelpLink != null && ! KMManager.isTestMode() && diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java index ef1b180c52f..ce81927699a 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java @@ -81,7 +81,7 @@ public void onCreate(Bundle savedInstanceState) { final String customHelpLink = lm.getHelpLink(); // Check if app declared FileProvider // Currently, model help only available if custom link exists - icon = String.valueOf(R.drawable.ic_arrow_forward); + icon = String.valueOf(R.drawable.ic_action_forward); // Don't show help link arrow if both custom help and File Provider don't exist // TODO: Update this when model help available on help.keyman.com if ( (!customHelpLink.equals("") && !FileProviderUtils.exists(context)) || diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java index 8b74e261a37..4595aae1e70 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java @@ -321,7 +321,7 @@ public View getView(int position, View convertView, ViewGroup parent) { // once it has its own backing Dataset instance. // Is this an installed model or not? - holder.imgDetails.setImageResource(R.drawable.ic_arrow_forward); + holder.imgDetails.setImageResource(R.drawable.ic_action_forward); if (KeyboardPickerActivity.containsLexicalModel(context, modelKey)) { holder.imgInstalled.setImageResource(R.drawable.ic_check); } else { diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..7922c650163774b332ade994ec590a8605838a89 GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8JTOS+@4BLl<6e(pbstUx|flDE4H z!~gdFGy8!&_7YEDSN0biB8;NE6Zt%D0EJ3DT^vI^j=#NR%h#a5)Amr@^kTHPP2=>B z=_d2M)MZKzScoLEU;eOh(()}LMLEYFm(Fnb;WyVOVcDg`S!W_r?lfmI-gtRasEcd8 zr)^-DesD)|d=3BlDB)jWKR3^qwW8jm_u5Lw$y%i={C25bk1SYoecyA5(~P z+$aP(K()j*q9i4;B-JXpC>2OC7#SFu=o(n)8X1Hb7+M(_SeaO88yHv_80`Nak%^)q sH$NpatrDccSl7Tz*U&7)z}O0?$G`}p;l$B1$v_PZp00i_>zopr0L$uYU;qFB literal 0 HcmV?d00001 diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1eb7a4174665a072e617f180f97aa265c71146 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|}ByV>Y zhX3vTXZ8bm>?NMQuIw*3L>R?HSNb>m0)?DBT^vI!PA4Zw>|4;du+ik{f60FaFD5Os zsam3OoPA+KXT;|f4^&#FX>=3_cH9IjGDG3P)NxjKQ#{-xc4lo)Q-1uL( z5@?!giEBhjN@7W>RdP`(kYX@0Ff!3Ku+TL!2r)3UGBUCNS%G|}ByV>Y zhX3vTXZ8bm>?NMQuIw*3L>R?Ijt4i*0t(f6x;TbJ9DX~&o6o^PfR&p&{Zr(QX`+Qj zGbdXxvrBMq6j1Ew^N%}Qwk&IxBS+!1S+~O8e=O_teE)IEwqK1gzjABU{!ZIBz0QAr zT}1s;iPzzcAO5l0>|2z&NPXixCr;HV74=G1HJeozFeofL9O-v)PGwO|Qze&s>gQ_3 zf2TR(l3uzfi7#La^8Cw||X&V?= z85rz{*yMnsAvZrIGp!P&!C2S8OxMsX#K71JsK)@R;l$B1$v_PZp00i_>zopr09|u! A6951J literal 0 HcmV?d00001 diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..831448cb178f6af0841e5ba5b01f9e8748b1991c GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawSkfJR9T^xl_H+M9WCik>lDyqr z82-2SpV<%Ov6p!Iy0X9E5MdP8oNs-u8z?lz)5S3)5l3<^A{=HeInRdKG2^riX9j$LzB%}>)m$CNH| z?t1U%Cg$axpBtH%&-~m1WL+*kW5K(i+H(2=_h+sZZ%_1H$@H#%ciXv%g;PkyV}b&& z!>m0X(PrwCuH|^C?AwgT!g XFoI|}ar8_wPy>UftDnm{r-UW|I~sRl literal 0 HcmV?d00001 diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..5794fbcaac0b9ace84d73afca26e8e0cef024f9a GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgSkfJR9T^xl_H+M9WCik>lDyqr z82-2SpV<%Ov6p!Iy0X9E5MdOz&S~0z7$|hn)5S5Q;?~| zWM<3wT+er-cc)?gQGXBHg@58_)OdKmIbNG&re=}Q#EgY#KI><(dUKI*-m4{dT2@=B zn=efHXZ3K=xf8w_uRx)|5dD>9V!`w1%a#{hfIQU_*NBpo#FA92sBK_iWnjSjYneKVhTQy=%(P0724h_VGhIWo5CdZ?pdJGwh=vnK R&m@B^^mO%eS?83{1OP9Qo00$k literal 0 HcmV?d00001 diff --git a/android/KMEA/app/src/main/res/drawable/ic_action_back.xml b/android/KMEA/app/src/main/res/drawable/ic_action_back.xml new file mode 100644 index 00000000000..cae0c5eed49 --- /dev/null +++ b/android/KMEA/app/src/main/res/drawable/ic_action_back.xml @@ -0,0 +1,5 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward.xml b/android/KMEA/app/src/main/res/drawable/ic_action_forward.xml similarity index 67% rename from android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward.xml rename to android/KMEA/app/src/main/res/drawable/ic_action_forward.xml index 15efecf5575..2ffd9c4540e 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward.xml +++ b/android/KMEA/app/src/main/res/drawable/ic_action_forward.xml @@ -1,4 +1,5 @@ + android:tint="@android:color/black" + android:autoMirrored="true"/> diff --git a/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml b/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml index ac07621cdd6..a2116bcb4c5 100644 --- a/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml +++ b/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml @@ -44,7 +44,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/transparent" - android:src="@drawable/ic_arrow_forward" + android:src="@drawable/ic_action_forward" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="12dp" From 5f134fe590b7d187f82cfb7348bd847700a0347e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 20 Aug 2024 09:34:06 +0700 Subject: [PATCH 088/262] feat(android): Add localization for Arabic --- .../src/main/res/values-ar-rSA/strings.xml | 168 ++++++++++++++++ .../com/keyman/engine/DisplayLanguages.java | 1 + .../src/main/res/values-ar-rSA/strings.xml | 188 ++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml create mode 100644 android/KMEA/app/src/main/res/values-ar-rSA/strings.xml diff --git a/android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml new file mode 100644 index 00000000000..6ceb657187b --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml @@ -0,0 +1,168 @@ + + + + + مشاركة + + متصفح ويب + + حجم النص + + المزيد + + محو النص + + معلومات + + الإعدادات + + تنزيل التحديثات + + إصدار: %1$s + + يتطلب Keyman إصدار 57 من Chrome أو أعلى. + + تحديث Chrome + + ابدأ الكتابة هنا… + + + + حجم النص: %1$d + + تكبير حجم النص + + تصغير حجم النص + + Text size slider + + \nسيتم محو كل النص\n + + إبدأ الآن + + اضف لوحة مفاتيح بلغتك + + تمكين Keyman لوحة المفاتيح الأساسية للنظام + + إعداد Keyman لوحة المفاتيح الإفتراضية + + المزيد من المعلومات + + أظهر \"%1$s\" عند البدء + + لتثبيت حزم لوحات المفاتيح، امنح Keyman الإذن لقراءة التخزين الخارجي. + + تم رفض أذونات التخزين. قد يفشل تثبيت حزمة لوحة المفاتيح + Storage permission request failed. Try Keyman Settings - Install from local file + + الإعدادات + + + اللغات المثبتة (%1$d) + اللغات المثبتة (%1$d) + اللغات المثبتة (%1$d) + اللغات المثبتة (%1$d) + اللغات المثبتة (%1$d) + اللغات المثبتة (%1$d) + + + تثبيت لوحة المفاتيح او القاموس + + لغة العرض + + تعديل ارتفاع لوحة المفاتيح + + نص مفتاح المسافة + + لوحة المفاتيح + + اللغة + + اللغة + لوحة المفاتيح + + فارغ + + أظهر اسم لوحة المفاتيح على مفتاح المسافة + + أظهر اسم اللغة على المسافة + + اللغة ولوحة المفاتيح على المسافة + + لا تظهر نص على المسافة + + الاهتزاز عند الكتابة + + إظهار اللافتة دائماً + + سيتم تنفيذها + + عند إيقاف التشغيل، يظهر فقط عند تفعيل النص التنبؤي + + السماح بإرسال تقارير توقف التشغيل المفاجئ عبر الشبك + + عند التشغيل، سيتم إرسال تقارير التوقف المفاجئ + + عند إيقاف التشغيل، لن يتم إرسال التقارير + + تثبيت من keyman.com + + التثبيت من ملف محلي + + التثبيت من جهاز آخر + + اضف لغات للوحة المفاتيح المثبتة + + (من حزمة لوحة المفاتيح) + + اختر حزمة لوحة مفاتيح + + اختر لغة %1$s + + تمت اضافة %1$s إلى %2$s + + كل اللغات مثبتة مسبقا + + اسحب لوحة المفاتيح لتغيير مدى ارتفاعها + + دور الجهاز لضبط الوضع العمودي والوضع الأفقي + + إعادة ضبط الافتراضيات + + ابحث أو اكتب عنوان URL + + علامات مرجعية + + لا تتوفر أي علامات مرجعية + + أضف علامة مرجعية + + العنوان + + عنوان Url + + فشل في تنزيل حزمة %1$s + + يتم الآن تحميل لوحة مفاتيح حزمة \n%1$s… + + فشل في الإستخراج + + تثبيت لوحة المفاتيح + + تثبيت القاموس + + ليس %1$s ملف حزمة صالحة لبرنامج Keyman.\n%2$s\" + + ليس في حزمة لوحة المفاتيح لوحات مفاتيح مكيّفة لشاشات اللمس + + لا يوجد نصوص تنبؤية للتنزيل + + لا لوحات مفاتيح و نصوص تنبؤية للتنزيل + + ليس لحزمة لوحة المفاتيح لغات ذات صلة لتثبيتها + + غير صالح\ناقص البيانات الوصفية في الحزمة + + تتطلب لوحة المفاتيح إصدارًا أحدث من Keyman + + غير قادر على تشغيل متصفح الويب + diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java index 6a159523a2e..4783e401b8e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java @@ -40,6 +40,7 @@ public static final DisplayLanguageType[] getDisplayLanguages(Context context) { DisplayLanguageType[] languages = { new DisplayLanguageType(unspecifiedLocale, context.getString(R.string.default_locale)), new DisplayLanguageType("am-ET", "አማርኛ (Amharic)"), + new DisplayLanguageType("ar-SA", "(Arabic) العربية"), new DisplayLanguageType("az-AZ", "Azərbaycanca (Azəricə)"), new DisplayLanguageType("bwr-NG", "Bura-Pabir"), new DisplayLanguageType("cs-CZ", "čeština (Czech)"), diff --git a/android/KMEA/app/src/main/res/values-ar-rSA/strings.xml b/android/KMEA/app/src/main/res/values-ar-rSA/strings.xml new file mode 100644 index 00000000000..cb2e9f0d512 --- /dev/null +++ b/android/KMEA/app/src/main/res/values-ar-rSA/strings.xml @@ -0,0 +1,188 @@ + + + + + + + لوحة مفاتيح + لوحات مفاتيح + لوحتا مفاتيح + لوحات مفاتيح + لوحة مفاتيح + لوحة مفاتيح + + + + طريقة إداخل أخرى + طريقة إدخال أخرى + طريقتا إدخال أخريتان + طرق إدخال + طريقة إدخال أخرى + طريقة إدخال أخرى + + + إضافة لوحة مفاتيح جديدة + + اللغات المثبتة + + إعدادات %1$s + + إضافة + + عودة + + إلغاء + + إغلاق + + إغلاق Keyman + + الأمام + + طريقة الإدخال التالية + + تحميل + + تثبيت + + لاحقًا + + التالي + + موافق + + تحديث + + لا يوجد اتصال بالإنترنت + + لا يمكن الاتصال بخادم Keyman! + + هل ترغب بإزالة لوحة المفاتيح هذه؟ + + هل ترغب بتحميل آخر إصدار من لوحة المفاتيح هذه؟ + + هل ترغب بتحديث لوحات المفاتيح والقواميس الآن؟ + + تحديثات الموارد + + تتوفر تحديثات موارد + + %1$s (تحديث متوفر) + + تحديثات متوفرة للوحة مفاتيح %1$s: %2$s + + تحديثات متوفرة لقاموس %1$s: %2$s + + إصدار لوحة المفاتيح + + رابط المساعدة + + إزالة تثبيت لوحة المفاتيح + + [new] %1$s + + امسح هذا الرمز لتحميل \nلوحة المفاتيح هذه على جهاز آخر + + أهلًا بك في %1$s + + مكتبة FileProvider مطلوبة لعرض ملفات المساعدة: %1$s + + خطأ كبير في لوحة مفاتيح مع %1$s:%2$s لأجل لغة %3$s. سيتم تحميل لوحة المفاتيح الأساسية. + + خطأ في لوحة مفاتيح %1$s:%2$s لأجل لغة %3$s. + + يجري التحقق من قاموس ذي صلة للتحميل + لا يمكن الاتصال بخادم Keyman للتحقق من تنزيل القاموس المرتبط + + هل ترغب بتحملي آخر إصدار من هذا القاموس؟ + + لا يوجد قاموس لتحميله + + كتالوغ المصدر غير متوفر + + بدء تحديث الكتالوغ في الخلفية + + الكتالوغ لا يزال يتم تحميله، يرجى المحاولة مجددًا بعد قليل! + + البحث عن مصدر + + بدء تحميل لوحة المفاتيح في الخلفية + + يجري بالفعل تحميل لوحة المفاتيح المختارة، يرجى المحاولة مجددًا بعد قليل! + + اكتمل تحميل لوحة المفاتيح! + + بدأ تحميل القاموس في الخلفية + + يجري بالفعل تحميل القاموس، يرجى المحاولة مجددًا بعد قليل! + + اكتمل تحميل القاموس. + + فشل التحميل + + فشل استرجاع الملف المحمل + + فشل الوصول إلى الخادم! + + "جميع الموارد محدّثة!" + + فشل تحديث واحد أو أكثر من المصادر! + + تم تحديث الموارد بنجاح! + + إصدار القاموس + + إزالة تثبيت القاموس + + هل ترغب بإزالة هذا القاموس؟ + + تم حذف القاموس + + تم تثبيت لوحة مفاتيح %1$s + + تم حذف لوحة المفاتيح + + تفعيل التصحيحات + + تفعيل التوقعات + + القاموس + + Dictionaries + Dictionary + Dictionaries + Dictionaries + Dictionaries + Dictionaries + + + البحث عن قاموس متوفر + تحقق من وجود قواميس على الإنترنت + + قاموس: %1$s + + قواميس %1$s + + + تم تثبيت القاموس + + + (%1$d اوحات) + (لوحة مفاتيح %1$d) + (لوحتا مفاتيح %1$d) + (%1$d لوحة مفاتيح) + (%1$d لوحة مفاتيح) + (%1$d لوحة مفاتيح) + + + اللغة الأصلية + + + + حذف + + + اضغط هنا لتغيير لوحة المفاتيح + + غير قادر على تشغيل متصفح الويب + From 92275697c049770f2f3e07577756442e0ce60123 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 20 Aug 2024 10:52:28 +0700 Subject: [PATCH 089/262] fix(web): improve tokenization output when wordbreaker breaks spec for span .left, .right values in output --- common/models/templates/src/tokenization.ts | 28 ++-- .../templates/test/custom-breakers.def.js | 128 ++++++++++++++++++ .../templates/test/test-tokenization.js | 75 ++++++++++ 3 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 common/models/templates/test/custom-breakers.def.js diff --git a/common/models/templates/src/tokenization.ts b/common/models/templates/src/tokenization.ts index 46290a455d4..2fb07da357b 100644 --- a/common/models/templates/src/tokenization.ts +++ b/common/models/templates/src/tokenization.ts @@ -61,20 +61,21 @@ export function tokenize( let currentIndex = 0; while(leftSpans.length > 0) { const nextSpan = leftSpans[0]; - if(nextSpan.start != currentIndex) { + if(Math.max(nextSpan.start, currentIndex) != currentIndex) { + const nextIndex = Math.max(currentIndex, nextSpan.start); // Implicit whitespace span! tokenization.left.push({ - text: context.left!.substring(currentIndex, nextSpan.start), + text: context.left!.substring(currentIndex, nextIndex), isWhitespace: true }); - currentIndex = nextSpan.start; + currentIndex = nextIndex; } else { leftSpans.shift(); // Explicit non-whitespace span. tokenization.left.push({ text: nextSpan.text }); - currentIndex = nextSpan.end; + currentIndex = Math.max(currentIndex, nextSpan.end); } } @@ -84,11 +85,12 @@ export function tokenize( // Note: the default wordbreaker won't need this code, as it emits a `''` // after final whitespace. if(context.left != null && currentIndex != context.left.length) { + const nextIndex = Math.max(currentIndex, context.left!.length); tokenization.left.push({ - text: context.left.substring(currentIndex, context.left!.length), + text: context.left.substring(currentIndex, nextIndex), isWhitespace: true }); - currentIndex = context.left!.length; + currentIndex = nextIndex; } // New step 2: handle any rejoins needed. @@ -134,13 +136,14 @@ export function tokenize( // `caretSplitsToken` check is additional. while(rightSpans.length > 0) { const nextSpan = rightSpans[0]; - if(nextSpan.start != currentIndex) { + if(Math.max(nextSpan.start, currentIndex) != currentIndex) { + const nextIndex = Math.max(currentIndex, nextSpan.start); // Implicit whitespace span! tokenization.right.push({ - text: context.right!.substring(currentIndex, nextSpan.start), + text: context.right!.substring(currentIndex, nextIndex), isWhitespace: true }); - currentIndex = nextSpan.start; + currentIndex = nextIndex; } else { const leftTail = tokenization.left[leftTokenCount-1]; if(leftTail) { @@ -159,7 +162,7 @@ export function tokenize( tokenization.right.push({ text: nextSpan.text }); - currentIndex = nextSpan.end; + currentIndex = Math.max(currentIndex, nextSpan.end); } // We've always processed the "first right token" after the first iteration. @@ -176,11 +179,12 @@ export function tokenize( // Also note: is pretty much WET with the similar check after the // leftSpan loop. if(context.right && currentIndex != context.right.length) { + const nextIndex = Math.max(currentIndex, context.right!.length); tokenization.right.push({ - text: context.right.substring(currentIndex, context.right!.length), + text: context.right.substring(currentIndex, nextIndex), isWhitespace: true }); - currentIndex = context.right!.length; + currentIndex = nextIndex; } return tokenization; diff --git a/common/models/templates/test/custom-breakers.def.js b/common/models/templates/test/custom-breakers.def.js new file mode 100644 index 00000000000..c907467ce02 --- /dev/null +++ b/common/models/templates/test/custom-breakers.def.js @@ -0,0 +1,128 @@ +/** + * The custom wordbreaker used by the sil.km.gcc model as of + * https://github.com/keymanapp/lexical-models/pull/265. + * @type {WordBreakingFunction} + * */ +export function customWordBreakerProper(str) { + const whitespaceRegex = /\s|\u200b|\n|\r/; + const tokens = str.split(whitespaceRegex); + + for(let i=0; i < tokens.length; i++) { + const token = tokens[i]; + if(token.length == 0) { + tokens.splice(i, 1); + i--; + continue; + } else if(token.length == 1 && whitespaceRegex.test(token)) { + tokens.splice(i, 1); + i--; + continue; + } + + // Certain punctuation marks should be considered a separate token from the word they're next to. + const punctuationMarks = ['«', '»', '$', '#' /* add extras here */]; + const punctSplitIndices = []; + + // Find if and where each mark exists within the token + for(let i = 0; i < punctuationMarks.length; i++) { + const split = token.indexOf(punctuationMarks[i]); + if(split >= 0) { + punctSplitIndices.push(split); + } + } + + // Sort and pick the earliest mark's location. If none exists, use -1. + punctSplitIndices.sort(); + const splitPoint = punctSplitIndices[0] === undefined ? -1 : punctSplitIndices[0]; + + if(splitPoint > -1) { + const left = token.substring(0, splitPoint); // (0, -1) => '' + const punct = token.substring(splitPoint, splitPoint+1); + const right = token.substring(splitPoint+1); // Starting past the end of the string => '' + + if(left) { + tokens.splice(i++, 0, left); + } + tokens.splice(i++, 1, punct); + if(right) { + tokens.splice(i, 0, right); + } + // Ensure that the next iteration puts `i` immediately after the punctuation token... even if + // there was a `right` portion, as it may have extra marks that also need to be spun off. + i--; + } + } + + let latestIndex = 0; + return tokens.map(function(token) { + const start = str.indexOf(token, latestIndex); + latestIndex = start + token.length; + return { + left: start, + start: start, + right: start + token.length, + end: start + token.length, + length: token.length, + text: token + } + }); +} + +/** + * The version of the custom wordbreaker used by the sil.km.gcc model + * before https://github.com/keymanapp/lexical-models/pull/265, which + * triggered #12200. + * @type {WordBreakingFunction} + * */ +export function customWordBreakerFormer (str) { + const tokens = str.split(/\s|\u200b/); + + for(let i=0; i < tokens.length; i++) { + const token = tokens[i]; + if(token.length == 1) { + continue; + } + + // Certain punctuation marks should be considered a separate token from the word they're next to. + const punctuationMarks = ['«', '»', '$', '#' /* add extras here */]; + const punctSplitIndices = []; + + // Find if and where each mark exists within the token + for(let i = 0; i < punctuationMarks.length; i++) { + const split = token.indexOf(punctuationMarks[i]); + if(split >= 0) { + punctSplitIndices.push(split); + } + } + + // Sort and pick the earliest mark's location. If none exists, use -1. + punctSplitIndices.sort(); + const splitPoint = punctSplitIndices[0] === undefined ? -1 : punctSplitIndices[0]; + + if(splitPoint > -1) { + const left = token.substring(0, splitPoint); // (0, -1) => '' + const punct = token.substring(splitPoint, splitPoint+1); + const right = token.substring(splitPoint+1); // Starting past the end of the string => '' + + if(left) { + tokens.splice(i++, 0, left); + } + tokens.splice(i++, 1, punct); + if(right) { + tokens.splice(i, 0, right); + } + // Ensure that the next iteration puts `i` immediately after the punctuation token... even if + // there was a `right` portion, as it may have extra marks that also need to be spun off. + i--; + } + } + return tokens.map(function(token) { + return { + left: str.indexOf(token), + start: str.indexOf(token), + right: str.indexOf(token) + token.length, + end: str.indexOf(token) + token.length, + text: token + } + }); +} \ No newline at end of file diff --git a/common/models/templates/test/test-tokenization.js b/common/models/templates/test/test-tokenization.js index 2a5ec136f69..20b7082dfec 100644 --- a/common/models/templates/test/test-tokenization.js +++ b/common/models/templates/test/test-tokenization.js @@ -5,6 +5,7 @@ import { assert } from 'chai'; import * as models from "@keymanapp/models-templates"; import * as wordBreakers from "@keymanapp/models-wordbreakers"; +import { customWordBreakerFormer, customWordBreakerProper } from './custom-breakers.def.js'; function asProcessedToken(text) { // default wordbreaker emits these at the end of each context half if ending with whitespace. @@ -490,6 +491,80 @@ describe('Tokenization functions', function() { caretSplitsToken: true }); }); + + it('mitigates effects of previously-distributed malformed wordbreaker output', function () { + const text = 'the quick brown fox jumped over the lazy dog '; + /** @type { Context } */ + const context = { + left: text, + right: '', + startOfBuffer: true, + endOfBuffer: true + } + + const tokenized = models.tokenize(customWordBreakerFormer, context); + + // Mitigation aims to prevent the _worst_ side-effects that can result from invalidating the + // underlying assumption of a monotonically-increasing index within the context - + // assigning repeated or blank entries the text that preceded them! + assert.notExists(tokenized.left.find((token) => token.text == text)); + assert.notExists(tokenized.left.find((token) => token.text.startsWith(text.substring(0, 25)))); + + // 'the' appears twice in the context, which should result in two separate 'the' tokens here. + // This was improperly handled when we didn't check that assumption. + assert.equal(tokenized.left.filter((token) => token.text == 'the').length, 2); + + // Does not address multiple blank-token ('') entries that result from intervening spaces; + // that would add too much extra complexity to the method... and it can already be + // handled decently by the predictive-text engine. + }); + + it('properly works with well-formed custom wordbreaker output', function () { + const text = 'the quick brown fox jumped over the lazy dog '; + /** @type { Context } */ + const context = { + left: text, + right: '', + startOfBuffer: true, + endOfBuffer: true + } + + const tokenized = models.tokenize(customWordBreakerProper, context); + + // Easier-to-parse version + assert.deepEqual( + tokenized.left + .filter((entry) => !entry.isWhitespace) + .map((entry) => entry.text), + ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'] + ); + + // This time, with whitespaces. + assert.deepEqual( + tokenized.left.map((entry) => entry.text), [ + 'the', + ' ', + 'quick', + ' ', + 'brown', + ' ', + 'fox', + ' ', + 'jumped', + ' ', + 'over', + ' ', + 'the', + ' ', + 'lazy', + ' ', + 'dog', + ' ' + ] + ); + }); + + // }); describe('getLastPreCaretToken', function() { From ec32e21bf4bd6f7d225126e34deaec7ff728e302 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 20 Aug 2024 11:04:32 +0700 Subject: [PATCH 090/262] feat(web): adds extra assertion to 'mitigation' test --- common/models/templates/test/test-tokenization.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/models/templates/test/test-tokenization.js b/common/models/templates/test/test-tokenization.js index 20b7082dfec..d8f27197a27 100644 --- a/common/models/templates/test/test-tokenization.js +++ b/common/models/templates/test/test-tokenization.js @@ -517,6 +517,13 @@ describe('Tokenization functions', function() { // Does not address multiple blank-token ('') entries that result from intervening spaces; // that would add too much extra complexity to the method... and it can already be // handled decently by the predictive-text engine. + assert.deepEqual( + tokenized.left + .filter((entry) => !entry.isWhitespace) + .filter((entry) => entry.text != '') + .map((entry) => entry.text), + ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'] + ); }); it('properly works with well-formed custom wordbreaker output', function () { From 3326ac2c6b62b66b0567965b3bb834ee432d9d6c Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:14:57 +1000 Subject: [PATCH 091/262] feat(windows): remove check for non-chiral vkey --- .../keyman32/k32_lowlevelkeyboardhook.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 6d5067516e9..f5fbdb8f301 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -169,15 +169,14 @@ LRESULT _kmnLowLevelKeyboardProc( if (GetKeyState(VK_LCONTROL) < 0) { FHotkeyShiftState |= HK_CTRL; + SendDebugMessageFormat("ProcessHotkey VK_LCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); } if (GetKeyState(VK_RCONTROL) < 0) { FHotkeyShiftState |= UseRightModifierHotKey() ? HK_CTRL : HK_RCTRL_INVALID; + SendDebugMessageFormat("ProcessHotkey VK_RCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); } - if (GetKeyState(VK_CONTROL) < 0) { - FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RCTRL_INVALID : HK_CTRL; - } - + if (GetKeyState(VK_LMENU) < 0) { FHotkeyShiftState |= HK_ALT; } @@ -186,10 +185,6 @@ LRESULT _kmnLowLevelKeyboardProc( FHotkeyShiftState |= UseRightModifierHotKey() ? HK_ALT : HK_RALT_INVALID; } - if (GetKeyState(VK_MENU) < 0) { - FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RALT_INVALID : HK_ALT; - } - if (GetKeyState(VK_LSHIFT) < 0) { FHotkeyShiftState |= HK_SHIFT; } @@ -197,9 +192,9 @@ LRESULT _kmnLowLevelKeyboardProc( FHotkeyShiftState |= UseRightModifierHotKey() ? HK_SHIFT : HK_RSHIFT_INVALID; } - if (GetKeyState(VK_SHIFT) < 0) { - FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RSHIFT_INVALID : HK_SHIFT; - } + //if (GetKeyState(VK_SHIFT) < 0) { + // FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RSHIFT_INVALID : HK_SHIFT; + //} // #7337 Post the modifier state ensuring the serialized queue is in sync // Note that the modifier key may be posted again with WM_KEYMAN_KEY_EVENT, From 93b1881cb8b2c8544f0210d8f9896d5f8bd6d244 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 20 Aug 2024 13:55:13 +0700 Subject: [PATCH 092/262] fix(android): Auto-mirror increment and decrement arrows --- .../java/com/tavultesoft/kmapro/MainActivity.java | 14 ++++++++++++-- .../src/main/res/drawable/ic_action_decrement.xml | 5 +++++ .../src/main/res/drawable/ic_action_increment.xml | 5 +++++ .../src/main/res/layout/text_size_controller.xml | 4 ++-- 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 1e5231f5215..21d1af493ae 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -646,7 +646,7 @@ private void showTextSizeDialog() { final View textSizeController = inflater.inflate(R.layout.text_size_controller, null); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this); dialogBuilder.setIcon(R.drawable.ic_light_action_textsize); - dialogBuilder.setTitle(String.format(getString(R.string.text_size), textSize)); + dialogBuilder.setTitle(getTextSizeString()); dialogBuilder.setView(textSizeController); dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() { @Override @@ -677,7 +677,7 @@ public void onStartTrackingTouch(SeekBar seekBar) { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { textSize = progress + minTextSize; textView.setTextSize((float) textSize); - dialog.setTitle(String.format(getString(R.string.text_size), textSize)); + dialog.setTitle(getTextSizeString()); } }); @@ -702,6 +702,16 @@ public void onClick(View v) { }); } + /** + * Combine a localized string for "Text Size" plus Arabic numerals + * @return String + */ + private String getTextSizeString() { + // Instead of formatting the number, will truncate formatting and concat the actual textSize + String label = getString(R.string.text_size).replace("%1$d", ""); + return label + KMString.format(" %d", textSize); + } + private void showClearTextDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this); dialogBuilder.setIcon(R.drawable.ic_light_action_trash); diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml new file mode 100644 index 00000000000..c3085bec5e9 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml @@ -0,0 +1,5 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml new file mode 100644 index 00000000000..ea188f818f1 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml @@ -0,0 +1,5 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml index 2245ef557a3..c250b1bf072 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml @@ -15,7 +15,7 @@ android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:contentDescription="@string/ic_text_size_down" - android:src="@drawable/ic_light_dialog_textsize_down" /> + android:src="@drawable/ic_action_decrement" /> + android:src="@drawable/ic_action_increment" /> From 97697b695ff85eed11047489b08c1f9299f559ca Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 20 Aug 2024 14:19:14 +0700 Subject: [PATCH 093/262] fix(web): fix documentation-keyboard spacebar-text scaling Fixes: #12231 --- web/src/engine/osk/src/visualKeyboard.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 26b269d7f26..0d529af651e 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1246,6 +1246,7 @@ export default class VisualKeyboard extends EventEmitter implements Ke const groupStyle = getComputedStyle(this.layerGroup.element); const isInDOM = computedStyle.height != '' && computedStyle.height != 'auto'; + const isGroupInDOM = groupStyle.height != '' && groupStyle.height != 'auto'; if (computedStyle.border) { this._borderWidth = new ParsedLengthStyle(computedStyle.borderWidth).val; @@ -1258,10 +1259,11 @@ export default class VisualKeyboard extends EventEmitter implements Ke this._computedHeight = this.height; } else if (isInDOM) { this._computedWidth = parseInt(computedStyle.width, 10); - if (!this._computedWidth) { - this._computedWidth = parseInt(groupStyle.width, 10); - } this._computedHeight = parseInt(computedStyle.height, 10); + } else if (isGroupInDOM) { + // May occur for documentation-keyboards, which are detached from their VisualKeyboard base. + this._computedWidth = parseInt(groupStyle.width, 10); + this._computedHeight = parseInt(groupStyle.height, 10); } else { // Cannot perform layout operations! return; @@ -1604,6 +1606,11 @@ export default class VisualKeyboard extends EventEmitter implements Ke // the page. kbdObj.appendStyleSheet(); + // Unset the width + height we used thus far; this method's consumer may choose to rescale + // the returned element. If so, we don't want to use our outdated value by mistake. + delete kbdObj._width; + delete kbdObj._height; + return classWrapper; } From 23e02d2a86812e7807fdad6304db99abf1d773de Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 20 Aug 2024 14:38:58 +0700 Subject: [PATCH 094/262] change(web): better way to re-establish default sizing --- web/src/engine/osk/src/visualKeyboard.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 0d529af651e..a70c6b68ed4 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1608,8 +1608,7 @@ export default class VisualKeyboard extends EventEmitter implements Ke // Unset the width + height we used thus far; this method's consumer may choose to rescale // the returned element. If so, we don't want to use our outdated value by mistake. - delete kbdObj._width; - delete kbdObj._height; + kbdObj.setSize(); return classWrapper; } From 01ae0c0a9ac23df03482223390a1a8f0ceba3d43 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 20 Aug 2024 14:40:24 +0700 Subject: [PATCH 095/262] chore(web): Revert "change(web): better way to re-establish default sizing" This reverts commit 23e02d2a86812e7807fdad6304db99abf1d773de. --- web/src/engine/osk/src/visualKeyboard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index a70c6b68ed4..0d529af651e 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1608,7 +1608,8 @@ export default class VisualKeyboard extends EventEmitter implements Ke // Unset the width + height we used thus far; this method's consumer may choose to rescale // the returned element. If so, we don't want to use our outdated value by mistake. - kbdObj.setSize(); + delete kbdObj._width; + delete kbdObj._height; return classWrapper; } From 5c96f8f76921ef23f70a827eb483f5f60580b23a Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 20 Aug 2024 16:14:40 +0700 Subject: [PATCH 096/262] fix(mac): remove text from short bundle version string in info.plist for both input method and engine, remove the framing text so that it just contains the version number triplet Fixes: #12197 --- mac/Keyman4MacIM/Keyman4MacIM/Info.plist | 2 +- mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Info.plist b/mac/Keyman4MacIM/Keyman4MacIM/Info.plist index 29451d4cce2..49f532055ec 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Info.plist +++ b/mac/Keyman4MacIM/Keyman4MacIM/Info.plist @@ -36,7 +36,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - Keyman $(PRODUCT_VERSION) for macOS + $(PRODUCT_VERSION) CFBundleSignature ???? CFBundleURLTypes diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist b/mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist index 970606a26a0..3f42d3e63f7 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - Keyman Engine $(PRODUCT_VERSION) for macOS + $(PRODUCT_VERSION) CFBundleSignature ???? CFBundleVersion From ab3722ecfaf2f804a53679315891187bb05feab8 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 20 Aug 2024 15:10:14 +0200 Subject: [PATCH 097/262] fix(core): look for `emcc` instead of `emcc.py` on Linux and Mac `emcc.py` is not marked as executable in emcripten's git repo so the build failed when trying to locate emscripten. However, it turns out that `emcc` is marked as executable, so we use that instead. On Windows however, we still need to use `emcc.py` because otherwise Meson won't detect it as valid compiler. --- core/wasm.build.linux.in | 6 ++--- core/wasm.build.mac.in | 6 ++--- core/wasm.defs.build | 6 ++--- developer/src/kmcmplib/wasm.build.linux.in | 6 ++--- developer/src/kmcmplib/wasm.build.mac.in | 6 ++--- resources/locate_emscripten.inc.sh | 30 +++++++++++++--------- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/core/wasm.build.linux.in b/core/wasm.build.linux.in index d449dcf94a0..27cb7ba68fe 100644 --- a/core/wasm.build.linux.in +++ b/core/wasm.build.linux.in @@ -1,4 +1,4 @@ [binaries] -c = ['$EMSCRIPTEN_BASE/emcc.py'] -cpp = ['$EMSCRIPTEN_BASE/em++.py'] -ar = ['$EMSCRIPTEN_BASE/emar.py'] \ No newline at end of file +c = ['$EMSCRIPTEN_BASE/emcc'] +cpp = ['$EMSCRIPTEN_BASE/em++'] +ar = ['$EMSCRIPTEN_BASE/emar'] diff --git a/core/wasm.build.mac.in b/core/wasm.build.mac.in index d449dcf94a0..27cb7ba68fe 100644 --- a/core/wasm.build.mac.in +++ b/core/wasm.build.mac.in @@ -1,4 +1,4 @@ [binaries] -c = ['$EMSCRIPTEN_BASE/emcc.py'] -cpp = ['$EMSCRIPTEN_BASE/em++.py'] -ar = ['$EMSCRIPTEN_BASE/emar.py'] \ No newline at end of file +c = ['$EMSCRIPTEN_BASE/emcc'] +cpp = ['$EMSCRIPTEN_BASE/em++'] +ar = ['$EMSCRIPTEN_BASE/emar'] diff --git a/core/wasm.defs.build b/core/wasm.defs.build index 4a598a1e1c1..f41af337a38 100644 --- a/core/wasm.defs.build +++ b/core/wasm.defs.build @@ -1,7 +1,7 @@ [binaries] -c = ['emcc.py'] -cpp = ['em++.py'] -ar = ['emar.py'] +c = ['emcc'] +cpp = ['em++'] +ar = ['emar'] exe_wrapper = 'node' [properties] diff --git a/developer/src/kmcmplib/wasm.build.linux.in b/developer/src/kmcmplib/wasm.build.linux.in index c5e61a9bda9..27cb7ba68fe 100644 --- a/developer/src/kmcmplib/wasm.build.linux.in +++ b/developer/src/kmcmplib/wasm.build.linux.in @@ -1,4 +1,4 @@ [binaries] -c = ['$EMSCRIPTEN_BASE/emcc.py'] -cpp = ['$EMSCRIPTEN_BASE/em++.py'] -ar = ['$EMSCRIPTEN_BASE/emar.py'] +c = ['$EMSCRIPTEN_BASE/emcc'] +cpp = ['$EMSCRIPTEN_BASE/em++'] +ar = ['$EMSCRIPTEN_BASE/emar'] diff --git a/developer/src/kmcmplib/wasm.build.mac.in b/developer/src/kmcmplib/wasm.build.mac.in index c5e61a9bda9..27cb7ba68fe 100644 --- a/developer/src/kmcmplib/wasm.build.mac.in +++ b/developer/src/kmcmplib/wasm.build.mac.in @@ -1,4 +1,4 @@ [binaries] -c = ['$EMSCRIPTEN_BASE/emcc.py'] -cpp = ['$EMSCRIPTEN_BASE/em++.py'] -ar = ['$EMSCRIPTEN_BASE/emar.py'] +c = ['$EMSCRIPTEN_BASE/emcc'] +cpp = ['$EMSCRIPTEN_BASE/em++'] +ar = ['$EMSCRIPTEN_BASE/emar'] diff --git a/resources/locate_emscripten.inc.sh b/resources/locate_emscripten.inc.sh index be66adfa32f..20923770e2a 100644 --- a/resources/locate_emscripten.inc.sh +++ b/resources/locate_emscripten.inc.sh @@ -2,30 +2,36 @@ # no hashbang for .inc.sh # -# We don't want to rely on emcc.py being on the path, because Emscripten puts far +# We don't want to rely on emcc being on the path, because Emscripten puts far # too many things onto the path (in particular for us, node). # -# The following comment suggests that we don't need emcc.py on the path. +# The following comment suggests that we don't need emcc on the path. # https://github.com/emscripten-core/emscripten/issues/4848#issuecomment-1097357775 # -# So we try and locate emcc.py in common locations ourselves. The search pattern +# So we try and locate emcc in common locations ourselves. The search pattern # is: # # 1. Look for $EMSCRIPTEN_BASE (our primary emscripten variable), which should -# point to the folder that emcc.py is located in -# 2. Look for $EMCC which should point to the emcc.py executable -# 3. Look for emcc.py on the path +# point to the folder that emcc is located in +# 2. Look for $EMCC which should point to the emcc executable +# 3. Look for emcc on the path # locate_emscripten() { + local EMCC_EXECUTABLE + if [[ "${BUILDER_OS}" == "win" ]]; then + EMCC_EXECUTABLE="emcc.py" + else + EMCC_EXECUTABLE="emcc" + fi if [[ -z ${EMSCRIPTEN_BASE+x} ]]; then if [[ -z ${EMCC+x} ]]; then - local EMCC=`which emcc.py` - [[ -z $EMCC ]] && builder_die "locate_emscripten: Could not locate emscripten (emcc.py) on the path or with \$EMCC or \$EMSCRIPTEN_BASE" + local EMCC=$(which ${EMCC_EXECUTABLE}) + [[ -z $EMCC ]] && builder_die "locate_emscripten: Could not locate emscripten (${EMCC_EXECUTABLE}) on the path or with \$EMCC or \$EMSCRIPTEN_BASE" fi - [[ -f $EMCC && ! -x $EMCC ]] && builder_die "locate_emscripten: Variable EMCC ($EMCC) points to emcc.py but it is not executable" - [[ -x $EMCC ]] || builder_die "locate_emscripten: Variable EMCC ($EMCC) does not point to a valid executable emcc.py" + [[ -f $EMCC && ! -x $EMCC ]] && builder_die "locate_emscripten: Variable EMCC ($EMCC) points to ${EMCC_EXECUTABLE} but it is not executable" + [[ -x $EMCC ]] || builder_die "locate_emscripten: Variable EMCC ($EMCC) does not point to a valid executable ${EMCC_EXECUTABLE}" EMSCRIPTEN_BASE="$(dirname "$EMCC")" fi - [[ -f ${EMSCRIPTEN_BASE}/emcc.py && ! -x ${EMSCRIPTEN_BASE}/emcc.py ]] && builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) contains emcc.py but it is not executable" - [[ -x ${EMSCRIPTEN_BASE}/emcc.py ]] || builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) does not point to emcc.py's folder" + [[ -f ${EMSCRIPTEN_BASE}/${EMCC_EXECUTABLE} && ! -x ${EMSCRIPTEN_BASE}/${EMCC_EXECUTABLE} ]] && builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) contains ${EMCC_EXECUTABLE} but it is not executable" + [[ -x ${EMSCRIPTEN_BASE}/${EMCC_EXECUTABLE} ]] || builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) does not point to ${EMCC_EXECUTABLE}'s folder" } From 36b2a98e20eb5e27aa525143e5c111b3607fd8f1 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 20 Aug 2024 21:01:53 +0700 Subject: [PATCH 098/262] Update android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java Co-authored-by: Marc Durdin --- .../tavultesoft/kmapro/AdjustLongpressDelayActivity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index 590bbdc2673..e45d2a2fb22 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -1,5 +1,7 @@ -/** - * Copyright (C) SIL International. All rights reserved. +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * (Optional description of this file) */ package com.tavultesoft.kmapro; From ac87fc483e3a5ff256ae6950465b3892cc1a7b77 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 20 Aug 2024 14:03:19 -0400 Subject: [PATCH 099/262] auto: increment master version to 18.0.94 --- HISTORY.md | 8 ++++++++ VERSION.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 5d82f22e40a..0c527e2390e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # Keyman Version History +## 18.0.93 alpha 2024-08-20 + +* (#12188) +* fix(web): fix malformed reversion display strings (#12201) +* feat(android): Add menu to specify long-press delay (#12170) +* feat(android): Pass longpress delay to KeymanWeb (#12185) +* (#12223) + ## 18.0.92 alpha 2024-08-19 * chore(deps-dev): bump @75lb/deep-merge from 1.1.1 to 1.1.2 (#12118) diff --git a/VERSION.md b/VERSION.md index f74f9c87c90..8f14bdc62b3 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.93 \ No newline at end of file +18.0.94 \ No newline at end of file From c48a4a72428626aad3137480cd36b66486297089 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:56:55 +1000 Subject: [PATCH 100/262] feat(windows): Use RegisterHotkey only for chirl mode --- windows/src/engine/keyman/UfrmKeyman7Main.pas | 36 +++++++++++++++++++ windows/src/engine/keyman32/hotkeys.cpp | 10 ++++-- .../keyman32/k32_lowlevelkeyboardhook.cpp | 31 ++++++++++------ 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 155ab9208c0..fbed70b24d0 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -2049,7 +2049,25 @@ procedure TfrmKeyman7Main.RegisterHotkeys; i: Integer; language: IKeymanLanguage; id: Integer; + RegistryErrorControlled: TRegistryErrorControlled;// I2890 + UseRegisterHotKey: Boolean; // Use Win32 API RegisterHotkey begin + RegistryErrorControlled := TRegistryErrorControlled.Create; + try + if RegistryErrorControlled.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and + RegistryErrorControlled.ValueExists(SRegValue_UseRightModifierHotKey) then + begin + UseRegisterHotKey := RegistryErrorControlled.ReadBool(SRegValue_UseRightModifierHotKey); + end + else + begin + UseRegisterHotKey := False; + end; + finally + RegistryErrorControlled.Free; + end; + + if not UseRegisterHotKey then Exit; TDebugLogClient.Instance.WriteMessage('Enter RegisterHotkeys', []); @@ -2102,7 +2120,25 @@ procedure TfrmKeyman7Main.RegisterHotkeys; procedure TfrmKeyman7Main.UnregisterHotkeys; var i, hk: Integer; + RegistryErrorControlled: TRegistryErrorControlled;// I2890 + UseRegisterHotKey: Boolean; // Use Win32 API RegisterHotkey begin + RegistryErrorControlled := TRegistryErrorControlled.Create; + try + if RegistryErrorControlled.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and + RegistryErrorControlled.ValueExists(SRegValue_UseRightModifierHotKey) then + begin + UseRegisterHotKey := RegistryErrorControlled.ReadBool(SRegValue_UseRightModifierHotKey); + end + else + begin + UseRegisterHotKey := False; + end; + finally + RegistryErrorControlled.Free; + end; + + if not UseRegisterHotKey then Exit; TDebugLogClient.Instance.WriteMessage('Enter UnregisterHotkeys', []); diff --git a/windows/src/engine/keyman32/hotkeys.cpp b/windows/src/engine/keyman32/hotkeys.cpp index 8327a786498..e7fdbe3d79d 100644 --- a/windows/src/engine/keyman32/hotkeys.cpp +++ b/windows/src/engine/keyman32/hotkeys.cpp @@ -137,9 +137,13 @@ void Hotkeys::Load() { // I4390 Hotkey *Hotkeys::GetHotkey(DWORD hotkey) { - for(int i = 0; i < m_nHotkeys; i++) - if(m_hotkeys[i].HotkeyValue == hotkey) - return &m_hotkeys[i]; + for (int i = 0; i < m_nHotkeys; i++) { + if (m_hotkeys[i].HotkeyValue == hotkey) { + SendDebugMessageFormat( + "LanguageHotkey[%d] = {HotkeyValue: %x, hkl: %x} passed in: %x", i, m_hotkeys[i].HotkeyValue, m_hotkeys[i].hkl, hotkey); + return &m_hotkeys[i]; + } + } return NULL; } diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index f5fbdb8f301..2bdfae62e39 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -131,8 +131,10 @@ BOOL IsTouchPanelVisible() { /* Cache UseRightModifierHotKey debug flag for this session + when using right modifier for Hotkeys use the Win32 API + ResisterHotkey functionality */ -BOOL UseRightModifierHotKey() { +BOOL UseRegisterHotkey() { static BOOL flag_UseRightModifierHotKey = FALSE; static BOOL loaded = FALSE; @@ -169,31 +171,33 @@ LRESULT _kmnLowLevelKeyboardProc( if (GetKeyState(VK_LCONTROL) < 0) { FHotkeyShiftState |= HK_CTRL; - SendDebugMessageFormat("ProcessHotkey VK_LCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); + // TODO remove + SendDebugMessageFormat("ProcessHotkey VK_LCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRegisterHotkey()); } if (GetKeyState(VK_RCONTROL) < 0) { - FHotkeyShiftState |= UseRightModifierHotKey() ? HK_CTRL : HK_RCTRL_INVALID; - SendDebugMessageFormat("ProcessHotkey VK_RCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); + FHotkeyShiftState |= UseRegisterHotkey() ? HK_CTRL : HK_RCTRL_INVALID; + // TODO remove + SendDebugMessageFormat("ProcessHotkey VK_RCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRegisterHotkey()); } - + if (GetKeyState(VK_LMENU) < 0) { FHotkeyShiftState |= HK_ALT; } if (GetKeyState(VK_RMENU) < 0) { - FHotkeyShiftState |= UseRightModifierHotKey() ? HK_ALT : HK_RALT_INVALID; + FHotkeyShiftState |= UseRegisterHotkey() ? HK_ALT : HK_RALT_INVALID; } if (GetKeyState(VK_LSHIFT) < 0) { FHotkeyShiftState |= HK_SHIFT; } if (GetKeyState(VK_RSHIFT) < 0) { - FHotkeyShiftState |= UseRightModifierHotKey() ? HK_SHIFT : HK_RSHIFT_INVALID; + FHotkeyShiftState |= UseRegisterHotkey() ? HK_SHIFT : HK_RSHIFT_INVALID; } //if (GetKeyState(VK_SHIFT) < 0) { - // FHotkeyShiftState |= (!UseRightModifierHotKey() && extended) ? HK_RSHIFT_INVALID : HK_SHIFT; + // FHotkeyShiftState |= (!UseRegisterHotkey() && extended) ? HK_RSHIFT_INVALID : HK_SHIFT; //} // #7337 Post the modifier state ensuring the serialized queue is in sync @@ -275,28 +279,35 @@ LRESULT _kmnLowLevelKeyboardProc( } BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState) { - + if (UseRegisterHotkey()){ + return FALSE; + } Hotkeys *hotkeys = Hotkeys::Instance(); // I4641 if (!hotkeys) { + SendDebugMessageFormat("Failed to get Instance"); return FALSE; } Hotkey *hotkey = hotkeys->GetHotkey(ShiftState | vkCode); // I4641 if (!hotkey) { + SendDebugMessageFormat("GetHotkey Null"); return FALSE; } if (isUp) { + SendDebugMessageFormat("Is Up"); return TRUE; } if (hotkey->HotkeyType == hktInterface) { + SendDebugMessageFormat("PostMasterController"); Globals::PostMasterController(wm_keyman_control, MAKELONG(KMC_INTERFACEHOTKEY, hotkey->Target), 0); } else { + SendDebugMessageFormat("ReportKeyboardChanged"); ReportKeyboardChanged(PC_HOTKEYCHANGE, hotkey->hkl == 0 ? TF_PROFILETYPE_INPUTPROCESSOR : TF_PROFILETYPE_KEYBOARDLAYOUT, 0, hotkey->hkl, GUID_NULL, hotkey->profileGUID); } - + SendDebugMessageFormat("PostDummyKeyEvent"); /* Generate a dummy keystroke to block menu activations, etc but let the shift key through */ PostDummyKeyEvent(); // I3301 - this is imperfect because we don't deal with HC_NOREMOVE. But good enough? // I3534 // I4844 From 45dee1c504afdd40f5df000b08d07f7a810acbb3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 21 Aug 2024 09:05:48 +0700 Subject: [PATCH 101/262] docs(web): extends doc-comment per review comment --- web/src/engine/osk/src/visualKeyboard.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 0d529af651e..3a439c1db4e 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1608,6 +1608,11 @@ export default class VisualKeyboard extends EventEmitter implements Ke // Unset the width + height we used thus far; this method's consumer may choose to rescale // the returned element. If so, we don't want to use our outdated value by mistake. + // + // While `kbdObj.setSize()` could be used in theory, it _also_ unsets the element styling. + // We actually wish to _leave_ this styling in place - one of our parameters is `height`, and + // it should remain in place in the styling on the output element as the default in case + // the consumer _doesn't_ add styling afterward. delete kbdObj._width; delete kbdObj._height; From dc434a776a59dcbf417730c3a3753fd97b44c04c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 21 Aug 2024 10:51:35 +0700 Subject: [PATCH 102/262] fix(android): Use increment and decrement arrows on longpress delay --- .../src/main/res/layout/activity_adjust_longpress_delay.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml index ed0bed54a9b..ef1ebd613b3 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml @@ -23,7 +23,7 @@ android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:contentDescription="@string/ic_delay_time_down" - android:src="@drawable/ic_light_dialog_textsize_down" /> + android:src="@drawable/ic_action_decrement" /> + android:src="@drawable/ic_action_increment" /> From 88df68cf9f96308431f6dbf843adabe9d75c42bb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 21 Aug 2024 10:53:57 +0700 Subject: [PATCH 103/262] chore(common/models): reorganize wordbreaker build outputs --- common/models/wordbreakers/build.sh | 6 +++--- common/models/wordbreakers/package.json | 12 ++++++------ .../wordbreakers/tools/data-compiler/tsconfig.json | 4 ++-- common/models/wordbreakers/tsconfig.json | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index e0edda09774..0cad38b1fed 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -23,7 +23,7 @@ builder_describe "Builds the predictive-text wordbreaker implementation module" builder_describe_outputs \ configure src/main/default/data.inc.ts \ - build build/obj/index.js + build build/main/obj/index.js builder_parse "$@" @@ -34,14 +34,14 @@ function do_configure() { # default wordbreaker. We rarely update the backing data, but it # is needed _before_ the `build` action's compilation step. tsc -b tools/data-compiler/tsconfig.json - node ./build/obj/data-compiler/index.js + node ./build/data-compiler/obj/index.js } function do_build() { tsc -b ./tsconfig.json # Declaration bundling. - tsc -p ./tsconfig.json --emitDeclarationOnly --outFile ./build/lib/index.d.ts + tsc -p ./tsconfig.json --emitDeclarationOnly --outFile ./build/main/lib/index.d.ts } function do_test() { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index d433bcdff49..f896050ecb6 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -14,19 +14,19 @@ ], "homepage": "https://github.com/keymanapp/keyman", "license": "MIT", - "main": "build/obj/index.js", - "types": "build/obj/index.d.ts", + "main": "build/main/obj/index.js", + "types": "build/main/obj/index.d.ts", "exports": { ".": { "es6-bundling": "./src/main/index.ts", - "default": "./build/obj/index.js" + "default": "./build/main/obj/index.js" }, "./lib": { - "types": "./build/lib/index.d.ts" + "types": "./build/main/lib/index.d.ts" }, - "./obj/*.js": "./build/obj/*.js", + "./obj/*.js": "./build/main/obj/*.js", "./test-index": { - "default": "./build/obj/test-index.js" + "default": "./build/main/obj/test-index.js" }, "./README.md": { "default": "./README.md" diff --git a/common/models/wordbreakers/tools/data-compiler/tsconfig.json b/common/models/wordbreakers/tools/data-compiler/tsconfig.json index 19f5dfbab07..74a8bb005e9 100644 --- a/common/models/wordbreakers/tools/data-compiler/tsconfig.json +++ b/common/models/wordbreakers/tools/data-compiler/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "baseUrl": "./", - "outDir": "../../build/obj/data-compiler", - "tsBuildInfoFile": "../../build/obj/data-compiler/tsconfig.tsbuildinfo", + "outDir": "../../build/data-compiler/obj", + "tsBuildInfoFile": "../../build/data-compiler/obj/tsconfig.tsbuildinfo", "rootDir": "./", "module": "node16", "moduleResolution": "node16" diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json index 64d193242de..46095418b27 100644 --- a/common/models/wordbreakers/tsconfig.json +++ b/common/models/wordbreakers/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "baseUrl": "./", - "outDir": "./build/obj", - "tsBuildInfoFile": "./build/obj/tsconfig.tsbuildinfo", + "outDir": "./build/main/obj", + "tsBuildInfoFile": "./build/main/obj/tsconfig.tsbuildinfo", "rootDir": "./src/main" }, "references": [ From a26389bfeabe4313e77e4afad4530f90fa4ed907 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 21 Aug 2024 11:12:43 +0700 Subject: [PATCH 104/262] test(mac): removed commented code --- mac/Keyman4MacIM/KeymanTests/InputMethodTests.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index a2953c44cf5..7372a2a5486 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -96,7 +96,6 @@ - (void)testCalculateInsertRange_deleteOneBMPCharacterWithOneSelected_returnsRan */ - (void)testCheckCompliance_withUnknownApplicationId_createsComplianceObject { id client = [[AppleCompliantTestClient alloc] init]; - //NSString *clientAppId = @"com.compliant.app"; KMInputMethodEventHandler *eventHandler = [[KMInputMethodEventHandler alloc]initWithClient:nil client:client]; [eventHandler checkTextApiCompliance:client]; XCTAssertNotNil(eventHandler.apiCompliance, @"apiCompliance object was not created"); @@ -129,7 +128,4 @@ - (void)testCheckCompliance_withContextChanged_createsNewComplianceObject { XCTAssertNotEqualObjects(originalComplianceObject, testEventHandler.apiCompliance, @"New TextApiCompliance object not created after contextChanged flag set"); } - - - @end From 24f4a61adacbbfd7e721b48904282bfe9bae86e3 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 21 Aug 2024 17:05:07 +0700 Subject: [PATCH 105/262] change(mac): responded to review comments --- .../Keyman4MacIM/KMDataRepository.h | 6 +-- .../Keyman4MacIM/KMDataRepository.m | 42 +++++++++++++------ .../Keyman4MacIM/KMInputMethodEventHandler.m | 11 ----- .../Keyman4MacIM/KMPackageReader.m | 12 ------ .../Keyman4MacIM/KMSettingsRepository.m | 35 +++++++++------- .../KeymanTests/InputMethodTests.m | 14 ------- 6 files changed, 52 insertions(+), 68 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index a5af2ccb881..0a1cc8cca37 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMDataRepository.h @@ -13,9 +13,9 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject -@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' +@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/keyman.inputmethod.Keyman' @property (readonly) NSURL *keymanKeyboardsDirectory; -// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' +// keymanKeyboardsDirectory = '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' + (KMDataRepository *)shared; - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 24495007bd4..20d6e892a46 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMResourcesRepository.m @@ -15,30 +15,39 @@ #import "KMLogs.h" @interface KMDataRepository () -@property (readonly) NSURL *applicationSupportSubDirectory; // '~/Library/Application Support' -@property (readonly) NSURL *documentsSubDirectory; // '~/Documents' -@property (readonly) NSURL *obsoleteKeymanKeyboardsDirectory; // '~/Library/Documents/Keyman-Keyboards' +@property (readonly) NSURL *applicationSupportSubDirectory; +@property (readonly) NSURL *documentsSubDirectory; +@property (readonly) NSURL *obsoleteKeymanKeyboardsDirectory; @end @implementation KMDataRepository - +/** + * Two directory trees are represented by the following properties, one in active use + * and one that is obsolete. + * The actively used directories, begin with the parent + * applicationSupportSubDirectory: '~/Library/Application Support' + * keymanDataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' + * keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' + * The obsolete directories, begin with the parent + * documentsSubDirectory: '~/Documents' + * obsoleteKeymanKeyboardsDirectory: '~/Documents/Keyman-Keyboards' + */ @synthesize applicationSupportSubDirectory = _applicationSupportSubDirectory; -@synthesize documentsSubDirectory = _documentsSubDirectory; +@synthesize keymanDataDirectory = _keymanDataDirectory; @synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; +@synthesize documentsSubDirectory = _documentsSubDirectory; @synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; -@synthesize keymanDataDirectory = _keymanDataDirectory; NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; /* The name of the subdirectory within '~/Library/Application Support'. - The convention is to use bundle identifier. - (Using the subsystem id, "com.keyman.app", is a poor choice because the API + We follow the convention of using the bundle identifier rather than our subsystem id. + (Also, using the subsystem id, "com.keyman.app", is a poor choice because the API createDirectoryAtPath sees the .app extension and creates an application file. */ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; -+ (KMDataRepository *)shared -{ ++ (KMDataRepository *)shared { static KMDataRepository *shared = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -149,7 +158,11 @@ - (NSURL *)obsoleteKeymanKeyboardsDirectory { } return _obsoleteKeymanKeyboardsDirectory; } - +/** + * Only called from migrateData. + * Causes user to be prompted for permission to access ~/Documents, but they should already have it. + * otherwise we would not be attempting to migrate. + */ - (BOOL)keyboardsExistInObsoleteDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; @@ -158,7 +171,10 @@ - (BOOL)keyboardsExistInObsoleteDirectory { } /** - * Migrate the keyboards data from the old location in '/Documents' to the new location '/Application Support/keyman.inputmethod.Keyman/' + * Migrate the keyboards data from the old location in '~/Documents' to the new location '~/Application Support/keyman.inputmethod.Keyman/' + * This should only be called if the Keyman settings written to the UserDefaults indicates that we have data in the old location. + * If this is the case, then we expect the user to have already granted permission for Keyman to access the ~/Documents directory. + * If that permission has been removed for some reason, then calling this code will cause the user to be asked for permission again. */ - (BOOL)migrateData { BOOL didMoveData = NO; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index efe628c43a3..02521b51cb3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -243,17 +243,6 @@ - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { // return NO to pass through to client app return NO; } - - // TODO: remove test code - /* - if (event.keyCode == kVK_ANSI_Slash) { - if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { - os_log_info([KMLogs startupLog], "keyboards migration needed"); - } else { - os_log_info([KMLogs startupLog], "keyboards migration not needed"); - } - } - */ } if (event.type == NSEventTypeFlagsChanged) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m index 186ec8ab5a0..bd9156bdb50 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -69,18 +69,6 @@ - (KMPackageInfo *)loadPackageInfo:(NSString *)path { return packageInfo; } -/* - // TODO: not used, delete -- (NSString *)packageNameFromPackageInfo:(NSString *)path { - NSString *packageName = nil; - - KMPackageInfo *packageInfo = [self loadPackageInfo:path]; - packageName = packageInfo.packageName; - - return packageName; -} -*/ - /** * read JSON file and load it into KMPackageInfo object * returns nil if the file does not exist or it cannot be parsed as JSON diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 489c4e63dc6..a605ea6e6ec 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMSettingsRepository.h @@ -27,6 +27,7 @@ */ NSString *const kDataModelVersion = @"KMDataModelVersion"; NSInteger const kVersionStoreDataInLibraryDirectory = 1; +NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirectory; @implementation KMSettingsRepository @@ -41,7 +42,7 @@ + (KMSettingsRepository *)shared } - (void)setDataModelVersionIfNecessary { - if (![self dataStoredInLibraryDirectory]) { + if (![self dataModelWithKeyboardsInLibrary]) { [[NSUserDefaults standardUserDefaults] setInteger:kVersionStoreDataInLibraryDirectory forKey:kDataModelVersion]; } } @@ -51,34 +52,38 @@ - (void)setDataModelVersionIfNecessary { * If this method is called after applicationDidFinishLaunching, then it will always return true. * If called from awakeFromNib, then it will return false when running for the first time. */ -- (BOOL)settingsExist -{ +- (BOOL)settingsExist { return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil; } /** - * For the first numbered version of the data model, the app stores the keyboards under the /Library directory - * For versions before version 1, the keyboards were stored under the /Documents directory. + * For the first numbered version of the data model, the app stores the keyboards under the ~/Library directory + * For versions before version 1, the keyboards were stored under the ~/Documents directory. */ -- (BOOL)dataStoredInLibraryDirectory -{ - return [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion] == kVersionStoreDataInLibraryDirectory; +- (BOOL)dataModelWithKeyboardsInLibrary { + // NSUserDefaults returns zero if the key does not exist + NSInteger dataModelVersion = [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion]; + + return dataModelVersion >= kVersionStoreDataInLibraryDirectory; } /** - * Determines whether the keyboard data needs to be moved from the old location in ~/Documents to the new location ~/Library... + * Determines whether the keyboard data needs to be moved from the old location to the new location * This is true if * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and - * 2) the value for KMStoreKeyboardsInLibraryKey is not set to true + * 2) the value for kVersionStoreDataInLibraryDirectory is < 1, */ - (BOOL)dataMigrationNeeded { BOOL keymanSettingsExist = [self settingsExist]; - os_log([KMLogs dataLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); + os_log([KMLogs dataLog], "keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); - BOOL dataInLibrary = [self dataStoredInLibraryDirectory]; - os_log([KMLogs dataLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); + BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; + os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); - return !(keymanSettingsExist && dataInLibrary); + BOOL migrationNeeded = keymanSettingsExist && !keyboardsStoredInLibrary; + os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); + + return migrationNeeded; } - (NSString *)selectedKeyboard { diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 38994c351a5..5a339931e70 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -88,18 +88,4 @@ - (void)testCalculateInsertRange_deleteOneBMPCharacterWithOneSelected_returnsRan XCTAssertTrue(correctResult, @"insert or replacement range expected to be {1,2}"); } -- (void)testMigrateData_oldDataExists_logsLocations { - os_log_t startupLog = os_log_create("com.keyman.app", "data-migration"); - if ([KMSettingsRepository.shared dataMigrationNeeded]) { - os_log_info(startupLog, "data migration needed, calling migrateData"); - [KMDataRepository.shared migrateData]; - os_log_info(startupLog, "test: call migrateData again"); - [KMDataRepository.shared migrateData]; - } else { - os_log_info(startupLog, "data migration not needed"); - } - - XCTAssertTrue(YES, @"test failed"); -} - @end From 17f903d4c70efb164fa29534acc6766c74373de9 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 21 Aug 2024 15:08:51 +0700 Subject: [PATCH 106/262] fix(android): Fix navigation arrows in Info Activity for RTL --- .../src/main/res/layout/activity_info.xml | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml index 3b71e871785..314a5034129 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml @@ -1,12 +1,12 @@ + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@color/ms_white" + tools:context=".InfoActivity" > - - + android:orientation="horizontal"> - + + + + + Date: Wed, 21 Aug 2024 14:24:10 +0200 Subject: [PATCH 107/262] chore(developer): enable build for emscripten 3.1.60+ Emscripten 3.1.60 made a breaking change to `BindingType::toWireType` (why is this done in a patch version?) so this change ensures that we can continue to build on 3.1.58 and 3.1.64 (which is needed for #12234), as a bridging strategy. We will need to merge this into 17.0-stable as well so that we can upgrade the build agents to 3.1.64. Relates-to: emscripten-core/emscripten#21692 Relates-to: #12234 --- developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp index a857b41dce4..1ddd45dd3f8 100644 --- a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp @@ -75,9 +75,16 @@ struct BindingType> { using ValBinding = BindingType; using WireType = ValBinding::WireType; +#if __EMSCRIPTEN_major__ == 3 && __EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 60 + // emscripten-core/emscripten#21692 + static WireType toWireType(const std::vector &vec, rvp::default_tag) { + return ValBinding::toWireType(val::array(vec), rvp::default_tag{}); + } +#else static WireType toWireType(const std::vector &vec) { return ValBinding::toWireType(val::array(vec)); } +#endif static std::vector fromWireType(WireType value) { return vecFromJSArray(ValBinding::fromWireType(value)); From b0bd95d276eddae88b170671d8384af0418a04d3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 21 Aug 2024 15:02:18 +0200 Subject: [PATCH 108/262] chore(common): allow build agents to automatically select emsdk version The build agents will now automatically install and activate the Emscripten version found in minimum-versions.inc.sh when configuring WASM projects. For developer machines, this behaviour can be enabled by setting the environment variable `KEYMAN_USE_EMSDK` (recommended). This will be back-ported to 17.0 so that we can ensure that all builds use a consistent Emscripten version. --- resources/build/minimum-versions.inc.sh | 3 +-- resources/locate_emscripten.inc.sh | 36 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh index c060d31c58f..97f25094067 100644 --- a/resources/build/minimum-versions.inc.sh +++ b/resources/build/minimum-versions.inc.sh @@ -20,8 +20,7 @@ KEYMAN_MIN_TARGET_VERSION_CHROME=95.0 # Final version that runs on Andro # Dependency versions KEYMAN_MIN_VERSION_NODE_MAJOR=20 # node version source of truth is /package.json:/engines/node KEYMAN_MIN_VERSION_NPM=10.5.1 # 10.5.0 has bug, discussed in #10350 -KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.44 # Warning: 3.1.45 is bad (#9529); newer versions work -KEYMAN_MAX_VERSION_EMSCRIPTEN=3.1.58 # See #9529 +KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automatically update to this version KEYMAN_MIN_VERSION_VISUAL_STUDIO=2019 KEYMAN_MIN_VERSION_MESON=1.0.0 diff --git a/resources/locate_emscripten.inc.sh b/resources/locate_emscripten.inc.sh index 20923770e2a..f678e8449e6 100644 --- a/resources/locate_emscripten.inc.sh +++ b/resources/locate_emscripten.inc.sh @@ -1,6 +1,8 @@ # shellcheck shell=bash # no hashbang for .inc.sh +. "$KEYMAN_ROOT/resources/build/minimum-versions.inc.sh" + # # We don't want to rely on emcc being on the path, because Emscripten puts far # too many things onto the path (in particular for us, node). @@ -34,4 +36,38 @@ locate_emscripten() { fi [[ -f ${EMSCRIPTEN_BASE}/${EMCC_EXECUTABLE} && ! -x ${EMSCRIPTEN_BASE}/${EMCC_EXECUTABLE} ]] && builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) contains ${EMCC_EXECUTABLE} but it is not executable" [[ -x ${EMSCRIPTEN_BASE}/${EMCC_EXECUTABLE} ]] || builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) does not point to ${EMCC_EXECUTABLE}'s folder" + + verify_emscripten_version +} + +# Ensure that we use correct version of emsdk on build agents. +# For developers, define KEYMAN_USE_SDK to do this on your +# build machine. +verify_emscripten_version() { + if [[ "$VERSION_ENVIRONMENT" != local || ! -z "${KEYMAN_USE_EMSDK+x}" ]]; then + _select_emscripten_version_with_emsdk + fi +} + +# Use emsdk to select the appropriate version of Emscripten +# according to minimum-versions.inc.sh +_select_emscripten_version_with_emsdk() { + if [[ -z "${EMSCRIPTEN_BASE+x}" ]]; then + builder_die "Variable EMSCRIPTEN_BASE must be set" + fi + + if [[ -z "${KEYMAN_MIN_VERSION_EMSCRIPTEN+x}" ]]; then + builder_die "Variable KEYMAN_MIN_VERSION_EMSCRIPTEN must be set" + fi + + pushd "${EMSCRIPTEN_BASE}/../.." > /dev/null + if [[ ! -f emsdk ]]; then + builder_die "emsdk[.bat] should be in $(pwd)" + fi + + export EMSDK_KEEP_DOWNLOADS=1 + git pull + ./emsdk install "$KEYMAN_MIN_VERSION_EMSCRIPTEN" + ./emsdk activate "$KEYMAN_MIN_VERSION_EMSCRIPTEN" + popd > /dev/null } From 79bd3830b577018ae7996ffcc6945b031ca4e3fd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 21 Aug 2024 18:57:45 +0200 Subject: [PATCH 109/262] docs: refresh windows.md @keymanapp-test-bot skip --- docs/build/windows.md | 177 +++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 105 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index dcb55ce4a42..bb8ce8f0feb 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -63,10 +63,6 @@ Dependencies: * [Base](#base-dependencies) * [Windows Platform](#windows-platform-dependencies) -**Note**: Keyman for Windows is currently built together with Keyman Developer. -We are working on splitting these projects. For now, you will need the Keyman -Developer dependencies as well. - Building: * [Building Keyman for Windows](../../windows/src/README.md) @@ -75,7 +71,7 @@ Building: Dependencies: * [Base](#base-dependencies) * [Web](#web-dependencies) -* [Windows Platform](#windows-platform-dependencies) +* [Windows Platform](#windows-platform-dependencies) (optional, for Windows-only components) Building: * [Building Keyman Developer](../../windows/src/README.md) @@ -94,42 +90,19 @@ Building: **Dependencies**: * [Base](#base-dependencies) * [Web](#web-dependencies) - -**Additional requirements**: -* Android SDK -* Android Studio -* Ant -* Gradle -* Maven -* Optional: OpenJDK 11 (https://learn.microsoft.com/en-us/java/openjdk/download) - -```ps1 -# Elevated PowerShell -choco install android-sdk android-studio ant gradle maven -# optionally install sdk images -sdkmanager "system-images;android-33;google_apis;armeabi-v7a" -sdkmanager --update -sdkmanager --licenses -``` - -* Run Android Studio once after installation to install additional components - such as emulator images and SDK updates. - -**Required environment variables**: -* [`JAVA_HOME`](#java_home) - -**Optional environment variables**: -* [`JAVA_HOME_11`](#java_home) +* [Android](#android-dependencies) Building: * [Building Keyman for Android](../../android/README.md) -## Prerequisites +--- + +## Dependencies and Prerequisites Many dependencies are only required for specific projects. We prefer [Chocolatey](https://chocolatey.org/install) at present for -installation of dependencies. Chocolatey should be run in an elevated +installation of most dependencies. Chocolatey should be run in an elevated PowerShell. ### Base Dependencies @@ -155,11 +128,9 @@ refreshenv **Environment variables**: * [`KEYMAN_ROOT`](#keyman_root) -* `PATH`: add your Python scripts folder to your path: it will normally be `%appdata%\Python\Python310\Scripts`. ```bat SET KEYMAN_ROOT=c:\Projects\keyman\keyman -SET PATH=%path%;%appdata%\Python\Python310\Scripts ``` To check whether environment variables are set, run `SET ` in command @@ -181,31 +152,37 @@ You can use Windows Settings to add these environment variables permanently: * KeymanWeb **Requirements**: -* emscripten 3.1.46 or later -* node.js 18+ -* [openjdk 11](https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-11)+ +* Emscripten +* node.js -```ps1 -# Elevated PowerShell +#### Emscripten -# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) -$ProgressPreference = 'SilentlyContinue' +In an appropriate folder, e.g. `/c/Projects/keyman/`, in bash, run the following commands: -choco install emscripten --version 3.1.46 +```bash +git clone https://github.com/emscripten-core/emsdk +cd emsdk +emsdk install 3.1.58 +emsdk activate 3.1.58 ``` Note: emscripten very unhelpfully overwrites JAVA_HOME, and adds its own -versions of Python, Node and Java to the PATH. For best results, go ahead -and remove those paths from your PATH variable before continuing. +versions of Python, Node and Java to the PATH. For best results, restart +your shell after installing Emscripten so that you don't end up with the +wrong versions. There is no need to add emscripten to the path in order to build Keyman. However, you should set the EMSCRIPTEN_BASE variable to the path where `emcc` can be found, but always in the upstream\emscripten subdirectory where you -installed emsdk (most likely %LocalAppData%\emsdk\upstream\emscripten) +installed emsdk. **Environment variables**: * `EMSCRIPTEN_BASE`: `\upstream\emscripten` +**Optional environment variables**: +* `KEYMAN_USE_EMSDK`: `1` to let the Keyman build scripts control the + version of Emscripten installed on your computer. + After installing emscripten, you'll need to install node.js and openjdk. #### node.js @@ -214,30 +191,17 @@ Our recommended way to install node.js is to use [nvm-windows](https://github.com/coreybutler/nvm-windows). This makes it easy to switch between versions of node.js. -Alternatively, use Powershell + Chocolatey to install node.js: - -```ps1 -# Elevated PowerShell - -# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) -$ProgressPreference = 'SilentlyContinue' -choco install nodejs ``` - -See [node.md](node.md) for more information. - -#### openjdk - -Use Powershell + Chocolatey to install OpenJDK: - -```ps1 -# Elevated PowerShell - -# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) -$ProgressPreference = 'SilentlyContinue' -choco install openjdk +nvm install 20.16.0 +nvm use 20.16.0 ``` +**Optional environment variables**: +* `KEYMAN_USE_NVM`: `1` to let the Keyman build scripts control the + version of node.js installed and active on your computer. + +See [node.md](node.md) for more information, including automatic selection +of appropriate node versions during builds. ### Windows Platform Dependencies @@ -260,8 +224,7 @@ choco install openjdk Start Delphi IDE once after installation as it will create various environment files and take you through required registration. - * Note: It is possible to build all components that do _not_ require Delphi by - adding the environment variable `NODELPHI=1` before starting the build. + * Note: It is possible to build all components that do _not_ require Delphi. Currently many components are Delphi-based, but if you are working just in Keyman Core, the compiler, or Keyman Engine's C++ components, you may be able to get away without building them. In this situation, we recommend @@ -291,13 +254,8 @@ choco install openjdk Keyman's design-time packages to load in Delphi. * [`KEYMAN_CEF4DELPHI_ROOT`](#keyman_cef4delphi_root) -**Optional environment variables**: -* [`GIT_BASH_FOR_KEYMAN`](#git_bash_for_keyman) -* [`USERDEFINES`](#userdefines) - ```bat SET KEYMAN_CEF4DELPHI_ROOT=c:\Projects\keyman\CEF4Delphi_Binary -SET GIT_BASH_FOR_KEYMAN="C:\Program Files\Git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l ``` **Additional requirements for release builds**: @@ -314,6 +272,46 @@ choco install wixtoolset --version=3.11.1 git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4Delphi_Binary ``` +### Android dependencies + +**Projects**: +* Keyman for Android + +**Requirements**: +* Android SDK +* Android Studio +* Ant +* Gradle +* Maven +* Optional: OpenJDK 11 (https://learn.microsoft.com/en-us/java/openjdk/download) + +```ps1 +# Elevated PowerShell +choco install android-sdk android-studio ant gradle maven +# optionally install sdk images +sdkmanager "system-images;android-33;google_apis;armeabi-v7a" +sdkmanager --update +sdkmanager --licenses +``` + +#### openjdk + +Use Powershell + Chocolatey to install OpenJDK: + +```ps1 +# Elevated PowerShell + +# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) +$ProgressPreference = 'SilentlyContinue' +choco install openjdk +``` + +* Run Android Studio once after installation to install additional components + such as emulator images and SDK updates. + +**Required environment variables**: +* [`JAVA_HOME`](#java_home) + ## Certificates In order to make a release build, you need to sign all the executables. See @@ -349,41 +347,10 @@ release build, the common/windows/cef-checkout.sh script will checkout the corre branch of the repository automatically and extract any compressed files found in it. -### GIT_BASH_FOR_KEYMAN - -This environment variable is optional: the build will run bash in a separate -window in order to build KeymanWeb if it isn't present, but you'll lose logging -and have the annoyance of a window popping up halfway through the build. To -resolve both of those issues, set the environment variable to: - -```bat -SET GIT_BASH_FOR_KEYMAN="C:\Program Files\Git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l -``` - -You should verify the install location of Git on your computer as it may vary. - -### USERDEFINES - -You can specify defines that will not be added to the git repository and will be -used in the build in the UserDefines.mak file in the root folder. This is used -mostly for code signing certificates. If not specified, a test certificate will -be used to sign executables when you build a release. - -To include UserDefines.mak in the build, use the command line parameter -`-DUSERDEFINES`. You can also set an environment variable `USERDEFINES=1` to get -the same result. - ### JAVA_HOME This environment variable tells Gradle what version of Java to use for building Keyman for Android. -**Multiple versions of Java:** If you need to build Keyman for Android 16.0 or older versions, you can set `JAVA_HOME_11` to the OpenJDK 11 path and `JAVA_HOME` to the OpenJDK 8 path. This will build both versions correctly from command line. But note that you do need to update your `JAVA_HOME` env var to the associated version before opening Android Studio and loading any Android projects. `JAVA_HOME_11` is mostly used by CI. - -```bat -SET JAVA_HOME="path to OpenJDK 8" -SET JAVA_HOME_11="path to OpenJDK 11" -``` - ## Optional Tools * sentry-cli (optional) From 002970d7cb750b00c38757e91978db27bdfa00de Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 21 Aug 2024 19:10:56 +0200 Subject: [PATCH 110/262] Update windows.md --- docs/build/windows.md | 117 +++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index bb8ce8f0feb..83839f91a15 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -127,22 +127,32 @@ refreshenv ``` **Environment variables**: -* [`KEYMAN_ROOT`](#keyman_root) + +If you pull the entire `keyman.git` repo to `c:\keyman`, then the paths by +default will work without changes. Otherwise, you will need to set an +environment variable `KEYMAN_ROOT` to the root path of the Keyman repo. For +example: ```bat -SET KEYMAN_ROOT=c:\Projects\keyman\keyman +SETX KEYMAN_ROOT "c:\Projects\keyman\keyman" ``` -To check whether environment variables are set, run `SET ` in command -prompt. - -You can use Windows Settings to add these environment variables permanently: - -1. In Windows Search, type "environment" and select "Edit System Environment - Variables" -2. Click `Environment Variables...` -3. You can add or edit variables in either User or System settings, as you - prefer. +> [!NOTE] +> The `SETX` command will set persistent environment variables but they do not +> impact the current shell environment. Start a new shell to see the variables. + +> [!TIP] +> +> To check whether environment variables are set, run `SET ` in command +> prompt. +> +> You can use Windows Settings to add these environment variables permanently: +> +> 1. In Windows Search, type "environment" and select "Edit System Environment +> Variables" +> 2. Click `Environment Variables...` +> 3. You can add or edit variables in either User or System settings, as you +> prefer. ### Web Dependencies @@ -177,11 +187,19 @@ can be found, but always in the upstream\emscripten subdirectory where you installed emsdk. **Environment variables**: -* `EMSCRIPTEN_BASE`: `\upstream\emscripten` + +```bat +SETX EMSCRIPTEN_BASE "\upstream\emscripten" +``` **Optional environment variables**: -* `KEYMAN_USE_EMSDK`: `1` to let the Keyman build scripts control the - version of Emscripten installed on your computer. + +To let the Keyman build scripts control the version of Emscripten +installed on your computer: + +```bat +SETX KEYMAN_USE_EMSDK 1 +``` After installing emscripten, you'll need to install node.js and openjdk. @@ -191,14 +209,19 @@ Our recommended way to install node.js is to use [nvm-windows](https://github.com/coreybutler/nvm-windows). This makes it easy to switch between versions of node.js. -``` +```bat nvm install 20.16.0 nvm use 20.16.0 ``` **Optional environment variables**: -* `KEYMAN_USE_NVM`: `1` to let the Keyman build scripts control the - version of node.js installed and active on your computer. + +To let the Keyman build scripts control the version of node.js installed +and active on your computer: + +```bat +SETX KEYMAN_USE_NVM 1 +```` See [node.md](node.md) for more information, including automatic selection of appropriate node versions during builds. @@ -252,10 +275,27 @@ of appropriate node versions during builds. * Add the C:\Projects\keyman\keyman\windows\lib folder in the Keyman repository to your `PATH` environment variable. This is required for Keyman's design-time packages to load in Delphi. -* [`KEYMAN_CEF4DELPHI_ROOT`](#keyman_cef4delphi_root) + +### KEYMAN_CEF4DELPHI_ROOT + +Keyman and Keyman Developer use Chromium Embedded Framework. The source repo is +at https://github.com/keymanapp/CEF4Delphi. In order to build the installers, we +need to source the binary files from the +https://github.com/keymanapp/CEF4Delphi_binary repo. The +`KEYMAN_CEF4DELPHI_ROOT` environment variable should be set to the root of this +repo on your local machine. + +The version of CEF in use is determined by CEF_VERSION.md. This maps to a branch +prefixed with `v` e.g. `v89.0.18` in the CEF4Delphi_binary repository. During a +release build, the common/windows/cef-checkout.sh script will checkout the correct +branch of the repository automatically and extract any compressed files found in +it. + +The [`KEYMAN_CEF4DELPHI_ROOT`](#keyman_cef4delphi_root) variable is +used to specify the path to the CEF4Delphi binaries. ```bat -SET KEYMAN_CEF4DELPHI_ROOT=c:\Projects\keyman\CEF4Delphi_Binary +SETX KEYMAN_CEF4DELPHI_ROOT "c:\Projects\keyman\CEF4Delphi_Binary" ``` **Additional requirements for release builds**: @@ -309,8 +349,11 @@ choco install openjdk * Run Android Studio once after installation to install additional components such as emulator images and SDK updates. + ## Certificates @@ -319,38 +362,6 @@ In order to make a release build, you need to sign all the executables. See for details on how to create test code signing certificates or specify your own certificates for the build. -## Notes on Environment Variables - -### KEYMAN_ROOT - -If you pull the entire `keyman.git` repo to `c:\keyman`, then the paths by -default will work without changes. Otherwise, you will need to set an -environment variable `KEYMAN_ROOT` to the root path of the Keyman repo. For -example: - -```bat -SET KEYMAN_ROOT=c:\projects\keyman\keyman -``` - -### KEYMAN_CEF4DELPHI_ROOT - -Keyman and Keyman Developer use Chromium Embedded Framework. The source repo is -at https://github.com/keymanapp/CEF4Delphi. In order to build the installers, we -need to source the binary files from the -https://github.com/keymanapp/CEF4Delphi_binary repo. The -`KEYMAN_CEF4DELPHI_ROOT` environment variable should be set to the root of this -repo on your local machine. - -The version of CEF in use is determined by CEF_VERSION.md. This maps to a branch -prefixed with `v` e.g. `v89.0.18` in the CEF4Delphi_binary repository. During a -release build, the common/windows/cef-checkout.sh script will checkout the correct -branch of the repository automatically and extract any compressed files found in -it. - -### JAVA_HOME - -This environment variable tells Gradle what version of Java to use for building Keyman for Android. - ## Optional Tools * sentry-cli (optional) From 98409dc81085f3799f35fcd2762a75eaf7fafc82 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 21 Aug 2024 14:04:21 -0400 Subject: [PATCH 111/262] auto: increment master version to 18.0.95 --- HISTORY.md | 10 ++++++++++ VERSION.md | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 0c527e2390e..7bb236717bb 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,15 @@ # Keyman Version History +## 18.0.94 alpha 2024-08-21 + +* fix(core): look for `emcc` instead of `emcc.py` (#12235) +* fix(web): improve tokenization output when wordbreaker breaks spec for span properties in output (#12229) +* chore(android): Use RTL-aware alignment and padding for layouts (#12225) +* fix(web): disable fat-finger data use when mayCorrect = false (#12220) +* fix(android): Auto-mirror back and forward arrows for RTL support (#12227) +* feat(android): Add localization for Arabic (#12228) +* fix(android): Auto-mirror increment and decrement arrows for RTL support (#12230) + ## 18.0.93 alpha 2024-08-20 * (#12188) diff --git a/VERSION.md b/VERSION.md index 8f14bdc62b3..a41e5425c0a 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.94 \ No newline at end of file +18.0.95 \ No newline at end of file From b87b107966dab09840bab8503f58dd54355767c5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 21 Aug 2024 22:12:00 +0200 Subject: [PATCH 112/262] Update windows.md --- docs/build/windows.md | 47 +++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index 83839f91a15..47b3e8696da 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -6,13 +6,13 @@ On Windows, you can build the following projects: * [Keyman for Android](#keyman-for-android) * [Keyman for Windows](#keyman-for-windows) -* [Keyman Developer](#keyman-developer) (together with Keyman for Windows) +* [Keyman Developer](#keyman-developer) * [KeymanWeb](#keymanweb) The following libraries can also be built: -* Keyman Core (Windows, wasm targets) (aka core) -* Common/Web +* Keyman Core (Windows, wasm targets) +* Common libraries The following projects **cannot** be built on Windows: @@ -123,7 +123,6 @@ PowerShell. # for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) $ProgressPreference = 'SilentlyContinue' choco install git jq python ninja pandoc meson -refreshenv ``` **Environment variables**: @@ -146,7 +145,8 @@ SETX KEYMAN_ROOT "c:\Projects\keyman\keyman" > To check whether environment variables are set, run `SET ` in command > prompt. > -> You can use Windows Settings to add these environment variables permanently: +> You can alternatively use Windows Settings to add these environment variables +> permanently: > > 1. In Windows Search, type "environment" and select "Edit System Environment > Variables" @@ -167,24 +167,26 @@ SETX KEYMAN_ROOT "c:\Projects\keyman\keyman" #### Emscripten -In an appropriate folder, e.g. `/c/Projects/keyman/`, in bash, run the following commands: +In bash, run the following commands: ```bash +cd /c/Projects/keyman git clone https://github.com/emscripten-core/emsdk cd emsdk emsdk install 3.1.58 emsdk activate 3.1.58 ``` -Note: emscripten very unhelpfully overwrites JAVA_HOME, and adds its own -versions of Python, Node and Java to the PATH. For best results, restart -your shell after installing Emscripten so that you don't end up with the -wrong versions. +> ![WARNING] +> Emscripten very unhelpfully overwrites `JAVA_HOME`, and adds its own +> versions of Python, Node and Java to the `PATH`. For best results, restart +> your shell after installing Emscripten so that you don't end up with the +> wrong versions. There is no need to add emscripten to the path in order to build Keyman. -However, you should set the EMSCRIPTEN_BASE variable to the path where `emcc` -can be found, but always in the upstream\emscripten subdirectory where you -installed emsdk. +However, you should set the `EMSCRIPTEN_BASE` variable to the path where `emcc` +can be found, in the `upstream\emscripten` subdirectory of where you installed +emsdk. **Environment variables**: @@ -259,10 +261,12 @@ of appropriate node versions during builds. ```ps1 choco install visualstudio2019community visualstudio2019-workload-nativedesktop visualstudio2019buildtools ``` + * Verify required build tools are installed * Run `Visual Studio Installer` * Check the `Individual components` tab - * Verify `MSVC v142 - VS 2019 c++ x64/x86 build tools (Latest)` is installed. If not, install it. + * Verify `MSVC v142 - VS 2019 c++ x64/x86 build tools (Latest)` is installed. + If not, install it. Recommended: configure Visual Studio to use two-space tab stops: 1. Open the options dialog: Tools > Options. @@ -272,7 +276,7 @@ of appropriate node versions during builds. **Required environment variables**: * `PATH` - * Add the C:\Projects\keyman\keyman\windows\lib folder in the Keyman + * Add the `C:\Projects\keyman\keyman\windows\lib` folder in the Keyman repository to your `PATH` environment variable. This is required for Keyman's design-time packages to load in Delphi. @@ -327,7 +331,12 @@ git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4 ```ps1 # Elevated PowerShell -choco install android-sdk android-studio ant gradle maven +choco install android-sdk androidstudio ant gradle maven +``` + +Start a new shell to get the new paths and then update Android SDKs: + +```ps1 # optionally install sdk images sdkmanager "system-images;android-33;google_apis;armeabi-v7a" sdkmanager --update @@ -349,12 +358,6 @@ choco install openjdk * Run Android Studio once after installation to install additional components such as emulator images and SDK updates. - - ## Certificates In order to make a release build, you need to sign all the executables. See From ed8f8508d390bfe9c588007f877e6b5c86e8db5f Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:42:23 +1000 Subject: [PATCH 113/262] feat(windows): update sil logo for windows UI --- oem/firstvoices/windows/src/xml/config.css | 10 +++++----- windows/src/desktop/kmshell/xml/config.css | 8 ++++---- windows/src/desktop/kmshell/xml/splash.css | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/oem/firstvoices/windows/src/xml/config.css b/oem/firstvoices/windows/src/xml/config.css index 28f09464765..763c7229a1c 100644 --- a/oem/firstvoices/windows/src/xml/config.css +++ b/oem/firstvoices/windows/src/xml/config.css @@ -762,10 +762,10 @@ th color: #444; position: absolute; bottom: 32px; - left: 80px; - height: 50px; - padding-top: 25px; - padding-left: 56px; + left: 73px; + height: 64px; + padding-top: 32px; + padding-left: 110px; box-sizing: border-box; } @@ -1021,4 +1021,4 @@ th width:100%; height:100%; border:none; -} \ No newline at end of file +} diff --git a/windows/src/desktop/kmshell/xml/config.css b/windows/src/desktop/kmshell/xml/config.css index 88c8d22b39c..851964112bf 100644 --- a/windows/src/desktop/kmshell/xml/config.css +++ b/windows/src/desktop/kmshell/xml/config.css @@ -840,10 +840,10 @@ th color: #444; position: absolute; bottom: 32px; - left: 80px; - height: 50px; - padding-top: 25px; - padding-left: 56px; + left: 73px; + height: 64px; + padding-top: 32px; + padding-left: 110px; box-sizing: border-box; } diff --git a/windows/src/desktop/kmshell/xml/splash.css b/windows/src/desktop/kmshell/xml/splash.css index 413280ce057..de0a33b188f 100644 --- a/windows/src/desktop/kmshell/xml/splash.css +++ b/windows/src/desktop/kmshell/xml/splash.css @@ -33,7 +33,7 @@ div { } #silLogo { - position: absolute; right: 20px; top: 24px; background: url("sil-logo-blue.png"); width: 48px; height: 50px; + position: absolute; right: 20px; top: 15px; background: url("sil-logo-blue.png"); width: 107px; height: 64px; } #family { From 05d52463f95dbee638ac7b0cd0fc7337b6fa0da2 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:45:23 +1000 Subject: [PATCH 114/262] feat(windows): remove registerhotkey and cache modifier Remove the feature flags that allows the use of cached modifier for hotkeys. Remove the feature flag that uses the Win 32 API RegisterHotkeys. --- .../windows/delphi/general/RegistryKeys.pas | 1 - windows/src/desktop/kmshell/xml/strings.xml | 16 +- windows/src/engine/keyman/UfrmKeyman7Main.pas | 153 ------------------ .../keyman32/k32_lowlevelkeyboardhook.cpp | 65 ++------ .../engine/kmcomapi/util/utilkeymanoption.pas | 3 +- .../delphi/general/KeymanOptionNames.pas | 5 +- 6 files changed, 22 insertions(+), 221 deletions(-) diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 0553b823065..1557de58657 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -115,7 +115,6 @@ interface SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU - SRegValue_UseRightModifierHotKey = 'use right modifier for hotkey'; // CU SRegValue_ReleaseShiftKeysAfterKeyPress = 'release shift keys after key press'; // CU SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index e15ba789453..774a4d5e425 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -377,11 +377,6 @@ Simulate AltGr with Ctrl+Alt - - - - Right Modifier keys work with Hotkeys - @@ -797,13 +792,18 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman Start Keyman + + + + Start %1$s + @@ -860,7 +860,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman @@ -1026,7 +1026,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index fbed70b24d0..d8cb3e30e73 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -228,9 +228,6 @@ TfrmKeyman7Main = class(TForm) FActiveHKL: Integer; FTrayIcon: TIcon; // I4359 - FHotkeyWindow: HWND; - FHotkeys: TIntegerList; - FInputPane: TFrameworkInputPane; FIsInputPaneVisible: Boolean; @@ -295,12 +292,6 @@ TfrmKeyman7Main = class(TForm) procedure UnregisterControllerWindows; // I4731 function IsSysTrayWindow(AHandle: THandle): Boolean; procedure GetTrayIconHandle; // I4731 - - procedure RegisterHotkeys; - procedure UnregisterHotkeys; - procedure HotkeyWndProc(var Message: TMessage); - - procedure DoLanguageHotkey(Index: Integer); protected procedure DoInterfaceHotkey(Target: Integer); @@ -550,8 +541,6 @@ procedure TfrmKeyman7Main.FormDestroy(Sender: TObject); FreeAndNil(FInputPane); - UnregisterHotkeys; - with TRegistryErrorControlled.Create do // I2890 try if OpenKey(SRegKey_KeymanOSK_CU, True) then @@ -833,7 +822,6 @@ function TfrmKeyman7Main.ProcessWMKeymanControl(Command, WParam: Word; LParam: D end; FOSKManuallyClosedThisSession := False; FRunningProduct.FLangSwitchConfiguration.Refresh; - RegisterHotkeys; end; KMC_NOTIFYWELCOME: // I1248 - Redesigned welcome begin @@ -869,23 +857,6 @@ function TfrmKeyman7Main.ProcessWMKeymanControl(Command, WParam: Word; LParam: D end; end; -procedure TfrmKeyman7Main.DoLanguageHotkey(Index: Integer); -var - FKeyboard: TLangSwitchKeyboard; -begin - if (Index >= 0) and (Index < kmcom.Languages.Count) then - begin - FKeyboard := FLangSwitchManager.FindKeyboard(kmcom.Languages[Index].HKL, kmcom.Languages[Index].ProfileGUID); - if not Assigned(FKeyboard) then Exit; - - // Handle toggle hotkey - if (FKeyboard = FLangSwitchManager.ActiveKeyboard) and (kmcom.Options['koKeyboardHotkeysAreToggle'].Value) then - FKeyboard := FLangSwitchManager.Languages[0].Keyboards[0]; - - ActivateKeyboard(FKeyboard); - end; -end; - procedure TfrmKeyman7Main.DoInterfaceHotkey(Target: Integer); begin if not Assigned(FRunningProduct) then @@ -1356,7 +1327,6 @@ function TfrmKeyman7Main.StartKeymanEngine: Boolean; // I1951 StartKeymanX64; - RegisterHotkeys; end; procedure TfrmKeyman7Main.RequestCurrentActiveKeyboard(Command: WORD); // I3961 @@ -2023,18 +1993,6 @@ procedure TfrmKeyman7Main.StartKeymanX64; end; end; -procedure TfrmKeyman7Main.HotkeyWndProc(var Message: TMessage); -begin - if Message.Msg = WM_HOTKEY then - begin - KL.Log('Hotkey %d', [Message.WParam]); - if Message.WParam > kh__High - then DoLanguageHotkey(Message.WParam - kh__High - 1) - else DoInterfaceHotkey(Message.WParam); - end; - Message.Result := DefWindowProc(FHotkeyWindow, Message.Msg, Message.WParam, Message.LParam); -end; - function KeymanHotkeyModifiersToWindowsHotkeyModifiers(v: KeymanHotkeyModifiers): Integer; begin Result := 0; @@ -2043,117 +2001,6 @@ function KeymanHotkeyModifiersToWindowsHotkeyModifiers(v: KeymanHotkeyModifiers) if (v and HK_ALT) = HK_ALT then Result := Result or MOD_ALT; end; -procedure TfrmKeyman7Main.RegisterHotkeys; -var - hk: IKeymanHotkey; - i: Integer; - language: IKeymanLanguage; - id: Integer; - RegistryErrorControlled: TRegistryErrorControlled;// I2890 - UseRegisterHotKey: Boolean; // Use Win32 API RegisterHotkey -begin - RegistryErrorControlled := TRegistryErrorControlled.Create; - try - if RegistryErrorControlled.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and - RegistryErrorControlled.ValueExists(SRegValue_UseRightModifierHotKey) then - begin - UseRegisterHotKey := RegistryErrorControlled.ReadBool(SRegValue_UseRightModifierHotKey); - end - else - begin - UseRegisterHotKey := False; - end; - finally - RegistryErrorControlled.Free; - end; - - if not UseRegisterHotKey then Exit; - - TDebugLogClient.Instance.WriteMessage('Enter RegisterHotkeys', []); - - if FHotkeyWindow = 0 then - FHotkeyWindow := AllocateHWnd(HotkeyWndProc); - - if not Assigned(FHotkeys) then - FHotkeys := TIntegerList.Create; - - UnregisterHotkeys; - - for i := 0 to kmcom.Hotkeys.Count - 1 do - begin - hk := kmcom.Hotkeys[i]; - if not hk.IsEmpty and (hk.VirtualKey <> 0) then - begin - // Note, if hk.VirtualKey is 0, this indicates a modifier-only hotkey such - // as Alt+Left Shift. These are handled in keyman32 k32_lowlevelkeyboardhook - // because RegisterHotkey cannot handle modifier-only hotkeys. - if RegisterHotkey(FHotkeyWindow, hk.Target, KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey) then - begin - TDebugLogClient.Instance.WriteMessage('Added hotkey %d -> %x %x', [hk.Target, - KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey]); - FHotkeys.Add(hk.Target) - end - else - TDebugLogClient.Instance.WriteLastError('RegisterHotkeys', 'RegisterHotkey', 'Failed to register hotkey '+IntToStr(hk.Target)); - end; - end; - - for i := 0 to kmcom.Languages.Count - 1 do - begin - language := kmcom.Languages[i]; - hk := language.Hotkey; - if Assigned(hk) and not hk.IsEmpty and (hk.VirtualKey <> 0) then - begin - id := kh__High + 1 + i; - if RegisterHotkey(FHotkeyWindow, id, KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey) then - begin - TDebugLogClient.Instance.WriteMessage('Added hotkey for language %s [%d] -> %x %x', [language.LocaleName, id, - KeymanHotkeyModifiersToWindowsHotkeyModifiers(hk.Modifiers), hk.VirtualKey]); - FHotkeys.Add(id); - end - else - TDebugLogClient.Instance.WriteLastError('RegisterHotkeys', 'RegisterHotkey', 'Failed to register hotkey '+IntToStr(id)); - end; - end; -end; - -procedure TfrmKeyman7Main.UnregisterHotkeys; -var - i, hk: Integer; - RegistryErrorControlled: TRegistryErrorControlled;// I2890 - UseRegisterHotKey: Boolean; // Use Win32 API RegisterHotkey -begin - RegistryErrorControlled := TRegistryErrorControlled.Create; - try - if RegistryErrorControlled.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and - RegistryErrorControlled.ValueExists(SRegValue_UseRightModifierHotKey) then - begin - UseRegisterHotKey := RegistryErrorControlled.ReadBool(SRegValue_UseRightModifierHotKey); - end - else - begin - UseRegisterHotKey := False; - end; - finally - RegistryErrorControlled.Free; - end; - - if not UseRegisterHotKey then Exit; - - TDebugLogClient.Instance.WriteMessage('Enter UnregisterHotkeys', []); - - if not Assigned(FHotkeys) then - Exit; - - for i := 0 to FHotkeys.Count - 1 do - begin - hk := FHotkeys[i]; - if not UnregisterHotKey(FHotkeyWindow, hk) then - TDebugLogClient.Instance.WriteLastError('UnregisterHotkeys', 'UnregisterHotkey', 'Failed to unregister hotkey '+IntToStr(hk)); - end; - FHotkeys.Clear; -end; - { TLangSwitchRefreshWatcher } constructor TLangSwitchRefreshWatcher.Create(AOwnerHandle: THandle); diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 2bdfae62e39..8e3fe92a3ce 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -129,27 +129,6 @@ BOOL IsTouchPanelVisible() { return touchPanelVisible; } -/* - Cache UseRightModifierHotKey debug flag for this session - when using right modifier for Hotkeys use the Win32 API - ResisterHotkey functionality -*/ -BOOL UseRegisterHotkey() { - static BOOL flag_UseRightModifierHotKey = FALSE; - static BOOL loaded = FALSE; - - if (!loaded) { - RegistryReadOnly reg(HKEY_CURRENT_USER); - if (reg.OpenKeyReadOnly(REGSZ_KeymanCU)) { - if (reg.ValueExists(REGSZ_UseRightModifierHotKey)) { - flag_UseRightModifierHotKey = !!reg.ReadInteger(REGSZ_UseRightModifierHotKey); - } - } - loaded = TRUE; // Set loaded to TRUE whether or not the key exists - } - return flag_UseRightModifierHotKey; -} - LRESULT _kmnLowLevelKeyboardProc( _In_ int nCode, _In_ WPARAM wParam, @@ -169,36 +148,18 @@ LRESULT _kmnLowLevelKeyboardProc( SendDebugMessageFormat("wparam: %x lparam: %x [vk:%s scan:%x flags:%x extra:%x]", wParam, lParam, Debug_VirtualKey((WORD) hs->vkCode), hs->scanCode, hs->flags, hs->dwExtraInfo); // I4674 - if (GetKeyState(VK_LCONTROL) < 0) { - FHotkeyShiftState |= HK_CTRL; - // TODO remove - SendDebugMessageFormat("ProcessHotkey VK_LCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRegisterHotkey()); - } - - if (GetKeyState(VK_RCONTROL) < 0) { - FHotkeyShiftState |= UseRegisterHotkey() ? HK_CTRL : HK_RCTRL_INVALID; - // TODO remove - SendDebugMessageFormat("ProcessHotkey VK_RCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRegisterHotkey()); - } - - if (GetKeyState(VK_LMENU) < 0) { - FHotkeyShiftState |= HK_ALT; - } - - if (GetKeyState(VK_RMENU) < 0) { - FHotkeyShiftState |= UseRegisterHotkey() ? HK_ALT : HK_RALT_INVALID; - } - - if (GetKeyState(VK_LSHIFT) < 0) { - FHotkeyShiftState |= HK_SHIFT; - } - if (GetKeyState(VK_RSHIFT) < 0) { - FHotkeyShiftState |= UseRegisterHotkey() ? HK_SHIFT : HK_RSHIFT_INVALID; - } + // #5190: Don't cache modifier state because sometimes we won't receive + // modifier change events (e.g. on lock screen) + FHotkeyShiftState = 0; + if (GetKeyState(VK_LCONTROL) < 0) FHotkeyShiftState |= HK_CTRL; + if (GetKeyState(VK_RCONTROL) < 0) FHotkeyShiftState |= HK_RCTRL_INVALID; + if (GetKeyState(VK_LMENU) < 0) FHotkeyShiftState |= HK_ALT; + if (GetKeyState(VK_RMENU) < 0) FHotkeyShiftState |= HK_RALT_INVALID; + if (GetKeyState(VK_LSHIFT) < 0) FHotkeyShiftState |= HK_SHIFT; + if (GetKeyState(VK_RSHIFT) < 0) FHotkeyShiftState |= HK_RSHIFT_INVALID; + //TODO: #8064. Can remove debug message once issue #8064 is resolved + SendDebugMessageFormat("!UseCachedHotkeyModifierState [FHotkeyShiftState:%x]", FHotkeyShiftState); - //if (GetKeyState(VK_SHIFT) < 0) { - // FHotkeyShiftState |= (!UseRegisterHotkey() && extended) ? HK_RSHIFT_INVALID : HK_SHIFT; - //} // #7337 Post the modifier state ensuring the serialized queue is in sync // Note that the modifier key may be posted again with WM_KEYMAN_KEY_EVENT, @@ -279,9 +240,7 @@ LRESULT _kmnLowLevelKeyboardProc( } BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState) { - if (UseRegisterHotkey()){ - return FALSE; - } + Hotkeys *hotkeys = Hotkeys::Instance(); // I4641 if (!hotkeys) { SendDebugMessageFormat("Failed to get Instance"); diff --git a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas index 2df38b5c825..ce1b8af33a8 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,13 +121,12 @@ TKeymanOptionInfo = record GroupName: string; end; -const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 +const KeymanOptionInfo: array[0..15] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 // Global options (opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koSwitchLanguageForAllApplications; RegistryName: SRegValue_SwitchLanguageForAllApplications; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I2277 // I4393 (opt: koAltGrCtrlAlt; RegistryName: SRegValue_AltGrCtrlAlt; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), - (opt: koRightModifierHK; RegistryName: SRegValue_UseRightModifierHotKey; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koShowHints; RegistryName: SRegValue_EnableHints; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), (opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'), diff --git a/windows/src/global/delphi/general/KeymanOptionNames.pas b/windows/src/global/delphi/general/KeymanOptionNames.pas index 73356d19cb3..27aef82ab55 100644 --- a/windows/src/global/delphi/general/KeymanOptionNames.pas +++ b/windows/src/global/delphi/general/KeymanOptionNames.pas @@ -5,10 +5,7 @@ interface type TUtilKeymanOption = ( // General options - koKeyboardHotkeysAreToggle, - koAltGrCtrlAlt, - koRightModifierHK, - koReleaseShiftKeysAfterKeyPress, + koKeyboardHotkeysAreToggle, koAltGrCtrlAlt, koReleaseShiftKeysAfterKeyPress, koShowHints, // I1256 // Startup options koTestKeymanFunctioning, From f1d9f5304863fee2afa012ba57fc977632b9bc20 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:32:39 +1000 Subject: [PATCH 115/262] feat(windows): add right modifier included in HotKey --- windows/src/desktop/kmshell/xml/strings.xml | 16 +++--- .../keyman32/k32_lowlevelkeyboardhook.cpp | 52 +++++++++++++++++++ .../engine/kmcomapi/util/utilkeymanoption.pas | 3 +- .../delphi/general/KeymanOptionNames.pas | 5 +- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 774a4d5e425..e15ba789453 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -377,6 +377,11 @@ Simulate AltGr with Ctrl+Alt + + + + Right Modifier keys work with Hotkeys + @@ -792,18 +797,13 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman Start Keyman - - - - Start %1$s - @@ -860,7 +860,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman @@ -1026,7 +1026,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman + Keyman diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 8e3fe92a3ce..4d842401dd8 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -129,6 +129,27 @@ BOOL IsTouchPanelVisible() { return touchPanelVisible; } +/* + Cache UseRightModifierHotKey debug flag for this session +*/ +BOOL UseRightModifierHotKey() { + static BOOL flag_UseRightModifierHotKey = FALSE; + static BOOL loaded = FALSE; + + if (!loaded) { + RegistryReadOnly reg(HKEY_CURRENT_USER); + if (reg.OpenKeyReadOnly(REGSZ_KeymanCU)) { + if (reg.ValueExists(REGSZ_UseRightModifierHotKey)) { + flag_UseRightModifierHotKey = !!reg.ReadInteger(REGSZ_UseRightModifierHotKey); + } + } + loaded = TRUE; // Set loaded to TRUE whether or not the key exists + } + return flag_UseRightModifierHotKey; +} + + + LRESULT _kmnLowLevelKeyboardProc( _In_ int nCode, _In_ WPARAM wParam, @@ -157,6 +178,37 @@ LRESULT _kmnLowLevelKeyboardProc( if (GetKeyState(VK_RMENU) < 0) FHotkeyShiftState |= HK_RALT_INVALID; if (GetKeyState(VK_LSHIFT) < 0) FHotkeyShiftState |= HK_SHIFT; if (GetKeyState(VK_RSHIFT) < 0) FHotkeyShiftState |= HK_RSHIFT_INVALID; + + if (GetKeyState(VK_LCONTROL) < 0) { + FHotkeyShiftState |= HK_CTRL; + // TODO remove + SendDebugMessageFormat("ProcessHotkey VK_LCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); + } + + if (GetKeyState(VK_RCONTROL) < 0) { + FHotkeyShiftState |= UseRightModifierHotKey() ? HK_CTRL : HK_RCTRL_INVALID; + // TODO remove + SendDebugMessageFormat("ProcessHotkey VK_RCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); + } + + if (GetKeyState(VK_LMENU) < 0) { + FHotkeyShiftState |= HK_ALT; + } + + if (GetKeyState(VK_RMENU) < 0) { + FHotkeyShiftState |= UseRightModifierHotKey() ? HK_ALT : HK_RALT_INVALID; + } + + if (GetKeyState(VK_LSHIFT) < 0) { + FHotkeyShiftState |= HK_SHIFT; + } + if (GetKeyState(VK_RSHIFT) < 0) { + FHotkeyShiftState |= UseRightModifierHotKey() ? HK_SHIFT : HK_RSHIFT_INVALID; + } + + + + //TODO: #8064. Can remove debug message once issue #8064 is resolved SendDebugMessageFormat("!UseCachedHotkeyModifierState [FHotkeyShiftState:%x]", FHotkeyShiftState); diff --git a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas index ce1b8af33a8..2df38b5c825 100644 --- a/windows/src/engine/kmcomapi/util/utilkeymanoption.pas +++ b/windows/src/engine/kmcomapi/util/utilkeymanoption.pas @@ -121,12 +121,13 @@ TKeymanOptionInfo = record GroupName: string; end; -const KeymanOptionInfo: array[0..15] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 +const KeymanOptionInfo: array[0..16] of TKeymanOptionInfo = ( // I3331 // I3620 // I4552 // Global options (opt: koKeyboardHotkeysAreToggle; RegistryName: SRegValue_KeyboardHotkeysAreToggle; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koSwitchLanguageForAllApplications; RegistryName: SRegValue_SwitchLanguageForAllApplications; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), // I2277 // I4393 (opt: koAltGrCtrlAlt; RegistryName: SRegValue_AltGrCtrlAlt; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), + (opt: koRightModifierHK; RegistryName: SRegValue_UseRightModifierHotKey; OptionType: kotBool; BoolValue: False; GroupName: 'kogGeneral'), (opt: koShowHints; RegistryName: SRegValue_EnableHints; OptionType: kotBool; BoolValue: True; GroupName: 'kogGeneral'), (opt: koBaseLayout; RegistryName: SRegValue_UnderlyingLayout; OptionType: kotLong; IntValue: 0; GroupName: 'kogGeneral'), diff --git a/windows/src/global/delphi/general/KeymanOptionNames.pas b/windows/src/global/delphi/general/KeymanOptionNames.pas index 27aef82ab55..73356d19cb3 100644 --- a/windows/src/global/delphi/general/KeymanOptionNames.pas +++ b/windows/src/global/delphi/general/KeymanOptionNames.pas @@ -5,7 +5,10 @@ interface type TUtilKeymanOption = ( // General options - koKeyboardHotkeysAreToggle, koAltGrCtrlAlt, koReleaseShiftKeysAfterKeyPress, + koKeyboardHotkeysAreToggle, + koAltGrCtrlAlt, + koRightModifierHK, + koReleaseShiftKeysAfterKeyPress, koShowHints, // I1256 // Startup options koTestKeymanFunctioning, From 259d044f395ff8c14fa0a7ebfe3fda2720691092 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:02:04 +1000 Subject: [PATCH 116/262] feat(windows): new sil logo --- .../windows/src/xml/sil-logo-blue.png | Bin 906 -> 2350 bytes .../src/desktop/kmshell/xml/sil-logo-blue.png | Bin 906 -> 2350 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/oem/firstvoices/windows/src/xml/sil-logo-blue.png b/oem/firstvoices/windows/src/xml/sil-logo-blue.png index e0d7ab6b37385b8f7ec090867ec71ff343d11a43..b65c2a4e086c081529363bfd7722cb087ab8aee6 100644 GIT binary patch delta 2337 zcmV++3EuXK2d)w!iBL{Q4GJ0x0000DNk~Le0001M0000$2nGNE03Fnc`H>+fe=Z^b z04^c{s^Z;}000QXNklF|sm>3h^2GqnSA0#oL7!@#tP(VSDQUuyUU$k4=?e4Z+?|=3# zJ2QJ{cV;`A-R;gVnateT?Ctjcf9ITg?zv~k!otGB!otGBB8Rc@a||UrTFTjJS-6#1 zRJikKZka3QxA*!-&mCA({SR+a{f2Mmj)tg)H?sn}&35SY$7{QJQ$akjZege2!>!EX zx^fqly7G7{jBg}F$9n?XyVlok;+a(2jp*ZbESr19iI(p2qp>)j4;8i6bmACFtKtKU1DKwYG5u;ojr)4^`yfxk~OYk zX2lR(POpD-+~@z|kx;BMHV@S^p+tk;8yKadJpuaT@(>;B@=H;g94Av3t}_{su~5_H z&t0nmaYqt~(>WSKf1PI$*e;kzMGeibDI>eqM4bjSf5}~Yf7(~D4}arL3GBu;b^A$R zKoXeW7O;p>4upjz8UafK^}f}qzM!70#ddFFo#CD0l?x}+=Eg--H>HT*iPAR^pa-|^ zqvp1LeuwC*msirvs$$+Ey7{>uDInWVJNWh*%6CO0*?Ttcr3;+{ycHkQc8bqo45efxLJTB_EGQ?Y9N(x<6QS82Us3|RUvhPI?CIJ6r zZ0HsX?yrwUfAMe$e$+5~w2e~WQ2s3RkczDk4D`0gmPjs83s^+IFUVWzMVDBx4n3|6 z(W4q3EE+=H4+0nSrD9gKd>R$zE8Xq!mMiqyXHB%}fBi%B?yi4m&yhC%N&3+x)+OJF z6q+YOl-TUuC`_RjR+A@9>rlWU=9^UpVeZIGue%ODW-u&io+Pd=?|1|w1 z6br+I9a%jT2<952VhNU4Da%?ExRq9QsmDh{!OUHFClm`AZao8z=}Mg*!WbJJXoq0_ zNLAs|f4R$7`Bj*CHKjCfy3*P*iG*Uc59)i;FwdxmS+qz_gus)s2sAa8^}~Vl?175* z?Xc(3&X;bb`&fjSiNqBP>nfCb>A|Cl;6O5BiK5&Tc|kgT(MuoybcVOWAk=>A>1FiU ziz}$A(!=j&264seG_W1v5txK;BpLq|+fRzLf0B6p%j5Jt3ntX8STLD(ZMj{_Vq^+& z#WIrR76ljCP$o9Q%=QHqrXyS*7*YmOSchT3TwbKi%A^ZTv0D0rFZ?kqzFK_D7930JD{12c^XQ?KwY-Ng zColBTShSikrHC4)mMD(dI8=WhziXCk>qU*`e{AH8cAVSzZ}4YDXOxyW~%BLzADt`INSr2?$KzF;Cf@z#FnIup>H7Sf-2_n64eo6e}faEV10Kz3n);F5dp=Wr&)gh?-gU7i-0mXF0jH4 ze*61>y4)ahm;8!o&_65n?nI_4GRjr6452d(SQ>r(h|g zlT~*tt`ctRn<5rESw1O0X_tn1r`fBt8Uz%G0Ode@?5%+tC%Uf%87PTEe+?resTzz- zBIR%E;LUmj18sxGUfY579cJRTvNPOeWSP3O(5z@BG2eCngMJ-lgQVPRomVPRomAwl#%=YLn~YS(md00000NkvXX Hu0mjfMI(4D delta 882 zcmV-&1C9Ky5{d^QiBL{Q4GJ0x0000DNk~Le0000j0000o2nGNE0LYqB)R7@4e+v)* z01FTSts}j40009YNkl;r22042L3a_7BQnsjYBVUXB3=Omz6>RLw_+sJ&O=pLM*BR+VP zE-grbBG~zDKP)!%4bQ@0nupECq>bxHT3Bnx^%_bdf?#rC6Lys}BenPql8c&9*ELQ* z<%3)4&0!%3S2~wP6On#jh*M9=tZXjHWBisX^>tOo~zAs4g0ze+*Hl z?{+QEAij>K-)5Kaf23`c(r@eq=v;;ND6`pYtN!?+i;bpPWo^rK?yIz~_*EW`3{EWI zTxE~T!dy%ImKJX5{rW*>l4FqAT<)Y z_%P(s%)aiWl_7FiS?66`vMxW;t$XPX!xZ{P=Ijq0;$t506E)%o;k%E8(f|Me07*qo IM6N<$f*S0ZjsO4v diff --git a/windows/src/desktop/kmshell/xml/sil-logo-blue.png b/windows/src/desktop/kmshell/xml/sil-logo-blue.png index e0d7ab6b37385b8f7ec090867ec71ff343d11a43..b65c2a4e086c081529363bfd7722cb087ab8aee6 100644 GIT binary patch delta 2337 zcmV++3EuXK2d)w!iBL{Q4GJ0x0000DNk~Le0001M0000$2nGNE03Fnc`H>+fe=Z^b z04^c{s^Z;}000QXNklF|sm>3h^2GqnSA0#oL7!@#tP(VSDQUuyUU$k4=?e4Z+?|=3# zJ2QJ{cV;`A-R;gVnateT?Ctjcf9ITg?zv~k!otGB!otGBB8Rc@a||UrTFTjJS-6#1 zRJikKZka3QxA*!-&mCA({SR+a{f2Mmj)tg)H?sn}&35SY$7{QJQ$akjZege2!>!EX zx^fqly7G7{jBg}F$9n?XyVlok;+a(2jp*ZbESr19iI(p2qp>)j4;8i6bmACFtKtKU1DKwYG5u;ojr)4^`yfxk~OYk zX2lR(POpD-+~@z|kx;BMHV@S^p+tk;8yKadJpuaT@(>;B@=H;g94Av3t}_{su~5_H z&t0nmaYqt~(>WSKf1PI$*e;kzMGeibDI>eqM4bjSf5}~Yf7(~D4}arL3GBu;b^A$R zKoXeW7O;p>4upjz8UafK^}f}qzM!70#ddFFo#CD0l?x}+=Eg--H>HT*iPAR^pa-|^ zqvp1LeuwC*msirvs$$+Ey7{>uDInWVJNWh*%6CO0*?Ttcr3;+{ycHkQc8bqo45efxLJTB_EGQ?Y9N(x<6QS82Us3|RUvhPI?CIJ6r zZ0HsX?yrwUfAMe$e$+5~w2e~WQ2s3RkczDk4D`0gmPjs83s^+IFUVWzMVDBx4n3|6 z(W4q3EE+=H4+0nSrD9gKd>R$zE8Xq!mMiqyXHB%}fBi%B?yi4m&yhC%N&3+x)+OJF z6q+YOl-TUuC`_RjR+A@9>rlWU=9^UpVeZIGue%ODW-u&io+Pd=?|1|w1 z6br+I9a%jT2<952VhNU4Da%?ExRq9QsmDh{!OUHFClm`AZao8z=}Mg*!WbJJXoq0_ zNLAs|f4R$7`Bj*CHKjCfy3*P*iG*Uc59)i;FwdxmS+qz_gus)s2sAa8^}~Vl?175* z?Xc(3&X;bb`&fjSiNqBP>nfCb>A|Cl;6O5BiK5&Tc|kgT(MuoybcVOWAk=>A>1FiU ziz}$A(!=j&264seG_W1v5txK;BpLq|+fRzLf0B6p%j5Jt3ntX8STLD(ZMj{_Vq^+& z#WIrR76ljCP$o9Q%=QHqrXyS*7*YmOSchT3TwbKi%A^ZTv0D0rFZ?kqzFK_D7930JD{12c^XQ?KwY-Ng zColBTShSikrHC4)mMD(dI8=WhziXCk>qU*`e{AH8cAVSzZ}4YDXOxyW~%BLzADt`INSr2?$KzF;Cf@z#FnIup>H7Sf-2_n64eo6e}faEV10Kz3n);F5dp=Wr&)gh?-gU7i-0mXF0jH4 ze*61>y4)ahm;8!o&_65n?nI_4GRjr6452d(SQ>r(h|g zlT~*tt`ctRn<5rESw1O0X_tn1r`fBt8Uz%G0Ode@?5%+tC%Uf%87PTEe+?resTzz- zBIR%E;LUmj18sxGUfY579cJRTvNPOeWSP3O(5z@BG2eCngMJ-lgQVPRomVPRomAwl#%=YLn~YS(md00000NkvXX Hu0mjfMI(4D delta 882 zcmV-&1C9Ky5{d^QiBL{Q4GJ0x0000DNk~Le0000j0000o2nGNE0LYqB)R7@4e+v)* z01FTSts}j40009YNkl;r22042L3a_7BQnsjYBVUXB3=Omz6>RLw_+sJ&O=pLM*BR+VP zE-grbBG~zDKP)!%4bQ@0nupECq>bxHT3Bnx^%_bdf?#rC6Lys}BenPql8c&9*ELQ* z<%3)4&0!%3S2~wP6On#jh*M9=tZXjHWBisX^>tOo~zAs4g0ze+*Hl z?{+QEAij>K-)5Kaf23`c(r@eq=v;;ND6`pYtN!?+i;bpPWo^rK?yIz~_*EW`3{EWI zTxE~T!dy%ImKJX5{rW*>l4FqAT<)Y z_%P(s%)aiWl_7FiS?66`vMxW;t$XPX!xZ{P=Ijq0;$t506E)%o;k%E8(f|Me07*qo IM6N<$f*S0ZjsO4v From f0dbcd639330416cbe20789f45fed0b998541825 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 22 Aug 2024 09:28:21 +0200 Subject: [PATCH 117/262] Update windows.md --- docs/build/windows.md | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index 47b3e8696da..663b68c2f89 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -203,8 +203,6 @@ installed on your computer: SETX KEYMAN_USE_EMSDK 1 ``` -After installing emscripten, you'll need to install node.js and openjdk. - #### node.js Our recommended way to install node.js is to use @@ -327,32 +325,41 @@ git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4 * Ant * Gradle * Maven -* Optional: OpenJDK 11 (https://learn.microsoft.com/en-us/java/openjdk/download) +* JDK 11 (Temurin11) -```ps1 -# Elevated PowerShell -choco install android-sdk androidstudio ant gradle maven -``` +#### JDK 11 -Start a new shell to get the new paths and then update Android SDKs: +Use Powershell + Chocolatey to install JDK 11: ```ps1 -# optionally install sdk images -sdkmanager "system-images;android-33;google_apis;armeabi-v7a" -sdkmanager --update -sdkmanager --licenses +# Elevated PowerShell + +# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) +$ProgressPreference = 'SilentlyContinue' +choco install temurin11 ``` -#### openjdk +**Multiple versions of Java:** If you need to build Keyman for Android 16.0 or +older versions, you can set `JAVA_HOME_11` to the JDK 11 path and +`JAVA_HOME` to the JDK 8 path. This will build both versions correctly +from command line. But note that you do need to update your `JAVA_HOME` env +var to the associated version before opening Android Studio and loading any +Android projects. `JAVA_HOME_11` is mostly used by CI. -Use Powershell + Chocolatey to install OpenJDK: +#### Android Studio and friends ```ps1 # Elevated PowerShell +choco install androidstudio ant gradle maven android-sdk +``` -# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) -$ProgressPreference = 'SilentlyContinue' -choco install openjdk +Start a new shell to get the new paths and then update Android SDKs: + +```ps1 +# optionally install sdk images +sdkmanager --update +# sdkmanager "system-images;android-33;google_apis;armeabi-v7a" +sdkmanager --licenses ``` * Run Android Studio once after installation to install additional components From 012bbb4c69860caf9a139f25d08277fcbfb9dc83 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 22 Aug 2024 14:24:16 +0700 Subject: [PATCH 118/262] fix(android): Add RTL assets for adjusting keyboard height menu --- .../main/res/drawable-ldrtl-hdpi/blank_osk.png | Bin 0 -> 8973 bytes .../res/drawable-ldrtl-land-hdpi/blank_osk.png | Bin 0 -> 9879 bytes .../drawable-ldrtl-sw600dp-land/blank_osk.png | Bin 0 -> 9268 bytes .../res/drawable-ldrtl-sw600dp/blank_osk.png | Bin 0 -> 8726 bytes .../drawable-ldrtl-sw720dp-land/blank_osk.png | Bin 0 -> 9281 bytes .../res/drawable-ldrtl-sw720dp/blank_osk.png | Bin 0 -> 8808 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/blank_osk.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-land-hdpi/blank_osk.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp-land/blank_osk.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp/blank_osk.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp-land/blank_osk.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp/blank_osk.png diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/blank_osk.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/blank_osk.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d555cacaebe0de05655e9c94b7b165390d5b36 GIT binary patch literal 8973 zcmd^_XH-*Bx2}Uq5d7s&y0tzBRq=b?XO6UO+ zIsyvPTL_&XB{T&>gb19C-?`^}cbt2_amP6K-}#Y|k)7-{)?RDQ_kHGEi80XEVr2$1 zgFqlw9c_(=AkZ;h5Qy$169aH%n)Tot@J8qNP)iMj>ET@lKAdn?g{Xo+WeF^Q?CF8e zC!c6r_<=xdEk`dpPGQ|=AkgJ(9Sv3Er#5SoEFVl9Cp&%}rYcAtKgCr|r>^zdtVKgy zw{lahrM&WXTM9QMDKB+la)=|vQOwUZP4ixwSgS80J3sX!moC;6!SGdc@bs-@J#AO( zg_eow{SY~=+RuJYY@eN-Hk76lR&Fe;`Z-M;)@RmJHxy}Ow5x7zZbViPs43_9d?PTs zsnc>G(6hi3Y#`7K^p%4gkw`2;qrX{NStS{@pi4{pbai!$va(30IFr-P#=PX@I$;E&*BeGj8wiHs`a+X;QWn3T3BLw1ki-s8>{1r%0+R2(J%pd3jPMc-w_K zb#wjxoO#lhvi-I)q#U+UZse+uM}$w&^^~1!7mmLgNKPbNaQ+lJ3cQjPsV#n zF@MSU*BKx{5jL?x@a-Doi>$2jSjnZ zwj)hri?@1*yCaQ&Bp`7{yza!ReYC95xT5nZ-o3o|@H-yzX^X-mSKXefN1xJBm`+2E zZf6L`I6%@iYO0PN#lxsSYu5=L%TQH}P=Do=zVS9veaPgjG+(zLhkK&O76dZe-yf<~ zh5>#%wg69S$;sJ`e+2a@(veTwD2hA|0txUl0kPPmaEuWI`WfFu2Ljbyc>-+e#{baE zU0}JhF>3DK?iE0EQke>e{Lil(MdjS{ND{mM5}n79z6@84i!mA20>4|i zu(%hLVJ;6j_fbq)+{&m`kftUv`P12$v}43e9+Ty5Ha1na%@I)+X4x*z*;hje*VwBY zOiVCzFCo0{v5tTe_yRd`a@^RX8eJ55C9kSV&?Ue4oAa!(vGIA1&-DbTCIb1~&XJ4L zaXo{W1a{SXgVR+f+)nyOwwFQ5ApX~|=--}3>C8ZcgJ?MdyAps-$^O+TzN6TGsxb z`E_eiW#y1*iH#=Z2=K@i5z94kuuoleSzp%nD5bbErJAMoI-QC=LdvorheU=CEu#ei9MXz7i(cgdPc0tnVZ5+SK zl;FPbYX=DNt8v#vt>0w)4w7lFZCj|NlCv5X;SB<2@s^jH6kyQoC$ zj*w5oj8Yo%fm_isc`_d{n3@reYEO}P=@?`M#J@442BHbEIrhpl1BY&zQmVSvU5!KE zmTri*Vb1}C%FW8nVR=CTBi-f+k9B|y^sm^r$#Ak7Lb;dc1)pdebKle2;%fs!KIt*s z+l@IP!!)TB(~^T4<`4{GIoj&fe=?;1%SQiCx}1{I^j`Z@Rn-b3plR3{#I@PHUB3jZ zj2;Jv5DJ;1&4Hulg1|ze-lVW2>k4 zhO#`^(bM>IKp;-=IRiKsB35?#q52I`(TbFhVrBGO{H2{!^;Ead*>RWvS^!)oEIN)) zwmD#xFpG>by2Q=>X`cIN+(wC!n?(dc#10)S+>pX7zVP5RX zYg1(6Py0;8i9J)3T87i|&3kSv;4xyAy9a5HVrpaf-e1t|N?}Q11sS2f_D=ntxoTgj zn1>?(8P5Wktg1cAQp}2BIx1GU1P9334$s(q4XQmn)|vJuwvK_#@QSCap_vJj*_?Q6 zEc1)@)O!2%0zlyM3#E5&deZOF8k$Zc=@Y{~DalCg1#PVj6fV6^B{1UZY47jwn9Kw0Jy^j zQ{1d-o*LWWB{#%{|_kEv<3Hz6*wEB?I8BjfQ&&(bh! zDz17ue0-!>@31)7^1MU)+p=Gz4!j00y=5x1f!|_}zx3MJ-C?;Zj8o6&W%A%w=^F<~ z_5(4!6!9ufQPY~e9gepDggh;2&0mDV9i)-w8*Yh!NQd5?l*C)H!uzIXW?D$Q{yMh| zNx{J4ul+e%cyHymu|x5y(5$pWCD$~Yr2JJ4AW8F~=&-y(9VsQZHHmoXzdnbGcZ;1X zlO0AfoI3xmYVn4{Odr1i^}&!!hNP!K8pf+g*z-v?XFjG6wpHvJEH zhZ-3@g-J~t9-)B$y^*6Je8>K;eD(iXZlM?Dw#A+TakYMC)cebEP?l&_WhJnq#h%j* zN+_+cu&@-qH+ZQh`T5kTkY0V|-Ny=)A*0FOmq1qRK>hqGC~M=R;`r6ucpysF3JNjg zAyS$ZU{-Llgji=X}C)5CVgc3Sxl`XE=j%kT_OazR32F$2Jkd3tEy_4P5JlCd_|ZfY{`H|DT!E9f<0yDU2ciSg$_}!{A1e6 zYHDUL*-9d>KnXSLxD}7#0+XXi4P?_{;sb%^dmR##7TTRxbvq`kyz8ZRJ-i##UsX4~ zzwU9v3j2L;d)c8rgo-t@35x2Y1Kqt?|9kFD04Yw^(!n8zC+CvM|-b z0A(1>&hNFg&|Pue2wB7P@(B0V8xIeFCAF&c&4a@FHP>G>2H)Eh+=x_{M3sRTGeR(U zQtHKX*Ysh5oZTA7HT{`|CU0asIAdpgIwBXXGLZ9=1FyZ0I5ZmSTY)R*y z%E1RJlMp@U-OV2XzuLIR0o~jRKecn)#aI$OD6&tZ9?l?6Unb8QbPm)ajJ;pkj8p3O zpe;Zfg0`@flYqrwsBAwq-_Gl(8d0+$M?az~s@4_!IhzXjRnK6(wA6W6 z#F9vVmqg=)JK>%9NnYGlQ9UJyUrOm*tcftZ&aeNUel_yy*1bNM&ZQu*lg;Q=+M5iFP_&cgf9(vsZLhGB;Q1prMSWeW301uOUlEh+Or z&&cHJ9S8DuDfRz%@r!xU)bF*nw)QQv)qAn-o^v=Ih+tJ2U!T3Z252+`+y0}CDU*?b z0RW;r5#lym$e2r9bWFB`h7eG6`}{)=XINJ_IXT{gL%CV9kwAF)2MrooxYVXJ>wBv=#uF1s)_qtb648Qn2A zcSlFZoq=*etlhzJ)y{-)8sO|@f7?fWq-ykQ-Vn`81!69GDtM9>)_-N>?b?Ie0NuT( z`1_kG!SeZhzcPt3fdiJJwNI?*wPB$6b{UMV_YY1^SF=aDSKXLhUX@B~S4)&wE}Obw zlZAtsaLT@i z+p4o=vT}OJ>z*iwRr!lIUt~BNJqwJE3SPSij(^FG7$Y-uu2)Hc)`Q%xFc_-AT3msJ zCg$o_eBBH;qQYGLgZ?xt(QH|ny>z=)}bF$o43)I}o&&Js`CP z^*|?3#<7!w=Z3o#9g~EG)D*se=X~j07!ZLI$Mjg$$W^1`Hv!1MLb&ZAf}u z&^vE**vRWMS2hQDntjAo)7(RA7C3{=`? zE!yj(hKBZoK_F;M)0x}2l18=mh=VPDDy1v)U>mgtD0CHo2;<+bWqN4w`F~*H-KeK# zu<(OG8g!9YG`9V_;IiJ>oCIZJM(CyvTqU9*Y{hE2JXO+}nChJ2dW2csY0_O6!?q1W ze$T%0TqTt401Sr90F1ZgZPS^v&&z)teC{@STbGQda3Lpb`mLLcFqmBKPBikq6MxSg&FcuW70m zV&r}DENp+pwt8UT;Sv5HIf{sg$ZA=~#R}!Hz0>j_5Q1~B32}=xl|jcTL`(!GpPQTe zWxavsMYW#Ii8wq67&bAhH4@oqJZum=dy;BtZ;v>#@gukx#s7Mr5c-AnYBFsd-&>zB zC<+3S#MYhva3kVyJZ}6A?Nxi6+%ufJ*um@fw$LJcZ!nXna=80| zHjvUVpE2z}3iYx877=2vPTO|Q2xMKy``d%-(s3ce5)ug$ zod&G~HE$cjXhg>p=)&_awTl_<5r?#CPry&attWz4iUS5AvbEGhvRR|AN&;BAQ|tw| z&C_Oa+|p$;YSpOCihli40hfL;=x&W`#9?Z0X#0NX;XV}~;V+&2ybS|WG=6YIRY5D0 zc&bf_mQFQeJdofx)%VVsVORwNBo_v`{H*Z7q4Q^-==}xRMjr>R_5^-!RC+*~LTOKi z(s;1|CE(8-cjJ@8dojWOQbOvH?rpK0rU1z4Cxye_m>TBJ8mbFuH%&n!7EH$cO^gbT)bKT@mSX@XtZkGx^2E{`?KSDu>x7f3@>Wy5l&uOIz9e94^3u z@`E*wAaZ56Foowny6G&vn>iT&t3M%p2s&3VU+|?+KoBSjv606aaW@fqwwkl|U#K&l zI&Y`<@rDn8)!QL^VbC()K2^qA$J5C_01Qlzw?&r)hYX-o$Yi&`x}jT!r2S|%KJd4` z{^CgW7XqA8Lk_>lg(a52feYNm#Kh6|cMG?5Z!}kB=&A$1-(jpizjf%ZHjLI?Mi1yW z#MBvoX-7*}`<%x|35w4g#v^_SgClM|{b?ub2{a%9k29reQ_O|wR%)m4Zo?w7kS?t* zgX_QZQfzz&ht$tf{K7zegTmIr+N*ng2evf7II7(2%X1SnDI0G>sXhN&_qVY}OIC^T zx-2LwSO*2jyI0JPa$@Oj5nW=rUGp*S+Z?7GiWi^G&;?qmLiNeBubGGQz(2;P53u;F zrd8?Td&`)>xxhwRdN*9Q7mCjQCE~l79*7H;hLTJ*P}zUiXzVgBREBJ}0lNDjI}EFj zMy-pa2QSs`EbrYc?%;0xtw}uqx}St*Mt)v&U;mne9EUu|jRC^~qn1JRxv<1Wpa`32 z8CEl^dHS=NaV=3#+y})UzMaEgYx8c1lh+;veu$U>;@PjBw<|>=Fc56gY2qCfd`l{9 zjaaoFMy9s}xE|hc)J}jXUD{KEjJgWV!k9rCdO%A;0g7RvgZ;WoMl9~VD<1Xdy33FC zXSlnq8v6U^tk-!p4p_*{R}B^e7Sqi6X#M4Et1MKw z8OdJ5LHS`96X>)Tc~)h;hO}CdxnBgyPz)-0grZ_i^Jk~J(VuZj*x=^FQb3HVCw??An=)Slq||m|B}oY4tHcQg6~H56 z9%694uqs`*wBWEILgn`%7u%p8oYcVVvvr`XYXjkXyNj8DDl6x+417i`ob$q#x)hv& z#!mG4rOx|&j1rB1e5rJQfWpyF{am&HN2EHiJlwFVD7Fmni!472TuHRo+iX1*$}%+2 zo<}LTaz$+*FIV>|_{QF5mjoXEJTLfOw%QA5#7;$KZ8R$KZh=|73>nd9B8RJv)<-X` zENfC%`<6MPZVJve?yq@!ov83i{1}|D{;)vLl@2tU=a3wHV_WrM{qNL^TD(3lc@|M5 zcuTahIr(=MT%D||pY9aX$@pCZ0)Y_EL|dNjxESJ*5*O2FXTW-P*p_VHM>xDOaWDISi(jL*g{1C$Zm zWzBkCW+B|*XYfbyE2dyB`gp)9XzR*?VC@1M{`>+T zikmm(^Ga24dvethup2|al_-yv7*Z;ny33E+YJA5lOW3=f*7xx0srl(@X6_i-t4oQi zrW{+_?W4i^h8u%1^cicqDW!vdR zcNgH|9xko@jxA_CPdZRPij=Mv)i4AMh?;HXl93m+^y5)?y9wqk@be`L_NN<5gLhk4 zBw(m_LtBWOm&n})=lfZ0rCdxmj5f5~{zQwz=g1tO*X$JXirUG3s)y?hEsoacCopC@ z|1$*&WWP+>XBs&Ox>?{O!VH#AP9(1z9mfNcm}GLCe4!z`w+A$oW?Ev^^o&D-K%iyU zY0R%s+$q!QXMIX{viF)30pww3kNF2C(k-v-x2^7yeOLC zJ$dM%+XO&4Hh@S;!QQ^!NTm znU(qL!Kc9Vrv;Tf`&^7Q-1>x-bCdJDBTNVePlNU;qm%p6uov9^d9Jv2_f_w^AW&A} zX*o50wBdCdcEJ#asAHh)*E!t(tutYHCSKP@ZLF5NcW>SO@_H^O!*p*DA!7A#GcU(M zXQau{+??Z2Bz9hM32yZ!Rx(1p&}MwP1i9hHH~ayAf{x*t?Cwg-w@5N&X@!qz#*v9gBxg5EKn z3_M*(4t!P|z9t>wa!_ZNIjpctY#_;1XZk9Be0+*`0O-H73D{=HOtQ)Uo=IPpAmK?m z(%zG}HXHLlW+7hvlBQ$$z@N=ZVX+2gwre|U0ju*Q&$-+OR?`h8BV1QEOTXnIt@DWA zzo9amTG_~{o(y-x1)uMK#oA?3=C*prGY{vTGl!EB{AePhN4s)K_Ze`Y)7&Vg6d-t& z`7F?gdjxd5_+)&e;*m&X))`rg5UZ!$=Hk*nfGR{_n&2E|B)*c@dej3hZ9IPos51ZY e2ZG-32mJ5G6Lv!(@KE4P4y2>0uYpmsd;V`io;S7t literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-land-hdpi/blank_osk.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-land-hdpi/blank_osk.png new file mode 100644 index 0000000000000000000000000000000000000000..3e41a359a03b58db80c1c62c895e5f327de53e58 GIT binary patch literal 9879 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 Z{{5rYrFPfpRMQSlC@ZPnK`T7*|357?>;?b; literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp-land/blank_osk.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp-land/blank_osk.png new file mode 100644 index 0000000000000000000000000000000000000000..282606a9d509fadbeeb4706725b947b72ee0afbd GIT binary patch literal 9268 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 QLH~t@iq_rYJ5PiE14n|qQUCw| literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp/blank_osk.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp/blank_osk.png new file mode 100644 index 0000000000000000000000000000000000000000..8246a641687a092571b1046ad5296621c2efb50c GIT binary patch literal 8726 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 literal 0 HcmV?d00001 From b721ff6d5deaea46c0f043f03aa25293c87861b2 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 22 Aug 2024 11:19:37 +0100 Subject: [PATCH 119/262] fix(developer): add call test cases from #11990 to GetXStringImpl_type_c test --- .../kmcmplib/tests/gtest-compiler-test.cpp | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index 1c205976452..9d335de83a1 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1225,6 +1225,59 @@ TEST_F(CompilerTest, GetXStringImpl_type_o_test) { EXPECT_EQ(0, u16cmp(tstr_outs_space_after_valid, tstr)); } +// tests strings starting with 'c' +TEST_F(CompilerTest, GetXStringImpl_type_c_test) { + KMX_WCHAR tstr[128]; + fileKeyboard.version = VERSION_60; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = nullptr; + PFILE_STORE file_store = new FILE_STORE[100]; + fileKeyboard.cxStoreArray = 3u; + fileKeyboard.dpStoreArray = file_store; + file_store[1].fIsStore = TRUE; + u16cpy(file_store[0].szName, u"a"); + u16cpy(file_store[1].szName, u"b"); + u16cpy(file_store[2].szName, u"c"); + + // call, KmnCompilerMessages::ERROR_501FeatureOnly_Call + fileKeyboard.version = VERSION_50; + u16cpy(str, u"call"); + EXPECT_EQ(KmnCompilerMessages::ERROR_501FeatureOnly_Call, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, KmnCompilerMessages::ERROR_CallInVirtualKeySection *** TODO *** + + // call, no close delimiter => NULL + fileKeyboard.version = VERSION_501; + u16cpy(str, u"call("); + EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, empty delimiters => empty string + fileKeyboard.version = VERSION_501; + u16cpy(str, u"call()"); + EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, space in delimiters (see I11814, I11937, #11910, #11894, #11938) + fileKeyboard.version = VERSION_501; + u16cpy(str, u"call( )"); + EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, KmnCompilerMessages::ERROR_StoreDoesNotExist + // fileKeyboard.version = VERSION_501; + // u16cpy(str, u"call(d)"); + // EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, KmnCompilerMessages::ERROR_StoreDoesNotExist, space before store + // fileKeyboard.version = VERSION_501; + // u16cpy(str, u"call( d)"); + // EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, KmnCompilerMessages::ERROR_StoreDoesNotExist, space after store + // fileKeyboard.version = VERSION_501; + // u16cpy(str, u"call(d )"); + // EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); +} + // KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) // KMX_DWORD process_platform(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) // KMX_DWORD process_if_synonym(KMX_DWORD dwSystemID, PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) From 7e2f797dd8d65958e194a03d3696b580677afb96 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 22 Aug 2024 11:35:49 +0100 Subject: [PATCH 120/262] fix(developer): move check on store name search to just after search --- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmcmplib/tests/gtest-compiler-test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 3893a728559..313cc859e59 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -2310,11 +2310,11 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX { if (u16icmp(q, fk->dpStoreArray[i].szName) == 0) break; } + if (i == fk->cxStoreArray) return KmnCompilerMessages::ERROR_StoreDoesNotExist; if (!kmcmp::IsValidCallStore(&fk->dpStoreArray[i])) return KmnCompilerMessages::ERROR_InvalidCall; kmcmp::CheckStoreUsage(fk, i, FALSE, FALSE, TRUE); - if (i == fk->cxStoreArray) return KmnCompilerMessages::ERROR_StoreDoesNotExist; tstr[mx++] = UC_SENTINEL; tstr[mx++] = CODE_CALL; tstr[mx++] = (KMX_WCHAR)i + 1; diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index 9d335de83a1..87db0faa664 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1235,7 +1235,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) { PFILE_STORE file_store = new FILE_STORE[100]; fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; - file_store[1].fIsStore = TRUE; + file_store[1].fIsCall = TRUE; u16cpy(file_store[0].szName, u"a"); u16cpy(file_store[1].szName, u"b"); u16cpy(file_store[2].szName, u"c"); From 75508f46b451f9dde798e9c0832814f443648b92 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 22 Aug 2024 11:43:03 +0100 Subject: [PATCH 121/262] fix(developer): add ERROR_StoreDoesNotExist test cases to GetXStringImpl_type_c test --- .../src/kmcmplib/tests/gtest-compiler-test.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index 87db0faa664..f55bb63ffa4 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1263,19 +1263,19 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) { EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); // call, KmnCompilerMessages::ERROR_StoreDoesNotExist - // fileKeyboard.version = VERSION_501; - // u16cpy(str, u"call(d)"); - // EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + fileKeyboard.version = VERSION_501; + u16cpy(str, u"call(d)"); + EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); // call, KmnCompilerMessages::ERROR_StoreDoesNotExist, space before store - // fileKeyboard.version = VERSION_501; - // u16cpy(str, u"call( d)"); - // EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + fileKeyboard.version = VERSION_501; + u16cpy(str, u"call( d)"); + EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); // call, KmnCompilerMessages::ERROR_StoreDoesNotExist, space after store - // fileKeyboard.version = VERSION_501; - // u16cpy(str, u"call(d )"); - // EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + fileKeyboard.version = VERSION_501; + u16cpy(str, u"call(d )"); + EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); } // KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) From f082ddc0708b88b7c5568ce1d69d95f77c09a2c0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 22 Aug 2024 13:20:52 +0200 Subject: [PATCH 122/262] chore(common): adjust build settings for windows clean builds --- common/windows/.gitignore | 1 + .../delphi/components/common_components.dproj | 63 ++++-------------- .../ext/cef4delphi/packages/CEF4Delphi.res | Bin 708 -> 0 bytes .../delphi/tools/certificates/build.sh | 2 + docs/build/linux-ubuntu.md | 26 ++++++-- docs/build/macos.md | 36 ++++++++-- docs/minimum-versions.md | 3 +- windows/src/.gitignore | 5 ++ windows/src/README.md | 6 +- windows/src/desktop/kmshell/kmshell.res | Bin 7036 -> 0 bytes windows/src/engine/keyman/build.sh | 1 + 11 files changed, 75 insertions(+), 68 deletions(-) delete mode 100644 common/windows/delphi/ext/cef4delphi/packages/CEF4Delphi.res delete mode 100644 windows/src/desktop/kmshell/kmshell.res diff --git a/common/windows/.gitignore b/common/windows/.gitignore index 7af23e7e1aa..a6cb808a340 100644 --- a/common/windows/.gitignore +++ b/common/windows/.gitignore @@ -7,3 +7,4 @@ delphi/**/*.~* delphi/**/__recovery/ delphi/**/__history/ delphi/general/keymanversion_build.inc +delphi/ext/cef4delphi/packages/CEF4Delphi.res diff --git a/common/windows/delphi/components/common_components.dproj b/common/windows/delphi/components/common_components.dproj index 2bd91473abe..4ecda904642 100644 --- a/common/windows/delphi/components/common_components.dproj +++ b/common/windows/delphi/components/common_components.dproj @@ -13,31 +13,6 @@ true - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - true Base @@ -59,6 +34,12 @@ Base true + + true + Cfg_2 + true + true + .\obj\$(Platform)\$(Config) .\$(Platform)\$(Config) @@ -77,23 +58,6 @@ 3081 CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - - None - android-support-v4.dex.jar;cloud-messaging.dex.jar;com-google-android-gms.play-services-ads-base.17.2.0.dex.jar;com-google-android-gms.play-services-ads-identifier.16.0.0.dex.jar;com-google-android-gms.play-services-ads-lite.17.2.0.dex.jar;com-google-android-gms.play-services-ads.17.2.0.dex.jar;com-google-android-gms.play-services-analytics-impl.16.0.8.dex.jar;com-google-android-gms.play-services-analytics.16.0.8.dex.jar;com-google-android-gms.play-services-base.16.0.1.dex.jar;com-google-android-gms.play-services-basement.16.2.0.dex.jar;com-google-android-gms.play-services-gass.17.2.0.dex.jar;com-google-android-gms.play-services-identity.16.0.0.dex.jar;com-google-android-gms.play-services-maps.16.1.0.dex.jar;com-google-android-gms.play-services-measurement-base.16.4.0.dex.jar;com-google-android-gms.play-services-measurement-sdk-api.16.4.0.dex.jar;com-google-android-gms.play-services-stats.16.0.1.dex.jar;com-google-android-gms.play-services-tagmanager-v4-impl.16.0.8.dex.jar;com-google-android-gms.play-services-tasks.16.0.1.dex.jar;com-google-android-gms.play-services-wallet.16.0.1.dex.jar;com-google-firebase.firebase-analytics.16.4.0.dex.jar;com-google-firebase.firebase-common.16.1.0.dex.jar;com-google-firebase.firebase-iid-interop.16.0.1.dex.jar;com-google-firebase.firebase-iid.17.1.1.dex.jar;com-google-firebase.firebase-measurement-connector.17.0.1.dex.jar;com-google-firebase.firebase-messaging.17.5.0.dex.jar;fmx.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar - - - None - android-support-v4.dex.jar;cloud-messaging.dex.jar;com-google-android-gms.play-services-ads-base.17.2.0.dex.jar;com-google-android-gms.play-services-ads-identifier.16.0.0.dex.jar;com-google-android-gms.play-services-ads-lite.17.2.0.dex.jar;com-google-android-gms.play-services-ads.17.2.0.dex.jar;com-google-android-gms.play-services-analytics-impl.16.0.8.dex.jar;com-google-android-gms.play-services-analytics.16.0.8.dex.jar;com-google-android-gms.play-services-base.16.0.1.dex.jar;com-google-android-gms.play-services-basement.16.2.0.dex.jar;com-google-android-gms.play-services-gass.17.2.0.dex.jar;com-google-android-gms.play-services-identity.16.0.0.dex.jar;com-google-android-gms.play-services-maps.16.1.0.dex.jar;com-google-android-gms.play-services-measurement-base.16.4.0.dex.jar;com-google-android-gms.play-services-measurement-sdk-api.16.4.0.dex.jar;com-google-android-gms.play-services-stats.16.0.1.dex.jar;com-google-android-gms.play-services-tagmanager-v4-impl.16.0.8.dex.jar;com-google-android-gms.play-services-tasks.16.0.1.dex.jar;com-google-android-gms.play-services-wallet.16.0.1.dex.jar;com-google-firebase.firebase-analytics.16.4.0.dex.jar;com-google-firebase.firebase-common.16.1.0.dex.jar;com-google-firebase.firebase-iid-interop.16.0.1.dex.jar;com-google-firebase.firebase-iid.17.1.1.dex.jar;com-google-firebase.firebase-measurement-connector.17.0.1.dex.jar;com-google-firebase.firebase-messaging.17.5.0.dex.jar;fmx.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar - - - None - - - None - - - None - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) Debug @@ -113,6 +77,7 @@ false true 1033 + ..\..\lib false @@ -120,6 +85,11 @@ 0 0 + + ..\..\lib + true + 1033 + MainSource @@ -164,8 +134,6 @@ common_components.dpk - File c:\projects\keyman\app\windows\lib\DCPdelphi2009.bpl not found - File c:\projects\keyman\app\windows\lib\delphiprojectmanager.bpl not found Microsoft Office 2000 Sample Automation Server Wrapper Components Microsoft Office XP Sample Automation Server Wrapper Components @@ -944,13 +912,6 @@ - False - False - False - False - False - False - False True False diff --git a/common/windows/delphi/ext/cef4delphi/packages/CEF4Delphi.res b/common/windows/delphi/ext/cef4delphi/packages/CEF4Delphi.res deleted file mode 100644 index b9b013c2123fcf20e24680fc5ad3191fc452d216..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 708 zcmZ{i&q~8U5XQgO9IRfvd-32|A_PGYr5Gy(ZKal8CDvLCZBo*D^K>y3I?ZFSY*+-7ZI zn|uX7zmnlg4!>VHuVjSY^LM3BE^6>Cm209@W7sB|Y6NqtS7ii#xR_9pLY<=#HCRQV zcES4i0hnA1&>2XRQ=JgwRXWo?@y5G!v8fU=by#PdrE7RwFvpyxn(GZNlM?p;d!`q? zYY4tVZO28fzIEJYm76v8bV!XQW=uw_txZKHL-Nn)XTuv$k<~^sAYF^v$mc7QyTSm`Py^eJ3Ubc@nVYUlqm#pzNg71JYrVF$v_UP_TP4(gG W@|W(hn(Vhszt2x7<^TWN;c5kN0bbt# diff --git a/common/windows/delphi/tools/certificates/build.sh b/common/windows/delphi/tools/certificates/build.sh index 378723a1257..10ab4c75cec 100755 --- a/common/windows/delphi/tools/certificates/build.sh +++ b/common/windows/delphi/tools/certificates/build.sh @@ -21,6 +21,8 @@ builder_describe_outputs \ function do_certificates() { rm -f KeymanTestCA-sha1.* KeymanTest-sha1.* KeymanTest-sha256.* KeymanTestCA-sha256.* + source "$KEYMAN_ROOT/resources/build/win/visualstudio_environment.inc.sh" + makecert -r -pe -n "CN=Keyman Test CA SHA1" -ss CA -sr CurrentUser -a sha1 -cy authority -sky signature -sv KeymanTestCA-sha1.pvk KeymanTestCA-sha1.cer certutil -user -addstore Root KeymanTestCA-sha1.cer makecert -pe -n "CN=Keyman Test Certificate SHA1" -a sha1 -cy end -sky signature -ic KeymanTestCA-sha1.cer -iv KeymanTestCA-sha1.pvk -sv KeymanTest-sha1.pvk KeymanTest-sha1.cer diff --git a/docs/build/linux-ubuntu.md b/docs/build/linux-ubuntu.md index 0107491dda5..a45d9390ba5 100644 --- a/docs/build/linux-ubuntu.md +++ b/docs/build/linux-ubuntu.md @@ -63,17 +63,33 @@ See [node.md](node.md) for more information. #### Emscripten -You'll also have to install `emscripten` (version 3.1.44 is known to work): +You'll also have to install `emscripten`: ```shell git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install 3.1.44 -./emsdk activate 3.1.44 -export EMSCRIPTEN_BASE=$(pwd)/upstream/emscripten +./emsdk install 3.1.58 +./emsdk activate 3.1.58 +export EMSCRIPTEN_BASE="$(pwd)/upstream/emscripten" +echo "export EMSCRIPTEN_BASE=\"$EMSCRIPTEN_BASE\"" >> .bashrc ``` -**NOTE:** Don't put EMSDK on the path, i.e. don't source `emsdk_env.sh`. +> ![WARNING] +> Don't put EMSDK on the path, i.e. don't source `emsdk_env.sh`. +> +> Emscripten very unhelpfully overwrites `JAVA_HOME`, and adds its own +> versions of Python, Node and Java to the `PATH`. For best results, restart +> your shell after installing Emscripten so that you don't end up with the +> wrong versions. + +**Optional environment variables**: + +To let the Keyman build scripts control the version of Emscripten installed on +your computer: + +```shell +export KEYMAN_USE_EMSDK=1 +``` ## Keyman Core diff --git a/docs/build/macos.md b/docs/build/macos.md index 81b1bcd0e79..25b0c1f82ae 100644 --- a/docs/build/macos.md +++ b/docs/build/macos.md @@ -91,17 +91,39 @@ See [node.md](node.md) for more information. ### emscripten -To install emscripten: +The recommended way to install emscripten is with the official method, as that +then allows the build scripts to select the appropriate version automatically. + +To install emscripten, `cd` to an appropriate path, and then: + +```bash +git clone https://github.com/emscripten-core/emsdk +cd emsdk +emsdk install 3.1.58 +emsdk activate 3.1.58 +export EMSCRIPTEN_BASE="$(pwd)/upstream/emscripten" +echo "export EMSCRIPTEN_BASE=\"$EMSCRIPTEN_BASE\"" >> .bashrc +``` + +You will want to add `EMSCRIPTEN_BASE` to your .bashrc. + +> ![WARNING] +> Don't put EMSDK on the path, i.e. don't source `emsdk_env.sh`. +> +> Emscripten very unhelpfully overwrites `JAVA_HOME`, and adds its own +> versions of Python, Node and Java to the `PATH`. For best results, restart +> your shell after installing Emscripten so that you don't end up with the +> wrong versions. + +**Optional environment variables**: + +To let the Keyman build scripts control the version of Emscripten installed on +your computer: ```shell -brew install emscripten +export KEYMAN_USE_EMSDK=1 ``` -Note: if you install emscripten with brew on macOS, only emscripten binaries are -added to the path via symlinks. This makes it reasonably safe to have emscripten -on the path, unlike on other platforms where emscripten also ends up adding its -versions of node, python, and other binaries to the path. - ## Keyman for iOS Dependencies * XCode, swiftlint, carthage diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md index a9a2ead26b9..f5826320070 100644 --- a/docs/minimum-versions.md +++ b/docs/minimum-versions.md @@ -49,7 +49,6 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN Variable | Value | |-----------------------------------|--------------| | KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER | noble | -| KEYMAN_MAX_VERSION_EMSCRIPTEN | 3.1.58 | | KEYMAN_MIN_TARGET_VERSION_ANDROID | 5 | | KEYMAN_MIN_TARGET_VERSION_CHROME | 95.0 | | KEYMAN_MIN_TARGET_VERSION_IOS | 12.2 | @@ -58,7 +57,7 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN_MIN_TARGET_VERSION_WINDOWS | 10 | | KEYMAN_MIN_VERSION_ANDROID_SDK | 21 | | KEYMAN_MIN_VERSION_CPP | 17 | -| KEYMAN_MIN_VERSION_EMSCRIPTEN | 3.1.44 | +| KEYMAN_MIN_VERSION_EMSCRIPTEN | 3.1.58 | | KEYMAN_MIN_VERSION_MESON | 1.0.0 | | KEYMAN_MIN_VERSION_NODE_MAJOR | 20 | | KEYMAN_MIN_VERSION_NPM | 10.5.1 | diff --git a/windows/src/.gitignore b/windows/src/.gitignore index 350b7e4bdbf..065f4a07f3b 100644 --- a/windows/src/.gitignore +++ b/windows/src/.gitignore @@ -34,3 +34,8 @@ keyman-windows-coverity.tgz # This file is still generated by devtools. TODO: eliminate in future PathDefines.mak + +# .res files, generated during build +desktop/kmshell/kmshell.res +engine/keyman/keyman.res +engine/keyman32/version64.res diff --git a/windows/src/README.md b/windows/src/README.md index 1d057482bb7..ae0090c340e 100644 --- a/windows/src/README.md +++ b/windows/src/README.md @@ -45,8 +45,8 @@ build, you will need to obtain valid code signing certificates. See Certificates, below. Official release builds for Keyman are built in the Keyman project's CI environment. -1. Start 'Developer Command Prompt for VS 2019'. -2. Run `nmake release` from the **windows/src** folder. +1. Start bash +2. Run `./build.sh :publish` from the **windows/src** folder. 3. Artifacts from a successful build will be placed in **windows/release** folder. 4. **buildtools/help-keyman-com.sh** will push updated documentation to @@ -65,7 +65,7 @@ globally trusted. The environment variables `SC_PFX_SHA1` and `SC_PFX_SHA256` can be set to custom certificate paths. The Keyman repo can build test certificates for you. To build your own, run -`./build.sh certificates` from **common/windows/delphi/tools/certificates** to +`./common/windows/delphi/tools/certificates/build.sh certificates` to build and install your own local root CA "**KeymanTestCA**" certificates. If you specify a password for the certificate, you'll need to set that in the environment variable `SC_PWD`. diff --git a/windows/src/desktop/kmshell/kmshell.res b/windows/src/desktop/kmshell/kmshell.res deleted file mode 100644 index cfcbd309f137d6a5f5221aba48a1c759c0d56682..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7036 zcmeI1J!~7v6~~`s1u|_|P!*hAZ5EMn2(S`ACF(%RC)jrp6@LgheU!nHyOao#yBjVg zo)iR7mlS_n#!2}n!$--QQ1 zw}VKUad-O3=WLJru871Gadh$=T@Rj&BMP$9%qGz<#XB(zRY#^=#HP2+AL{#8-^a+g zf^j_WLSsz~4mQ`s$Iu3GG1DW)BRWJoD6BQh1`+Me%6}DUhy#9^LSC{pCb2-($ePa zZ9otyYiV#E4^IxM{`WtNd~z23bT;zJ4G&4J|JS2Gef1U9>K^T%d9+#cX!*#asRNIO zcHqU=_6gDYQ=;rcqG^{XIzzQ{@W`jJv-NeiQsM z_$ly1U`1r$2|R(v`XhWM&%qyq-vz$~-UYt^ehz$wPACmN0)7bmRq%E2XW(n#kH8;* z-vPe^ejEHc_$>Ho@KNv{cn`b>-UIJ}_rUY(1h1!fJ;ck!Yew*O@Mqv_;E%u`falS7 zKyQOy2b~2y4LXYP&%qyq-vz$~-UYt^ehz#Fd>VWN{1Eu7;4$w0KKM=W%iyQL4}mA} zo!LRw&cPpp-vz$~-UYt^ehz#Fd>Yw|Ae%$T=2h@@@Mqv_;E%u`fZqYX1AZI)I`}O3 zY4B0-9(WJD2i^nkf%m{8pc4f26mQo0sOlYRP*ZK&%oEf zAAvsrkK}YoPz{OL$F+%T8P^o9Aza-O^!DvrdiClRy?ptSUc7ig&!0c1Cr_Tx&dv@! ze*BoWx3}s3{rj}OzD|~9Q8t^U`T2R8o}Q+Oi3y5EqjdfHb-H%#8Vw8#U`4c-VOfZN zE(-Z(y)HYXO?4E}A#7pF4z{I{Y*jMr?`Us zY(susqo=cM;7b^N_#ze+?r8p?{S4lsgs`EZ@ZQE{&$o%%><9gh1bm&aqqXo4cn=hS z)FJzTuvzv2VGCITY)e@JZ2PhRU_<7e)2VU{up#?4V%Q6bxuuR6_Co9(=vCUo1`Hef zF#OTL-YaZ4umW3d&;$lHo%W)If?n9pz-h0-8#d#x>Gf%^Dkk7jAhWIT45ASB>t0m= zY(o}y06-SD53nq439u|}IR(qU&47@78-VZ*bi^R#Kvzk4L-*h2pG@@^95EC`BhPhx zBChVA19=|VkeGai59g5&=Mi2%AI>8m&Li?0;oW!tfZ|udIYpjUaNdvylmOx(0!f}{ z+H^jBv;%Ga!QrH3gjQY3?IX14f!qc(^`_hkH1t-v1qk$Zxe54sr`!OfE@l6W#V4&s z9)#|D3;m6_LzjtSJs7-07Xb7?mS?!8zZikv5OH|p0qbvsCFkc}RvR+w9GK{3)z<~U z9Qz3!wJ+m5hqE4&@d{=O19uh50_hB@aQZzmcYHd|4_gt7h3P*j8GN42M<4v~spluolzm z-0bICQdQiFZW(&fw#>A;X1eN)i7S^8@5P1ReA{footf3x_YM+MG4qv6ijp!+$FvMYx;6o`rK2+G z$0hrr1c7FNR|=Ii!Lv_g&A|gz3sx+uHaac6gw8v`0}L+|EW@t4!#C|x$+oncwpA(H zMYrkD$ld60m|soBS;-a)x!;&;v-YxyOqtGb)9R*{o( z^I;Wbr7)S}=S{15z37@v$$wl-Fw&TzbNbn z+zI8%rBF(D9K4$t$F@XADD?|Xn_V)M%q=Br7VWBnmBN7ODqt0N#c}L%MakP`#a~3q zd;uRNMbi*fD3tOE)42DAk}=ZiAIxYt5{W0qv`8X8s>Nfugr+C*rWOt-4bwDZMl2ux zqdM~5(~otZI+lpUlG$-xOBmyrI-fPPq!~3dJsOXPqtQfuJf`#1BJdyn;K}YY$MtAF znlxitB9O># zM6+5lW`y&jM%d8De$3pFaQCT`W;j2lN1|FbpN%2mW>Q1?6Iwi(HxgkzlF-LR!b71D zufCCv<(C?1Tc*6sM}jXp7}ytxQsR665`MS6jh$u|e^a@Kzk%IBYZCRZ@z Date: Thu, 22 Aug 2024 13:26:56 +0200 Subject: [PATCH 123/262] Update windows.md --- docs/build/windows.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/build/windows.md b/docs/build/windows.md index 663b68c2f89..3168bcd0ae0 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -272,6 +272,10 @@ of appropriate node versions during builds. 3. Change 'Tab size' to 2 and 'Indent size' to 2. 4. Select 'Insert spaces'. +* Windows SDK (C++ Desktop Development) + + https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ + **Required environment variables**: * `PATH` * Add the `C:\Projects\keyman\keyman\windows\lib` folder in the Keyman From b608e1ca84cffac3a237f5c5338e60df0c979df9 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 22 Aug 2024 15:15:32 +0100 Subject: [PATCH 124/262] fix(developer): add ERROR_InvalidCall and first valid test case to GetXStringImpl_type_c test --- .../src/kmcmplib/tests/gtest-compiler-test.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index f55bb63ffa4..c3ea2a90373 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1236,6 +1236,7 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) { fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; file_store[1].fIsCall = TRUE; + file_store[1].dwSystemID = TSS_NONE; u16cpy(file_store[0].szName, u"a"); u16cpy(file_store[1].szName, u"b"); u16cpy(file_store[2].szName, u"c"); @@ -1276,6 +1277,22 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) { fileKeyboard.version = VERSION_501; u16cpy(str, u"call(d )"); EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, KmnCompilerMessages::ERROR_InvalidCall + fileKeyboard.version = VERSION_501; + file_store[1].dpString = (PKMX_WCHAR)u"*"; // cause IsValidCallStore() to fail + u16cpy(str, u"call(b)"); + EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidCall, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // call, valid + fileKeyboard.version = VERSION_501; + file_store[1].dpString = (PKMX_WCHAR)u"a.dll:A"; + file_store[1].dwSystemID = TSS_NONE; + u16cpy(str, u"call(b)"); + EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_call_valid[] = { UC_SENTINEL, CODE_CALL, 2, 0 }; + EXPECT_EQ(0, u16cmp(tstr_call_valid, tstr)); + EXPECT_EQ(TSS_CALLDEFINITION, file_store[1].dwSystemID); } // KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) From 862f0d9191896951a7fb929e1991f7a59c8e8122 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 22 Aug 2024 15:21:01 +0100 Subject: [PATCH 125/262] fix(developer): add test cases for spaces before and after valid store in call in GetXStringImpl_type_c test --- .../src/kmcmplib/tests/gtest-compiler-test.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index c3ea2a90373..aab01f8131c 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1293,6 +1293,24 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) { const KMX_WCHAR tstr_call_valid[] = { UC_SENTINEL, CODE_CALL, 2, 0 }; EXPECT_EQ(0, u16cmp(tstr_call_valid, tstr)); EXPECT_EQ(TSS_CALLDEFINITION, file_store[1].dwSystemID); + + // call, space before store, valid + fileKeyboard.version = VERSION_501; + file_store[1].dpString = (PKMX_WCHAR)u"a.dll:A"; + file_store[1].dwSystemID = TSS_NONE; + u16cpy(str, u"call( b)"); + EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(tstr_call_valid, tstr)); + EXPECT_EQ(TSS_CALLDEFINITION, file_store[1].dwSystemID); + + // call, space after store, valid (see I11937, #11938) + fileKeyboard.version = VERSION_501; + file_store[1].dpString = (PKMX_WCHAR)u"a.dll:A"; + file_store[1].dwSystemID = TSS_NONE; + u16cpy(str, u"call(b )"); + EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(tstr_call_valid, tstr)); + EXPECT_EQ(TSS_CALLDEFINITION, file_store[1].dwSystemID); } // KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) From 5fb374341677db19b0b48c6503ef9cd4e050bb47 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 22 Aug 2024 14:02:35 -0400 Subject: [PATCH 126/262] auto: increment master version to 18.0.96 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 7bb236717bb..2ee27cd1989 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 18.0.95 alpha 2024-08-22 + +* chore(common): allow build agents to automatically select emsdk version, and enable support for 3.1.60+ (#12243) + ## 18.0.94 alpha 2024-08-21 * fix(core): look for `emcc` instead of `emcc.py` (#12235) diff --git a/VERSION.md b/VERSION.md index a41e5425c0a..998f9bf6fd3 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.95 \ No newline at end of file +18.0.96 \ No newline at end of file From 044c25a0e49b14028507629d36dedf98b433c45d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 23 Aug 2024 08:29:08 +0700 Subject: [PATCH 127/262] change(common/models): increase specificity in wordbreaker's .gitignore --- common/models/wordbreakers/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/.gitignore b/common/models/wordbreakers/.gitignore index c51c25f2fd7..032a2a523f4 100644 --- a/common/models/wordbreakers/.gitignore +++ b/common/models/wordbreakers/.gitignore @@ -1,2 +1,2 @@ build/ -**/**.inc.ts \ No newline at end of file +src/main/default/data.inc.ts \ No newline at end of file From fed6582f6280983d2d67ab7dfaee8ee891facea9 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 23 Aug 2024 08:32:25 +0700 Subject: [PATCH 128/262] fix(android): Check in material-stepper as internal dependency Follows this guide on [setting up a local maven respository](https://brightinventions.pl/blog/migrating-away-from-bintray-jcenter-when-there-is-no-alternative-repository/#setting-up-a-local-maven-repository) now that jcenter is deprecated. --- android/KMAPro/build.gradle | 11 ++- android/KMAPro/kMAPro/build.gradle | 2 +- .../4.3.1/material-stepper-4.3.1-javadoc.jar | Bin 0 -> 219877 bytes .../4.3.1/material-stepper-4.3.1-sources.jar | Bin 0 -> 48791 bytes .../4.3.1/material-stepper-4.3.1.aar | Bin 0 -> 121509 bytes .../4.3.1/material-stepper-4.3.1.pom | 90 ++++++++++++++++++ 6 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar create mode 100644 android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar create mode 100644 android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar create mode 100644 android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index b1f2d240a72..07a61850c93 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -2,8 +2,12 @@ buildscript { repositories { google() - jcenter() + //jcenter() deprecated August 2024 + mavenLocal() mavenCentral() + flatDir { + dirs "kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/" + } } dependencies { classpath 'com.android.tools.build:gradle:7.4.2' @@ -13,14 +17,15 @@ buildscript { // From jcenter() which could be sunset in future // https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ - classpath 'com.stepstone.stepper:material-stepper:4.3.1' + classpath 'com.stepstone.stepper:material-stepper:4.3.1@aar' } } allprojects { repositories { + maven { url uri("${projectDir}/libs") } google() - jcenter() + //jcenter() deprecated August 2024 mavenCentral() maven { url "https://jitpack.io" } } diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index 7eec37bcefa..e44f72ad576 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -145,7 +145,7 @@ repositories { dirs 'libs' } google() - jcenter() + //jcenter() } dependencies { diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar new file mode 100644 index 0000000000000000000000000000000000000000..895d772c3976617b65c3fb988fb60f6e132438c5 GIT binary patch literal 219877 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%0Aqz|3wIo{y6{Jwzh zTR{(?`xLapO)N9yqF5_8q)X{`d+M1!``%ft_=Nn=W;r&qz=VGU9l4*NBltfKIzqPA zc2*`%CjVbyhv%PR2lwA$$Lcvl=qK#7NB(!%A^k_#xqkXr*tsW`sB7VmT27YuDWA)F zJtkh7xM>fJd7sdM_qMaOo!uI#%)EfBKaY8M_&s9ZZlmU!wH9r3=zueKTb06n5XjrO#^81oW%u+8P4R?8GBq9ARh_o8~Sd*9)K`#81_$A_R?BNT`ehyjP6 zfd+XL=BK{{^T7rQITG+<-YA@Vhw~mE`twz#)p2(=(jbHqJPK=Sz6-g?F81>Z$t)V$ z?euR5E$45X?=mw4W%_w6D8_6gpTN{P>D9nmWSNpEh)1m;zDO!ZP~D1@HpjoSOxDlbkXf z93oyz=FX(Ogyc-^SpXbD!$^S+rI*C_=Cu6;XZ0-;3K$+OtwXstY3>WLV+LX}?7xx{ zg9GE?uupHAIt&c4dEXWZd>+N!%=dXOt$gyHuT(-tA&A{$52=kT@#YRSm%67Md_es9 zHX>{FZ;T6!Ot>SK5pn+26))o=E07^L3wM6@g*`Z#EYLSyk@%{bt7J_X`;6Ih8xCTN1l9R&%9{&~b~ zwhhhbp>#)orw|5LH`#7ijMa9YhMi!PCORI~rFiOIA_Z9v$PFhjJjR&Kdb^CSzXY!0 zmbmW3&PEVWe~GgnGVueT&#`b8X5x;A)B_+hcKHj%Mr%cSr}@74Vz>wj(#URXLxi^4 zZU^e(dKUYl6UhQI3cc_eOd{`t|R{bEm%MDQ%d41U%vvkx0#D9+)vb* zwQ+d{?Cwe4QhXNecx1imMx|6ta{u16c1C?9(I7mA2Z4Ge3+BGzz`#>;d*q0Ck9X-` z2T_?2TeKCl*RMz)%wre06K;e6mCx#vhlI8EFE&G-@y!&50vUCORa zyClaDbSrYm061Jzh(HLAo~4h69?BnHhrSx8=6wSpnpvs#t#auu4N`eD0fjjwQBF-K zBeKC32McBp9{x1>xAx9G3?IhaCQ0S%E_TVH=-+fj?iG4v+VF!+$P)~$ZEbml-L zcSzFh(VCV=ib=rFVaS-4@njd?YTgi?qLd_-Xa*TrGCYukY0@m7O~=5@B9X5I!n}fD zQH^Nxi0KO2Q@2ypB$(O*b3tGwS(F+V3=ILSwG;7>xfD;TJxZU(i{eF=GxqG%xc+@q z!4uF5<_Z*ZQTwf1Qw2~%DCiZP;I>5N9b{r%TBC7uD_`iIPm+3 z`>S+p!C(|P;>b1-WW&F~``&_TlX5s#$3tb7R@p?Mc`UQ(3g3sD$!ulz)W6RXt zD7kYuSNICSxSg~8%{!56RNceGg7oz+%unC*cw^)iAEh;&nt`!4&O#GW@}4EQKF9f|!OVU+^qaD3 z?Xe7?aPxwVoOgp+oydoi;5W)}s%Qt9m~MMNbTW--Ye(B=CG|?BDDxt2lOxUXL7!_I z>)6>DSmlN+bq@c4FJ_2_he;6};mGx|Judep`Of}{p?@Tvjh(2L`KYChezNpBMELCq zt|}s;O;KK4rGmZQV8P#Zc?7P3D}zoyi3i~fE@R`Jl6uYslQ~^%k!|evd_BGD+I=eE zPdxzXG|9|+|2R`EVKZzoNjJ|iKT=ZAClHAX@w&LIVTp)?rgfRF(d(wKTzyfkve+_P z!}k<0A}9r>WTo{wP9kve$#W`H_EO-W?6&j?Ij< z-cBd;os5lW^S!Qjq>)#y`TA%IYuA+c)O?=SS#(42c%4>)R7xxK51x*BxOo` z9qLLC26TVOXTm7ghEfUu<%X6z^6%eL@9Q5;Jz3X|IR)%nAJV&zSFrPnNgCovniR8MPKR+`~nl zo7_^|nTt1_1^Z*xn0Pw7EO`m{sPq$^XPiS}MOj*ITi)D`2c^_Vu^Hc6A`VkCyaR4R z8*-sim5z^mT`?ZUo{?CPrHk3TVS2Fz|m20tlhun(CNW577TP*rZyT>ZM-Giv>rEi^S}PN@S2Ib0!Xr0D0?dRqW6 zmNeXCLBa7~DMu5c@D3R%h$UN?7DIwauqg1*5GVl2^f`dUUY=yM?5y>V+1H_v&Q>*< zH7D>=Rnjm2JY*j7-@bleGn{Ut14FFAG)=H9Aovt$r)r-? zyiP%I81S`FfNIsf#rERek7D3_^F?ryVScmbk_@DLcm<}&OR@0K`{zu3KwiiFF|s4) zgj%N)&10`|Q}^~vr2Hz=kP-yGz z*VF#{f_p|3)KVwk}b;~Q1OG(B?Kp=(pC*zp?RS2b4EQi!k zSgSggNNu%9n1DUjNWd^1G8?Rpn21c1CNIXExQ7k*D2<&o zy*I2TfyU}zK#+iHog_}T8&iy-N^}h72%~WM5N>tFSZrshK?zl>;bSc?M^g|J!U_C= zu6{C(`k2k~PsRcMN5+xdF8j$iME$><{g9|0Abrq;J1LZQOr;_LnQ_lw%GX<+nLxcR zDHbJ2V#CeuMzln$;qc`!NIsApFdydAW|@evgg!Ak(q&i9gKA}eu}{x~+_r(&xj_>` zy;F@|%H7eYd)0Q7hBP<3|3=^Vxk&_8@tQzWDswap0z7=G*EDz)t(+3Q>c-?$Z4CEa zv(}@mkZ2dA{^^6erNeUNg`(%wIGFMN57*K7hwJFUKzY}Vs{cQ(16ii|PuHn8B4hn8 z*BLby!BO>``f(l2%^+~gC_ZJDj2O6oyH3hKTqi`kO!yzJlL71R%zcJcbI$p1*O5M> zb0~np{&5|Ef4WXR`v2xS9hy)X22aqh8bPmtte|MdmW#<2Ko$Vjjq57BWJW~xUo}~Xnd9uD~2+PYKEMZRiz5@xii2tcP={40zGyknT-?wcC&-k;u z4_i9#dnZ526Z2onV?f2(^xw*pUBQL#?DT(A9)W)<4_|i1e=1K1KB4pYKb0ruU&_Pw z59PUq{!it>{U0cgw#UDfC-h&+WAbn1dB@9GzB_eI)_LU2imq%>H}QX3>LYZMTFdm1 z4_OP~>GGCJS0sXxadN%#J$F?zd^g$al3t`F-ayFvxALI=Z_0CK{G&XMPf`C=o-Fo% zE04&J^7M`Efjh^`cjx^m53gw!&ra0yc+^oxZ`b5%7$E)BX#sr^9QZ|!>IQk&Sby-wEdeC!3pSF zLVKEEl6)>Cez?Yy~;(c;j!%`6oO`|dmqsL8O>4c)YMd1}&)9(&3A}K}Atd#XA z4pw-|nX?*H&Iz%mt^@tY$y85dSy+Zeg!>5;CURV2EF;lxV47MRZ%!UTMA-tkC>}wE zRHsJq@8EO0AQF6Wz@-z2DTd^s8%nZjZ(YL9V?#oB%sm>W?dztU?ly1h{>*JZtD}zN z=HWNqrN$HrYY(;fbYp(kYN~<;=OAd0h*AdRU&%(OR;oDi%ZjyGK{RdeEaa=AdK>F% zXU9iacaqyQsTq^yzMsH#^BU zOR6?wAvgq%+ONwqPF7zcI9$zW>{WdBprOD|G23Sm@&Hm9I?>#%7 z%_rC}LB?m>%}|p1!%v<*Pw!86nH;azmla$ukL_aHu(WiekM9eS543mZ0=H5P*dS(s zOdIxxY;dFe^TI=%KRzQcd*L~VZOOv}7cgYFN0-qZ=i0aX3*!Qk52_U*;x#^|^J#m` z^^x1|OD?Dxy?j!eecqBLG zDO`M1pPN`M|5x-%(cQzH!T|zX@Me|x%;(W7tsr=0 z&%%t`_enmx($Sa;cysVd1dEr6aDv6$j44)&_g;LpzOV8-PMH!~+cd1iN~q&*6_MI@ zVpf%6NidO}(v{yG_v>WWVC*0^mZ3x*ZczrnzyWe(ItBASQrbT)1yeBJl^!0(Rcz8SX4I@$^4MBwy z{biMpCJ{mqVmQkMVaIqu8J)A#It$%|<75MNcuax$^GBlRVXx6K@kw(91dQZRnS!35 z;c9_nRp;CQG8?}9Z$3{n_8<~yqjdT8LDb^g;`8buI)?Ey$W^Zo-VP@4uhX(!;^ZlI zx%NSo^bZAUfpkL!(#@#SdO*MoyI=f#nS;aO+Q*ZwLs?_)lW5K?nbKumw+w9jfP8|A*f@1RtC_-129KyE9VOUr$UB(Do|Q%O5~-1 zqJ$+QgFtD2fgWMFB>yEOaB;`kZYYD6!t7VooKy6qZ-{62mu9?U^65SR|5aVWJf;mo zcr7it=^w4AS!~sFH%uGl z_Pr?zF6B<5gP!aPGyf!ptg|hNpjN%sR@!g@-LZ?>e32r#Uj}NBn`v6YyH5TvC(=Ex zGb*-%%+Md6H72A=Y2`ZfcloVl{F6TXAfP>nB%#>qhk2MfxxLwAEi8j}Mmlj~Eud%9 z)f+WuQ%d$3EqCvlJKvd)L%h z#@t;=tEsZkxE+PrXxgM$VKJmLSwDj5PB2jlVB9}_oT<1IjbM)$aP*q*I^En(Xqf(a z%yj_(6<`MAGEr{i$1rcH7BZ|ndS`$oIkVSGAck_gH{82#GPmfklK?~TWndEF2#z|7 zK~AWlAv#zU$*!G{8Fx3Z89^_d41l>8;5{L8^q|Vc8r;~%Y^_HHVo4q&HGrIRr zhzA;V>cBM>9zMGb4KZD9P!>xAga2t}@_n#OsB{M)9wrpz&dvkYUIaiw8B3&LwF1la zpEGWj*ST9VTaOaXtMjWA1)h&!M8$3*mWaK)db#+9#48d6#Y1m22@M10YUM^rt}D`n zRoSANrp{!Xm0;1@(e~ZPR(E+oN}+&uJS+l`D2P2g3}Pr|03kleC-;n-K)R&$XToU> zJbD}peezVwpevN9n)yhM8LAv;-dX4sea1`Fb~QUpW>p_7qwq&$Tw40Ut5_`&389r( zM}V%*7N1=|KL@}&q@Ei4iU)u~6fU8=v+A~-%qfWjm?j2VuhBADup zAdo)Ql<)ZUJ4Y?h-=HPSs-c=OA+}fN(m@2Dwlyku;8yrFL{W@VcK~61VsV&^Rs_&w zm=Yc$dz}Vj$yn1Ieq;3#gq0;19AirfDk49g7Yr?_o+h3TYL-l*0w%bX8icd6 zA&~xG;auWhQwTlSddPIK7L82 znSS76!zWrJ8sP=V^(6`l4GrZKXRWyj^`NosXfFomN+=Mr{&M5h|4L&4AxBx-jZ$Le zBVO3>FRVpGeeMhB{w(J_gXKI%<=|Z`6wZe-=g?Ym0%Ry^m3N(wOS6@+mbX~p;&Tqc zqW|^YgQG3tD5zcesL9m}PuWr>U0ENFWZKTq7s=A|C0Z+&0UxGXe#fw1mz>W^(H`K< zsAa^^z(xOP1rxrU5YUSZTZq)6{3laUjI37EF=9oKrXJ$t&qY0wfuWqb3`(Zh+A#WD zV!3*_KuOn*QTC)VI%T6;qs>3+=N`iRj*i~u=pB7R9<6!ngL)5{?bI` zXe(I7Wb?bzLb{S)(4U@3Qd7XKCwI2akF8XAfm8ibaMS>tlgW1tf1J+Ri;6Rd*Cu3I zb6Uu)FMpsHYJ-%j<}4SHDPwK>o4r<~uy}b_qW|AWhTh7HWh+RLV<1X;#XI z2N4E%MQn8)?(Tfz|IhN)1a~&I)Ngsqniv3p=Ku4Ur)uJ8VQOJy;A~-ME8^&A=lK5u z4LFc~`0@g)Ky7jR60=ur5=419{(4OlHUz0}4<6W4m`B8}O(ez@Uw^oMZo9qBEjAuD zw*#YvCvYspGvmx0vI`NZ@xhGv{H%Twdfxu0mIc4s%I;&lUi&%Q)N={<6@bU##zj76 z#<8zF$I{<$2`7fuNJNO1R@Q`=GxmYU zhh)6TAMYd|og_9(6p@XHh?5!NM-FMA9*-cyeuk{O&j2fbIplHZ%=^#VH+li&eI6+W zIDiYi{PE_l|(Y#ZXOB=e!OND#bD}g1VXp<=4KaRg{T~t$u zE%XvXF}#oILfp%T;>YJ2EClz;a=-5X0vDu_a;a`!3^R5t;;i&;mL+;a9c2YWXM+^8 zyoko;465_V0Er4)Owsro&C40BC5BaBtuF}C=$s0zH>FgeBIC;)%fDFvQnAk@drpxn zQPNc%y%SI32z6Y30Bv*4PnBW?CrFhjn9Y;uFf#+OvKdY%Qj!E@BXmp)(|qQJZvVWQ zf5E*Uda+!5J8pZ8iMn(I0H6$|J3-VtGY(LPSJ4C^b!8f!0Q?yQD$dZ>K0eB`n5lF+ z5tDF~1bQ$A`~?@xi=qAsg@}&$i8X4IY#g-6%|tQL^&`lRQyogN8MYMZ96?3jiLk(- zavry&)q-m%G&a3C^NjvFkX&eNHxOF2_%}`T^!1zdjbkl#m#Vr;R_Yk7kUMxk@b`K8 zNbC@6FbGDg~|&H$}x(N=&k|agr5HC zN>$%7q3Larngndlu6(m_+^S6pbiR>X+9#3;x4Ik6IqPl3Koa3t#~w=Uy{+B5Ieo%9tmAz53VFqHam7&x) zu+Oa$E8p=`y;cZ^)f^vJWNl(M$!$M1X{m9Gr=4?;%@mb;|KFo?K-Z zk*i5^pLPDuxk5$*P9?llUdgWxzNd&}hgDUd_em3AY?7b<2?GU0UP<`8iu~Q>nO@x` zW(V0sD+`Z-b7g z{{-Y3Q$#qH2>!&2Z)u*fDYqksVN5x`1b!6X&Dqs9sWO4tMzbpMWJvf3S%pP=lqxD5 z3d7a}97Yd}cs=>%&ZuLS(tzCXOlss@r0{Mq5fuln#BD#I@8`cd#b1#-Sfw4^Ac)Hs zb;MZ0=5X>Xr1p+TQmJQ2c(QC~h*+B-65O0(XQJgw=p5n1h=}GFoN3_V51{eUWGo)a zM*#Hu`5CSCa!=r3NV!&hl6X&}MG-%D1-Ehdb4P>QI=B0)ns_{7?zw|8^{v~9qGn(+ zOVY;n7=qu@xt=CLd9+OAkCX_X0i$c8P``TaUn$&rL0!-BRCt{ zEUjYv)-l%v9pwJLC08R+NozGU%jHw6&x+>G#daqc)#T4H8tQ>9gjz={+90as0Ur>uVqCAqBj%~f?77hzsw?x z#(pqm?4^I8Q5eE5cs3~pcrFIF*d!GN%(AZNi`}s!ls}teR?;IM+-{C>zjy!HisB=_ zy}B?jyP7gQd9keDV_#^T=(k?swljS015bBbwwF0S~s%IyNS@Aq~3 z@YBtVt{Uy)@2bQtX@MVEPziI{by2Pg?Ye2$8h73Kw5NV}n*96kb=t*5Uw=I7VP*T| z*Uj;tbsqJoW71l|I>r@Ls%U4~ZT0U{@LNdw^$h-h9!h@CApk)C`T6}2{CZ0MbS zfdo#!WF?dT`uB?_pm((}akH^AHnFA${$B(q=pbI$4?q9_7!UvervHoU?SD1c24*I- zrj7*GDse!6j(bncV*FMW2;$xPhz_R#n z3AGh%3PKY%Shc)c z(*E5)Uf9C zG=j0)C(-smQKXt*-NR8RUASf#rIpCmiH2@@Aazi*szkk<2+%5>%K>loj@p{0Or~f)}W)$DX_41!TLR_qUsr@_Wlm5O+roxfvpB@<;P~B8`%!A#;nqNoKobH;$E$07x67$lb z7I}Y(+thkN-XjA+-S_TZ4bDu45OL(h*Ar~7cA5DyZQk93Ebv?lE4`Br4oCN62RQ|bpgsob#my3crcoVsYNQdLElO$QXDk7^;1e7Mt8WKQB_}Kk$G1gOWcd$2# zh1E-$=vvB(j@H_;0FW%@zPb{55&-S4-6cRi(BVN_zmVebB0~W8&r79#8?Bv6lgl3# zk(5Ca9*L!(XL@nqu=q2r5-y!SbQIo_-BcEaFpA&`$38Z#rWlbM#ZdZ~q2aF8ODf-= zr8!U*^h{rwA*&@lAsHeE9=x`t&Rv4=%ruGK%{K?iKEv1^NTcRF#R$)MsA@=OKr^gZ zps8bsN1O-inAGm@+%MzknA3L$l(-K8Mt=KiEvnK=%N+wyluZ#ljdJ8iKYOGah3ddi z!rb>z;ob{}*e+Fm^^mz}yg+wCv0*58f}BnGChBeK2TrO?$B!8ml{Go&k)0<=lPn_7 zOwM|gWG88xNU24|SQ6AOsdm|s)@jgR0QsZ*xE?*BV(d?J2K(Kx2onJt1}jmf{DOI! zV--4sG5>@YQ0lvSndNqAL)+#IFGP#^7>J~83u&RsY*b=g(O({4l#u&Y&FcdKt;Ioj zyoIcn`AvLQ8l6U{8b|IGwFyhbbb7KnY|E^4Vw6Q8gZL56P%WvbMtYwTR@uo#KFQN* zl$m@cY?C=CJchZW=DQucFWxv@H>{pwcL897#m)b;*1l7JR@MBswnfp;iY-4h#Z*N= zDFSV{zlOFSIs!Ex4`SK~116Z+I&NO*uR!iFU}@+d(~A|<-5T)Vuqj>S;E;3-jh#pG zY;Mqx8&5glG`hG82#O)6cBW~%P^_`}J*Sze2WOXz7QDHiIeiyi3tC2TL(EZk)&57e z%Q3cL2*$P}=a9faM%P@gsAsTUOtq_oV9=4C(oHKsZvIy2d@lkuL~G!G?|aplH`oBX zEQVy)ON$kI^W;AA?4VBCMj6n%=$<65Z?Z=BpB5!?e&)Iy}X=0kCN9X!;&}z(GZCNZvC%z?#Ny z_-kS?Mhfh)$*HF6mqe$9t;3K;mNQo}HPpCy=p2Du;HvcE!=SWDCuSY4sJ#r=uq`qo z7N%sT?J=+4xq5cNx8qL-dA zOxnHpmjCrb9`?S>p7Wfhckd3%p*GEaR!t1TMoFY#4XFldT(v&H)@#hBmJO0%_H2%1 z{i(-sw5Uzk55r~ahU8o7dPg8&kI7yh zT3-V3Mki1)VoW&4{eu3V@p$nn8F>D8SBLzK$Bh3!;<2-%$$xL`*Xmk!n`|h)uXXyY z`Yw3F8xoOd6~~0GiKIe?*byrRa)6}sjL4cO;v2Q>PrbMDg~soHi`BXF`GX1aDRMvE zc=6gEPpsGAe{gb&ChtzLVtj9P^qvz=ksn4C^5UEBs^9FRwQNwRGA(V}q$>!Hy!Qz0 z45eKK#|#_#qKIjhfg$b2hopxY5fv>%k(1oFMNeaHivYEv&wzz`;IUs|kXU>mcv3fz zkwuZ+?$lD5L|~=ln}y??Wsxz9OtJm?YCc~_!Np4{vZv-4*vg;bT6nW@T4nX;`Ff$} z4XVC->VHkO)!37iE1@PEW68%=(Ud%=){H5T$mR6qD`$y80;qDS<(!~{kxKiWO$Z-| zwndBc%K~92T-Z63%qS(!6F~Fc#DcIn6q9vYNxndrX+qe5Vn>JPJVo_(MbrNKh8ZLiH2(T{i;9uq4 zwxiwtNC%2|D&wysBuuEUG{K*X|3(k|}gP76^$(W+}zW zs*}O9oxK8*H<7{)F-Wx*BmiapV2EZupqVVY$?D&6XNrn)OnZksZOOd z5Z-^XWGgfcrATz$&i+Er^1JH@i5`;Y=fV~!G<4D>9aehQaBdb=&IX(BGL4y)V{96f z-B=7T4I4@s_4sq-OgCA&#Z|eWdeFZsLy&u$?CKG)8xTDf6lAZFcC8eTNw58gykF>N zh;XX9qMqwT3lt?*DnwZE+IO4#H13T9Gm``fV|)2Umt+pSHWGm$X`~7@ynEUM6U4KY z^-eVKc(vc?xqhlAjKkT*7eMjAb$xbC(xLck^99v*F95$gVVz$crl$HR5axp5E&bUw z{pc~T^;*6A1k#0ktH9CHqH8J>M1gyXn$^NXQFot*1rW_nweo5QEhn;tc++O+yrQj3 zJi+^cr$K*oZ~{C|h$8UsPV`+>Is8Hz>TAfzmtr^)v0vml*0z1mf}GZ+NEckDZl{gh zC1{ih+#+gF)X$ll1vl$F_XaQ6j7qEUzOYauvC}~CZ0VSzD;A6`m}5>O)IPS+>WwZf z;R+j<+-8kgi+rf`N@Df_ssQL`#gQ#O@aq>B8jfmfTNwgqS-7-Gw{eQi%KBJAlhcuf zo1`{4jD79E#zuHGxN+-W2DLY;HO!KT(y=C&=84Apom`ybMb`R8Ep8`5;a2|$xnt{y z8jHN8pETOHcp3?Xt43e$;t2rI0ug4_Ub~BGACvR%hro-O4q$ngdu?IHbq>yG)4huI z!t&+UcaMyy`&@)G5RE2LZ2VAlvr%jF+DcX&I3 zZ@Gpw{{9Wt=!Ate`Z#wg`HflJ*J`Eh;r}YAx*2X6Pr?{*0>2x6aAOGpFa7D)+74g2B>mjSQ^;P%V1RI@@V@gNI23B#u zxm$d=DZ~BoL$BVfkQ@nN{gn=zd_;i9)6T}&-t%(i{o$_$P`i58#fY;PnHHmwzmX(U@tj>d{NFY8fg5gxC)znk?0 z+0$5I*KEkS&B)nRw%3Afo;G85A3QKqh62F`VR%PqlOWfek!)=q_D6sRNnaL+#aj^n zp4inJoAqV7)%0>Ot4Qa_>(>ZekUOn#iP5r?i_;>jDOT>aj-Ma2@(-I8$c>XUhnIX& zPn)Ut9#?SsF+&O$DSrfH2%N4|2JBghf|Ch;+Q-?|t;Nf-BA2@id4TcZF1Q&Md;@#z z<&P}S4%-mL^tWo03ypGF?JFc`@L$-GJPHFN!M1?6MHDW_2kutu5f~P@v2vUAQq*ts zRyGr8PTS41C_}=u^#JZ;2?0Y!<%$4xeY2r)s7GREuCfnkh=!*DIGZh zMzb76DBBbz(LYzX;RnDJJr5Y8xiUyDb|Tw|OD;aMa1e4!=YR~yI(fg?FT>zGSQXT_ z$8xGKYhgvGl7X zKIl1wC4CYGIof%QBa#m2MY}NiCH(Adc=_8uSWh=|*X#7BX0HY{jdlqB){&UNIG>AJ zUl4b&)60W#LOAL2c!}_eIW*Zf_r8(bNd@6Jc@g4Si%Xi)lP|^Ut&l+fg-Hy)=d5VNtEd;XHU)z z3ou+|q|y!FkZqic=dvlKA0*A(qXKF29e#59fSrYH!c6cG^M>Ge4g7IO(l|`8;j~}; zNdsJGGw2Eypood3ndyq#?~>kib9T~a&5+t9o!=mO$lcYLsD8^YWc@7Ik~GIQhU5Bw z`5HiG8E?FX7NX4}rZXW_n-_2>S)m9C!3Ao;b1e)-nWSk4qm2Q2)=|6?x&c0Sfhg(# ztLi5KJ-)KojThZK;YAi@JyUE0j+AVWpc@v9k7LIqzI<9dtacG*@WJ6haPGk^4sL`4}7r|yq8q@3HCx-aUHz+j&+k_S$#SCd+neU(?SZC(>32PIf z!vva98pin#5>7)P^%RMCbNO^QV}QQhLb6=MVmPhS`RrN?Xr8Jq=1ZdurS(53hvf#e zUV-Bw^>%cRKL5>ql4p%J2)=$)?~0-Ss)&~8pwNVQq=AeAI=}-JV`h-Z zV3%?6gK=mGV;nU`!Xebg=`5feJ_Kr&=FQ;N$kkotDDi-Yo<|TUCi4}Dh3x|<;Z#lS z-V)Y$cE%rkrA}!%N#ussW@ufD8Ft#AtS^{Xz2s|T^$U!L z&b>v`GLZ@kuEzeLNX7v>oFVxU=$=cP30-VJ40yrWSw?%OMTbE*9xkWmdWhk)@o>!0 z*tTF;npq?luA41)i)U!Jx^3cb{cPbr-6;H=W@R;f>M@hGM&5D3gA@U*7yAVf_wIS`htsOLHn%oxO7>G)WHK70({Y!59f|zv(_Zb-Z*X2qar70DoXz4*ur(Qe`8oi9$F3dOy(3Mu2tlVqWH75gT z42c_YY>(`vCoCmd7oXl!EY5FHyhd2ksGT?~Q1aKfg-pKmBiVS$()tavPFMVzX|g&E z!)W%P|8QX*MwTS5Kga z2ZFhOy7teR_i#>4v}2kh^g7mIj=Xmo0)fikxxogh{(T#z0d{eFqJEoP`yezjbPRu- zzK}0p1cCx|vj6gNQ4fcvb~KF$c;jebZ^8o(VNM#4SL7Txm=F&N;?WGS%{QERlHak- zBv}nuBJa>&55%3Wu*~asY7@^8&0kHRykR(%_@gs8M#WmXU}h|&KBPBNhhlMnD{eA6 zI1uft*`+)IcN8d!kk$FrAYEA9-nOZbZ*w@GJftfVhmxu!{RP@4{{`Brd~@M@V4AAK zNc`OZf9+xEQ%KW5r@m+|AhiSGy@)(bQjd3eZh?aiD@^~`)~}}lfIwokWq*cA={~b}QeO;eVL2Z8v+E=WJq&S!1rz{4Mj%2*n+zSYN67RgR9<&_0f9FdL>> zPrXSQdZ>yYTe1^PjOT!;${}v)rdoi)nrna1Isw{dHc0hX;GS6<#JAe8xG;-{f4Dj6 z*1RQ)V%v`jar*Z)>3$kmpFX7!jA}vH#MT{;;hOHZ53aUwbv-~}IFjmK(DN95X5(QGk6$(%-O=^q9^$S9lLzH!->WPs|c81h;1o0I9!WCu_&!y@<|G4}B9#3CdK2 zUb<9@T5pwJ$P&k97cKClv~q@&ov;K8GV6x>nbC0v+8A%yQ`|}r z^DQy>c0RYRq81nba1wI0Xl+D~)Bn(0kx5@lDNLY*yA?j}-VB)$slSm(%7-)kLn%!W z#PTnv6jOA=EGVZ-h*JxmaQ2nWE=U4Y#C_*1p;(AOYzLT#Hy~|JCMG>u)Gqh#pOF*` z(EJ&Ri_F;5&IobpryjLJA@p=9Qvj}Q(PXU6$kxBW!NE$6O(dOYDgq0h1(JjJqVGM5 z=EUfP32EJaUvLOlVk=$Bn=~N6ar{LS@(Hq`Fe|`T;h#97Zma>hLIZcvW+U694bcR~ zM4HzkLDYfYW^=UBIghT%Qss1Y1chL)BRHGQ)IH~O@&eh>9(rhfn^glr-y{LgcqE>i8dXd*U*1~?K?cZUHq?mg<$?H9*eR$UAeKWcl z(GvVgaJkZNXQesfplOP@poC*p2N}0EA?+OdF36z*#5Vn%8q2F!qfw0rTA`+4JK8k8 z*CJR_eUk!I$`jJ-eKu zp{1MI^2NhLenX^;s_Hj%>qO$!OJ*ILFysTaRls@$jm({%DKUN0uHI=&u=%3k<{Tdr zSW`Jna&6&V?hV&KvWSmQx*&twI+%6`U8huoZEwqaX(L10v72SB1*;{DN$xjvoU{MmHJ$!v>h)iQZEP#24e^Igzu{^{7b~@ZU_FTm#w~+BbUlMBCK4?z zHMQCV77S8BLo={&JNln*Pdra(K*n{KztW`QSTe89uTNR`?{eE&GiR%tc%FP6H$|K9 z=-*XvOc$xolMbVe9YwXC4u))S^Wg{n9$V1BS6Zre`UFM;?^jcesgot`JZwTA`Y|`v zka-{EsraGM>2z)}D?9IU&sjjNtDa?&eq|2+U+4ot_7L68sb=Ln1H4 zAHnEru(-{B<~^*~uXeI<@)-H^#<8|Iu~=p0_b*q)4l5i2IXm{ju~=btOHf;fycV#W z89q;A4_wX>aW7^5sh(?f%ouryP|>(qVdXwvF7~#R;oXmv`xv0^l9{2`P>DYDZ_yO@Szw28yavesNwW@N065;QWRb0UHIMZE$8%;Xd&FVT)o1f0mO_PnU< zjCEPIQTKEsuP*fOV1?P^yago1(Uy1er(-g53lMKn7f30{w%B- z`TUt*Rr2wvsk0k;zT}RGH_|fn^6~Wa_I$AmVvo+8fAMjyQDyq4#Emp4#^8<05DOeu zywi<(E4!yd4ud1fQ3uz$~}yL2Cc zuCPaZ@JX{o7_=a|KAsC(o-CseOGqb+CcCHlFLO-;hWW}H3_J_CL!}5a9mNGb-8K9c ze+LmkUkpY~|0Ee9#ucQM`b^yEAQ6)^*8ao=|0qA8^ZpEsqIkB#Xm_JLw8esw#2&+r4g;-(V=;3g)Db8`SgC3B#`c z=ZrjU>0c-yjp3(@oP_3oKHO}qUg^9D;gzooL-dq$6RKC2o_7M}ZPfW1!{`rNWN(^! zaLBH8(x1AWSt>N-0M7(CQ7z4$0xaSYt-(Mg7TCiE>cU2dUf?s_aCv?9v*ZUa2lo@( zdICQI;Tb#bevsHGbXhVOQ3J+kEtE=#$UI+Dqj5x%YY4Y|lj+n4H20Y62n_x{ar?>? zvS!2$IVLZHm|Bw4p%X4SlnSX716BV1j_sww`j&F!%~=*^g#^*bTIuqJ8_wct+2joS0r`dR{RbkN8776d?Ie`~fmZ0AX* z019kzIXTBL+1@`mg|=QxqSGqlLE^5EtWdSyBs}Hwnui?3j^ttqlPQ(4OuD1t-<1Oz zOrVK3MGN_b&=70L2u}GBGRpnXWbxk<#R`{!caWv!u~XQe$PVs}PRsUIZ!AtrrEVOT z6@{eQh0^M$)Z-fqawHpCTs#GZ{}A*LuCL(z zynNZN6>=@MWGsnXwUk0SVS91ckQ3f~5~0 zPd`(C=~?K>r#f3N73&@P>gglyDwV1zA_W^^jQ|i|h;vS%D#T&G&qt(3P!Hlzc%@$v zMAbiD&wrpyR;0Lg16n&DiIXSmO2pFlE9B&rfLZEAdFMg64JKk41aKdmd+xhblvsjj ze%d-n;?c%G?MLYaD$Pod18Z3tU=c(>_cC0fjbG$)>x0siS9ln6B}zSl?mImpBo)A7 zAVz5^l8}5mJ*_GkQwE4|m9|H0pz%;aYA(h)u4Y5F?aQUB^Zd5$pUO40Xn!hL8Zx&2 zH}f||gV;_vaSS#_g2>ZXwD|24PV@-$D+42rAyX{~{`;TLYwfqxXW?#;Fx$6!b8sX7 zYKc2#N)!^>Ooh;#3i>_5I>l95UGAr0h&m z8aWT5+0G{&ZX2#jv}3d|9IOhpj>g3ROm9Y{a*<2S1t6bd;J3oWWSi&GRhR7X4vl{> z?LaZ#OE1_H?!i4+YY42iG7-YkHa=svv=TAh$kO4)?VNzT9<#N#f%nn(!sv=O6UKeJ zZJQ&DDqy$x)N_D@1G%C>QCml;L7%69_(OjPN0Y_izGLgDTL}sel4*}!p460S>aPlQ zO~6}G7VqKfR11%*-oOQtDVHxGwI(i(>dF-d+i9V(Xsgd0q~^12D@6H%%|$8Oqk9A> z>ROl3xnX<)gHB06<4Eb=i$;00Ad8KE9eZ8tiUzqQ)`qiLh%&y;LgPar!^oAurWdx9 z4%3XVWgRN1cI2vP@P%as4g>+j+i{dHq$UA`8jjxa+OPqXM)-|C4e0v2pY+lG&ttj@ zurdr;&ib}8OIkZ`j2(i3dO;IbW0WWetQ12$x&LC>eMHDxJ4z}t|6N(oPAeCcY+|?6 zeQ;kl3HM4VH)|~2l4g7pxT-!$9T(57jBC=Tyr4rA2Gi4cyhGL*CiZBr5Q;x4M7I*zTV zoS9_Lms*+Y>goHJ&Cv3o+Ix`Bsd8%`Ev_*t7NPb-My^w-bxZ=4T7%67y6EfKS-Q~d zlA~?8dH!WYJXp(iK@t*i{f-~hej8=8rIecEc7Hx4-5nJl&QMCjL%WP}d|vFxK1A+u zzIYaQS8YmecubxI^)`TYC0f73{P{vos<)P$=MC$%X%JJ^_z?7wU?VN4*>6eUSK!t`QxuLA*$5+029f%2 zkp6wuy{7wrPp1S?W-avx2LMoJ1^__$ug-w2le2-XGp(zEwTp?<{}FU+Ic;#D_|DeJ z=VWXfm;EJ_pkD-Y#UX&@22rDY)5IsnQUNth~&dwd&AizkC=8QKceY9JSqJ7*jnVUo1vDO9K|_7n9zST)(7mJ)I#m;tom7zG9M zJ?mT*fx`SMNkLN(xyi65a^ZAwOI81UCb=Yq6VOprAiypFl5wsFMDkfN4Oo6j2q-MK z+j~kjWzjo$Z}5xsLrnWVj#-d=sfod@VdUO2)+(u&yC_L7haZ%92z*s7osYOTw@?za z;&?Hc>qoi)9>!v?VLezc_;Yg`c4BnxrdMp7-M?vseCV|(UJ)M^yHjhXH@ zFxyU=GY4Q}4gfsm5SsA(Le{(anOBtm+eXPuNWZLPj+hbYM}CjRlk`%%sA*Z$#>jWd zeALZEJTUNBAqRxNqus`clRj4@0s3s%+NwV91UN~GbM`V!4_8BFbcPXcNeRr50`Q~0 z?+p(`8X;ad0&4rBcaC|~WqNbMAvjF>7%+PgBAdK9Ctws{^Pz|_9!YvP=lv<1MtT2l z75$&$A!MC0_kOc5FqB|bR85^cW?}@E9vx<_mI*Imfdn#L(}|%&mb8!Iy=0-HKQ7hp z$Rr?E#3!m!7G?7Xhk&c}xeBOxyvUtC2GWXIil5Z#G9MmnDD+bYY{i2lk49~5M|z`1 zaoSFBzza9(sXs1sjj}`{FrV-slI#4!M=kJxh$vSM*uXZo=x!L&e6^TV15r4gg#e?5 znIOQwdL;(Fg-LS3%FCFx?|ADV#ddR<9bGMzhaMHmU=cxTTHR@Cidy~paWK-a2g~(l zIVgs_2gl=RSv3Bx)^cTix=MMC#mqARtmFd9VJUTSOu_MQstH_rhhQ*cDUnqtiRxBW zP&G%NmeA^BkK*_mj~LfKy$8ojJUap5^qC%*HdJ4d+NACAiMz!GMJeqSP05ryKEQn*@c#-<==lr0!0bw3+|rl44< z2*=O2EJnQ6sMhl7t!e^8bK!H#DMUGCd-)BpTsU@=2W$&454 z(K%|PSY>$XZmx80>?52TA`A6tL47jOu&n>Oqza|5&ND%R9bzaAa{h-w3?45Y;hGx zGx|Kz;;_mDUh2SF&VHNqENXYxuEB2SXc>IGUd)vPTVW&tWZ*^9fya60OhJ!}jzbV^ z0RNXClj`>B-y!T9l%2$_8DMoI9+2XshQN3`UP5gY%pQ9C^_5?4^`6-MR9y^2L)cyV zi~5HslxH!5#}gOMqw#oYi#(=56iVp}S2V}^qA5J3UiQOf?9`)F%_1ANjg!625;h<4 zXKP&UAo-{jkN&v#!uDHsBE-cAg8^TQ#(CxA z^Xw!qqfv@_8OJH?b&gh1lY?#+{W+{p4@m7Tln)CZy4kC;5yR#njG!eWt5t}3)RFAF&$ z(HFo5$e2en<8a_XSb!JCHdKpFrlPgbzfop3M|1IE&TI~fNw3e^WvnHy5wk;!mWM2S za(2+6d#H}7J8*n6b-wqMY?qXu{xQ6Nn%wWKPZ*D6?cln{HDx&-ur}Vm)7KX+_Kz-q zmu|MV4AQX(t}m=XZ~0@L2rK0+gtapeTOC|zhLnUAukZRsmvDus!KH{(zQ;|Eo1=b77 zJR)Lwu8@DhHKyRlC#<+Oq3N$xSPehu(EhbI&uX06+nF_6?7z0`zO1urQ0;%7o({ZV zv*bswMT~@I^BJ2Qa38?oB-6^3N(lFuyJAV-+86iUlMni?tHWv%={Ox*q+o)Woa;FB7^vvq9rOdfTD1_im)@oF+79z zMCN*ZeZ3)wg3Q}PHL44KU_R(|@m}=~^m#JG?wa}BZ*1o(POP0r9LEizT@$EayHR6J zB-`s)CY{LUn~U@T$kR^4gbs}1La9qBjh%-D>H8n?hKLqv77NVoz6Ew~`IsdiSvrBo z-Onhy52a~;t@LL)v4NT0t0bxuK_$lQ)5rljEp8z@TQt}-sFN(KG6qH+TF?CB7=%|O z?qyx`@okV$i32Xs30J(K%uH~+0AI~p{dum&Jx^es+WC15Z2|M_;!VNbdD~CE(kXES z(uQ+Fw5wO6LJ&fC;9;;1kH^Y|VL;K#o+e-+K6%Ihw<;lT`}zMw&fV9I6jRlFIS7VE z+FS+6Dh(2CMSJtdole7*1rn(`_R0ZGXJo5MX^PjV$btwN!w|~|ZJ8Ko_|nD5)ext2 zg;+X+&&Tb`$kmQ(rI6`R{%H&1ve8f=xT&@{_?O% z2>|C$plgf0$#_}?fs%nT;=j;kc}XI7a)Gi}C}aI;kZWh9cM$?aZj%hKaEYk$*EDM| za7=2fsavT+P3i1IX=l$wos`8!saH&6=@DWpM;|%q@Z1ntPHX^T8ZdyaEx4jgF&cT_ zi!9)~yxI)1R@y4dMlU}#-C1em>NMtgWo(Tqva{ddkXYIJ;*w)O?9g4Szj-v<2ODR5 zc2TNsMmrKRw85@cA@2rv=4C-i*?IbV=H37+q)n|+0gJ2N10V0{DC_2)(8?d!-GpLh9^){~tH6Ma zS#`uUHU+Qt}NLM7nv{fV26kV@60MSTxca+eKg+m)Nr&0m%p5)5jUHQCT*LgjHrPA*(8T#5ML4H0rOn)*94HCb|9uN}P}n zB8Sita=u2OU*C=qmIJSUEq)|b9 zVGV)P>rdc8)NR{UgP~^+%4S5e;0$Z#HGS}2v}q5!V0=ChBvgDp>_WmV>O)u#`ZlW9 zW}Hd#++Bk@X6dhMp79MTRGMjXkL3z;gvg*M?o2P7b*f&mHaU_39XC<-f?ox2;ADRn zc8-R7n0H@l@JRk2d%xqw*I;~0FdwshF%6^P`yfn*_qsd!#m%cTnn+XYby>k99Zm3x zVM{BhtCrphC_08-JECj(uGju;_nqGg^y=`vOv_-%#&5*`LV&-vik!@K$A3jV5|iX* zO~Azc0{x%CHWN_lyyzp4QmT=)a?^hK(IIJJP=!J;r4~HSBuV zYci3jrHB58oT$18o1qS{SIT@>G0l;xrfKhw9BLE#$s5kjzV}?kqz=kU#zsdBH44dcaz)DcLBz}&$ay)m3Q^_5 zNo7ST=i>+zLWT!qMW@`oJ(+mlQ%j~z?jf)q$H&%WV#$P>iR-fx(KNq|4~Nnbjj>L3 zBFADny2*;b@l18e<{O*0hpU_LaRcTd8|>-l#hAJHqQ0SNql2bRskXf%W;wN90M2GF zV81s1+~;Kv8q^~7>&B?>h@(;Zc4XoE(oUzR=abpVRoC8K5~oY+dwVWv%=nee85e|S zE7QIcp1qPx#`&77e|ygTOXQJo4cA=t`u0Q^ovEsM39-aeYUla^KmXXHC4enn2-lEB z6mDo#ND0kQwF=_mipofTb53=dlDXh>PuK0r8rY^z-`O6#9+so0-&tJQPQDBquN&gB7D})OolQ+*^t2-L-faG|gKmZH# z$kQCw$tY}J8c8G#4X%37WYfG8#oC0h-?Tmu>P^w8k*_1E9WB+Q3)vCQUMYI)WVk9R z0JZ&9c;Hd~Yz9c})cR~6Te2GJbC!(qc82l{D;f>BGtNj{)$dheZZ}O0zpEd4sM`!HyT+WXeDkxDg08 zIk{R#iH2o2|CtNN^tj^ee3<8L_mmZcKC?*dIfO5G1j7j?A+K>d7*5U1e6Ww5kc^Ud z^lhO~sdzLIF=oem)RIKlCGGpmf{D9k!A2_OsnBuZOi5qt z>r#JrlTYV#3X8O1)tGCcIc5aRsp|d+Aah^fLSQj0-JqNKrMrY9Ulx&MSp||wEEOpW zT4&FQZ$So%FB)MzjJ*hE#G>J$0G>w=$%~B#^G*JVT6e=?A-cugB0t`{dy$ENG&kO> zCHVY(i&(r!*`iEBIgx*F95nTjMSQ;=sFfva1}tv^ zK3lF-9aIM+{=FDl!wA*#N8H^2mZrMdL6s&?Jr6MwT${}q{YWvnjx(%Sv;4ejkU_dt zLjKY22VDmf2Wk^WVMTHSgvcDvpea7Xk$ZQ755Co7)W-lmBtue)g`WE>{#qP@JQfaD zlKQVw0vk1^i-mvZ`P#1fg5)RGi}>@}r&I6ZN>u0j&NA+1Z^?(S>dqho(NR8DZS2EU zrZIU!{Wgu6_Uf>p@Zt4*nd()e3{qA2tAko}Aj9+T8wOIM6wPRpc;MkGKLkiAAq80k zfo`phtC{fJ{UPy`cJ_p1Q<*Zz)Qxs}2krvf+IoX^ZUvtNfr78Oh`?6aUZ}Z@XBlN& zgWbFg^7W~FBTzw%O6tj`TUIgqdODw8gGSd5-_`<>%c3krLCJ;EMm3G|l%Ce1ikF?s z1xGJ(W<|G9=<3qm3KUu5Zq`=}o8l(SN6ts z%I0q8A;7YfgR3dwvIwQx#ELXYp^^#uEFQEEe)nefid2*rzAy=87r3W5A2~^CQqYK+ zI!VTr?CfLEJ+SIDByNCSPHQ9nnOdseb9h-wZ%dZd!9{?)WteqEnxE$VXw9#bC1%rV zk9XI2(+U1WdmIn~_>4M6_4+swM}IJI7%laOK(m4_*|RbP4dmu`o_>qy3SjrI^W-If73~a$!A|vzIF$Ihaiz0+G0AMGn5&HkRHl)~%Pc8F}E? zNNc;QlYFXB!1k@aW1u8#2DM{agKw~H8m0-Dl2vxodBDrnc&lKioIHcy)I zaC|-on;3vG3^7SbF$TlT;*^xt>e(tH7MGHPJXxUD#%7u5)8#oTbkrs$q&i6OvJ4f; zV@7UBh)Q-hfq|n{X;p)5?IQ{7x`0S=uFfQ;2cR(8HW=u&QYE`c?)o2;={mI(V$~gP zy+!_lbi-n9VdJh7`Ree@FRDyTqo z07rH_NWj-nRx`_uw(0vj!ge&ZR)jcHGP2Lewx-t?~WN1){+mL+=M52(yINM6=eLc zyhWsyqTnHolnJ3)D%{Iw5U(7U4ew}+nq_LB6Q?);6n)W-Shrk z>;FmbbkCjExe~swx&vu7Y?8{Q#wMn&woHw5N9z(##%g_<5?bF6@})-hLP#3POo!bX zzu#Tk$a3skHz}`Y_*>IwVh6Qzh%)q{#yvn@OeLIzHyR-D=hh$1Pu|957 zZw>63I}M<_AGg2rPm=Fw-QN)y{7#Md>DLiOQ!~eJL;;RAW!ZF2r9$ z-wMx~ik>iuKiwl-viCv zDX$?Y*f-SR{{*98U=yq?0dEniZ>hEDYVv*uHrGAgQ=^z%p0gWcBz($va$&*x#TQhq)AX&vnH};Cjo)d$$q5kW z1h|;>tpq6D8P(vM9<59>IJ^-;reU8!qYuxzQhcdgl@zp>Io?aBZ*9;l7hE3~@WzjY zpsD6s6rZ&T8F{%>2>_c8hSe^Vq@O+Pc4A$_(6aq1Am1bm(NBtwSMgp%RNIL~aYtMf z#EwNH0f!`r0y(w%h&eH;lmBepZ6Ww2veeQ@y#@hZ4Yz!cYk%n%9Q~d<`Go3_4R^QU zv^-vP*6|RZ$J>wlmC*fU3aW0NO?2DwkkPH$9=0NaV`>wAkb&k zDx&36{Bj4)<6(*N;qk>X+fHrc8`(FS=9Uv6_jJFC-GO>XrR|b4_ACIEm0Z@<6UxJi zR4Q0%FB%{rmx@Mbv5Ij!PHv(XRlkoy+Pj!9q}r$Z!-G7`6zjVkwS)UEg^(LD3}Am{ z0_nNog{!k4^DuX3P{ei%G4>b(x3A(mtYhs+D)A0PW>9?H!1hfk@dg%1DUlOG8d0eZ z!2g-|C%QhMZqC~M`Ldd|Be7&7$RM>uP8e!TrOPE``+$0zxBogGjl-78eiYl_wnDHb zZ>MC1!FTXh2zwefclqKFh|*~}XqRVa{!M6c5q;tzXcmqFTkT%RmX9QMQ}ivY2%5W} z0^^N+@So72_S5IvTSu2b4PwaOc4hi*o1XjS8TbQ23!}GYf4kfjr1#>7q}mttUWLjP zYxK-j7ThuJnBQccNzrM1STRZIF!}z+=HX-aeKQ!j(wGxYL5$3d3REGXz zRj7m6v|E7(F1y*EUPXaN`^kA+zqIRJSfW&K9E7$Fk3lZOAEzTp>dD5j9YCyq<1mz9 zV|$ht@iyf>Z$ADu<<ZSHE*eiUD-DF-KlSZ(1tFW?K(DoEw_Z>!bbqV8DH31P$6 z<7%*mJ=3(xzNB)}>B-<$eOoswlJkW|)MsG%EzHA1B#vrZdEW^6I#itZN%V$QJcP}p zi*}zR*NNmvt<{E`tLo(k!)j1g?u10kIj|Nt7fPI^A1|knJjjCmmjTc(;$p?pz_F?Y zFf-$v`IlyC>OX;ENl~buGcc= zcmV?xif&7SD%kFB`nz!8d;rmlV@p?2OP&=_KO(m+lYL z*Fetgy|Gtooo@d=P78S^1@GIk;HAnhUv>k-snbR^Jjed8E*(UxzPA z(vZ69b$dj)1#JJqZDy1!*QzIMzp0%CZKmO6qj_3TyeVB--aRg#LvdHz zCyVC`OWPZm{DYt5Z2NO=Trpo+On~}vAJZNutZ3R?&z!p<|4g#IIK90ghX>{IHsy2T zeTA&B@%Lc-Oe4zMk?Vv0wl__EiH7)I6&sT*W@M8ODSZ@2TLMOt0+IqnUTyf7LGF`5 zzb6=e6haz#R&QzV7<&rsd|EeE`3XSI46#(0`JY5KW}QTKYQ03ZaY0@H7p-`2!De;^ z&ZxF?ktjwrSHub#z(L_&W=B;_km27)r4X3kA-1;t9eORqL7skSF;)^m%W?#*A*`ys zsl+8ZsA5)D57P{KDI(fp(cYS@CQFUIIpNTsiV3I=h{>O=%R9h__M=03{mKU^j=VQ$Oq;D8$C)0HK zc35_1YnM}|WDJh;z(cx17T;w>GrS3&O6ro}%u-Ej0`854(3i4saM$i`*IW9u83B9z zYLb%K$5>3L@m>j#2fXBeU!l&odSD3Yz%O};Hm!!6)hE$ThGj=|I&LA<20ODmT){m! zb?M%Q8fESqsB}vw6OCiv*@*p+2t41a)b)G24ee5Mc}{aKJv@yU8ALW;>HeAc3t{gy3t>hLdo4+Ah_U9zJ`JY0aBbP zvV38;Vt5Y@Lw=-0*LXe6Zk##S4ABzecPbfdP*7>;!PG9wHmbo`V$8aY*5!RWRIu!Dq6mh|#iC^0g-RaMf3wqj=chw|taq z`56s0jk#F>frYOs-ho&BU$v$VF)oF4SLCO^94rQOMt{tXc#FVL&>^V^{xOsgR>Vg} znjr}N4A896dE@UodB=|~dQj{p%-f$m==5BiEB2PD*zpj+D9w%mfg_&xSP_G^i>c2% zM#g)}(RGDD!|WP(l$g+KmP1EryPn&zBoSZ83J;W$B3AT z*NKti?V*AcnDdM`hYf|%Q^xF+$;Nl?kbmhID4q*`(Atrq_nD^;N9X;IC+`RM7Cf!3 z5NE~3_?!*}d~UrW9VQX&qy*2dq1TX8K$@Uz@0PxD85PhC*lmV zk2=zW2C;xZ9_?@(H%rKT@hzG`p~frz2*&%#IBBA~CQ=cY#(kh*s44|gTAU2~=n~MJ zQiqhB&!X+;W|C7wq5au9d%`JlebE36=9Nfuy`G^MjKQ z1Z!}9=i#Zf@3Uzp`MVYTta831I?epebre!w&cN&o3z>At_@84jp*-!WG`174CfEhO ziAxOF93{1>;)8|_3k+f$6%GgFAsYCe2;1EOZT$Jo$Q?wtMLuBwgIRl!3>i)g?8F%) zl9THe*Bcp_KSOW@6_}cnRPKU6SNd24T5Z;`t-=ChV7F|yJjUJ3@ow+8kiQ8s5^k55 zkRPLOM6-Bj4I=%Anhh9WZF&JqE#Uj8LD|UN>7PLs*}P4u zw8?fOaemcm^Ow6e#6BJF<_%YlKs?s4m(_P~V`<-r9Hvy= zt+dU=6BCDJ2RIz1jIDE3N>w@iSwN{kn^x9o;n{&yZwjQ?cqp<1oqguz?&y1|nB z;hNU`5o2uG%f&J9VSknGrGr(cD`{3PIyKbA;r99-orrheTD2&lXIKZ+K>YP@7DOx} z+W3e}kbEd|xtU$;uzQ>SxTgeGAOHI@(+CBWu^KtP4Q<(uZg(OGK0hfUkWs_VH|#?r zUi0oLJb5t$cs`!pvC{Kpwjhhfo zBGSL}B!V=H=|vWLhIh0bZam>&j0QnPE|H|JAAY`9Z5^ilp+@?U&|J(ki8AO$)Bzl4 zvlnklgnn~+Qx#yt@ULc6F;?E3sb(}~`4l@wWjn+;GrKwZN4i#1KI>*D(YkOO?Sfpw z!R$p-(HO*O+~5s*9@DFWOVkV=)+h43PI}l^1jgQft(|)`wWYhAYvj?LJ&ORHa6eoyr>Th`w}?#v*pb&8caR5DH$*#wE9opm?Y5tWTkMAMcc-7kBFCU&HY!)%qCc zt*UpC)s=8i7ykTaM5 zL#|NfO??3RUK9=rQN$pq)srCew_kXpZ(8Txc72ytrpcjpkiod29aGWz&^J&wr#h0attGpK7_Znua5kqba z)-@h$)SOJ0q<%eRCz`tJF4-Tn2x zV;7{q{eAaLG296kGruw(h#Y2I1vqEaqRs84Yibj8M>qFRMHF@U@(!FQ8ZV@ zFbmQ(Pac2J?_Dl&MWMy<31 zYhTuF%S~Vwbd%^+fmD^LQM6dI?@KSK<=Rp0etEjc-21QB3?$3s$-5*Xo=r2^^f#2+ zek!yF4bJ!_G7UB3Y)3v6IX1b5a9QqKtT_X`tlp3Ga0P8eVr43)MW)QfH5HQ`J)qWt zJ!Tl<$bJi}wY6PY+eoni^8ykSufTdV_z<$5qOD1HhYPft=*|Hh;{ugUjm$ksR3shg z09&eQjsvqg+cXT9Lqh3S^9Y$L5sD>~wuA312HtDiJ$~xKAxdvD+(v_bhJ*hZ;LUO3|NEJ zL&clWX2fE68>?hcv>Pf2lygyU(dxfw}c7pO`v zS1y9l3RTklilTaNkVFuFYshhsH0@eWC=|XUVZ~S26ogyME40r%Ev5sjVmFkJo0ph7 zr2Ya;RLv8A^Yw3|wYt>>Ryo7$WHh*HTz3K%9rVrpm-!T?Yxob%s@GSjomL{?GC<41 zCk{vjV(%Ql92^gA`(xR$jkC!`XP-ULwA0Ac1+@{Vft%;b1Sxmtfm%g7VK#KK)Ddax zvf?@=-#kjH_Od##D|@d(d~z&tJoD&oS1nY z8foYD00|fUFtF^+;tP94Er4=d(2m4#^N@kq<=1F#(Q5iT1L~1og_93V|9g&Ue@17m z+@U0P;68;W4Y%reO+;)4vbK~?<(H*nZ-!|?TnRcKCnIAA?C^23aqoyK*A)yko*O!1 zKF4ueR&Nfq-X?A><+T(!gsJJhmh*v~Gxk>C8I7QD7mYhx@(nKI&By7QJtkk8*3{SS z1a>x+N|c90GNC4EduiW4n0CVGyf$s0Tt!4f$qmRO*-r-pxNS$JU51Osvi)>{)ETF# z9=Zdui)RaWFO-@eqCmuwuoZvy$fmmoq=$>O%hc~)c=BjXCg?Tcevho#zLiaEl!II3 zy=Wbb*{DS1nZ_M%K%T@`n-5Ig z*)a4$Dr4j|!3J08Z308`_w}pw^e*ykLeOFgU2A(d>DZKzxcJ_Epuxr~62b=WYQi;b z-X^<=J#iF~OzM@|?sdjz^1R4>Rt>xg=6Wk~RP{o-AEymV9!sbVo*dK0o_r8Y#28F~ zTmW0`eODC?*uxq+Z&d6MYHc7C( zyzI=gg0QBTgq1YPqrI80p+4WH0i)pQ$w`~w;&(;ju$AZ@-m%YvmGN5;p6fU?dYbMY ziG73bjS8Gu(}H!N!=>nix*Fe0tQLTcLjVcrVvdk$Y3G>B0fGp5a_}4-WLt5R7v0rZ zTOE{pdi%daS3qpsoi*hcuCz%9=SFJ&lg`y%{RcQ(!U=JAA&{r57^y1^wN&XXm@+%K z_}0Uij7)x;BTIUY1+#u&qEy=@dDQ7PMMSw}Q$f&g9VfI|Gz7EDz5yNIDGD|f3e;!6 z;s-YllF+D@Zgkbk7vjT9O3{+z9hbZMSMq+XF}~$7qUbXQFy3zSdc-=8?WqKe zplV%}PM~Yf*yxjbu}fk36IHf=gU}dOk}{*1!(eQ{y+n6u8Z^s{_h&j->P10RM?3sQm^I9shMRy{!F{ z77JL?5TayLa-uAT6#&hcwqg%?81dx;TTm%Rqp|k{im`2TCPQ4S^rC?ZR{F{Suh#9N z0|Z})K*Aw7wmb%=9`JM5|HELG3%7;e_%Ye|u~~TYW%=UxF8mT_E3|#H4qAsrr}hlF zDU0yVAK^3(gPL~saHflSQxB+@gpJIcJ{E zrR*iRdCko?NBiLC34Hhkugf%f zpM(k5y29%Y0C9)g_Bt&nu~ESWag`U)>)p^Bn4VC?63)S7!~j<*7fd1LrM~~szn2j} zxF{>oW`J5JhfeZ#>(pM2S@wIQZ;?rRg&mgt1v9MYs#1TAcxl?<1iCi{%wT|1+%jMU zuB+OS8*BZv#|O-c?l6Jbpl&xU&l^^N;<#i>%_egMBj8^0>F#W=9ZRpI%z~%!Mjx06 zGKw7b6rfqH8oq7D=5v0PS%ZRGn}2UUBPpp%4>x)FevT$63&wWaMC*%3Y=vD%tvjeT z7@Z;zxEukR-=as%K`O3CsEd5=>bzoF9=hIkeTC32_hb6s*{Q!WeZ0VhK0qHyqf&;UQ(yC-@+}%;AV7;4KbGD)1pGrhHs}e6?c-#4G7G9<8 zJAc|svjqXP*&qn+8IPAF$DA9eiARLqQU&p<}W7lKN^bCl{@?Z@ijW2-jtbdMwYk)~IV<}BR^ zGf$3pH#$c@ib_w|>WLNmRAF&rQ^+rWwxY;sKaS4r+tFw2*ZYF!_uXnUR>K0d`*kt< z6pZ&j>*vs@kF{S74boKmU+<4mPh_dz;zP?cz!&Iu-M{M&a})rR%T?U9O{lt%`vTS{w{>uXuOls+Q$Utetz7Z87i8>FA7Gp_G8TdFcC=+~=m zD8;AMm@O0<;gNaEOZV-)ziiWKbS-1ca$OxRR~26K9A>JFt#40nyJ%l;XZva zT$WAh=;TtotgE`;+aT8NYrU%a2vDtm32YGb-XNR&b8fgDM_ z^qBZ%x}Z|~G&B$|#Rtf}WS7PH!e&aq_{I*`KHpeu&8)aL#_F3kb%fPYh{9=RkH(;B zKeydw{BCkIS94=BHE%;I04!rN6Mp^~Bi&JzSn=U=VWo9UE0z)20FX-M%=4y7{9dB{ zAMQ!5-dtru>1kcY)bjg2ZH}ZSk{nl3PyR+IPGX$f4yFe1Uz2p#pjuca;jktnj3K;t zbj#HxSnkRon=G=V!**-MlLwsfso5qT_^tkTZx#DAI65Huvn}3#gF=EyfXjpoHh_EG z)=xXHhb)v}yleYn!|*&X^~ z!DZ*6{rdgRMJm@a~rPpRUinp}wzkKM_HsALJy!>Q!BB(L`- zCC>e8zQ7MGO~(` zyN*zNF8Lx`O&kw2L8aVvev;roJXl5=B-mY|)()U{N0&U=Brh;tPWVQS;)+qy-UY7- zo@GOt`h|MCWn;4jH2?NZZQ22j7nE8|{Hb^eLvdy;nQ6lXWKqI zHyy7Az0FE`KZ$fl3f}5;a(|Gx&vYGg=0mbHLnf!2*-3ad{4;+B5V67=_O&lpn*E@u zOX4-SwNrBJIGw99B>b$ynz{Qm`bZQZWpf*3Gc?E3;GI#w%Gstiwao7f*8+wGA3Y=T zA3W2l`*H?rx-R;coI~bgg9d9fGgr^FI!;eM2m}!P3Xlxh3`AA3Rfls~E?o#3VF}fc zG2#6K_mI^vk>RvDsvAW`-Fqb$AodlM+ap(Q;kD#sL|5aasj4i|ZA22z?hM}(c3&D! zA!M?eVw@i-&45-t9z$(Z!8z6M-MbdX;nVvO^RgbKt(J;Op=HoKlJh*o?rik$SH6YB zlWRxj_zI~mBaR#f=oUFtftu-c!kWOgD58T)!X;DGKH19^m2MfA?bphZEh9NoZQg6c zu&YJfHi+xK^Vq1%PF--pvuV@VnQ4rewlU_Q4&qZu-9 zd130|`a2|gjIq!P?@xH%Qp;1A_O<~`?DntEf*$u9)gxc~Vu}#i*Js&rG*R1K%@kN^ z(Z>0+EShLYbzt`!p1V~9r#)+mk$!9|VMM-wK#*}8_=01OepB{bEPaBWq>YkW-%~Z? z58pzxxPsRh_t2Xo&cr-7$BG$6!}yRVJ3@`F2APiOL7k+N2*MgHJK;di>c9#E1m^5! zwhm4P-iq!JN@kjJTxP3tqZFh4XJ69j5RS$KiFyVxPwV{MKbry$n)!osl&tze97O|C z?s9y2sU z!;;m^2O}Y*q9A^W4*FQsIX49lEEoV)2*q1d(_8nqXfT5zW${9oS>F~cDs4Qlo=ph& zkq3*&G(C)rUuhQUUM;4?Mmo^b=d1P+HO{zX2t%2Af%e5e=tK3PKK3wZ7*Z~{A(E$s zC|#*X2PWJ*AnhMnYlD&M0)K_Y?XD90=`BRxGnrZ}^ImwMuFi^;iiS|w83p2WG9xC) z4EQpdMqtorJ7bGUeAZlPb`?d3(Ay@+V{jD&*4EHtM^dYq|kY={yEVkZ=3IlJy ztjAc%)J-Dap_BQGR`y*H>j#Nqx^oDs$C!(`JH2A-DV*JZj$MH!j;ItbxM-%_MOU|* zP;Z|$ltPGv28OGgV!tc4=EPhTk4;h@ewguezLo=GyK-W$+*vYh`{8I9QuFe|C#O^1 z*i^CvWD_PA^6st>jmc&1CH5+O8xV+_qM35mH#Aa}V94KqnDvq2EHf&ENw;8W!j?CxS3lVW9{T8$A(r*8}>g zKcdu+&6h{Vh5N!0LmX}8)I1O*=f^|)h9@7 z-W!BfVg#7X9n;V`_8(0NH3hg(KJXk-Vw^ITsa zX4(Ralr5AD>?Ak3mTT?kl1lUhT)>+(eFc7#u=FQ>L;k1eD%fC}m*L0Fp9&TT=*Kzj zf5vkEdnC6=-SEezAL%n&-vMK3P2{(sA<(~B?iqS=-}B2kdQZZ~z*&e;9qW&Xt$OnuqKPY}QLYVx2M zz~$6I;2mcj1JiiEwWVVqu3eH^oq61vn8PD7wK1BAd+OnM6oNhn?Q8%a`&MO(dByB! z!WwLwN&yFlGGq=4+bS$J5(x9c>X;;F3^Y&vF^EBjB;f2h^=IFQN3dSlK1~dp?**sh zc}EwJ?UBlcwJD&uGjN`jitSv9iZ>N5IYf6#6Kxrh0K@I#Y5(cI>?S*)r2opIzp|p5i7YT*yAvZq@p)1%G)(OL8@Y<);8JHL zecavOaDw^Y1&KA|?9rgos;VXSy;g}iC%J5V@px%&<8qZ>P- zA{dAuLtWS@N}*()AUcBbN4^%8f*3sl1?7DRW7zqx+PK*Vtvzkcglt8WOx(0Hn6A5> zpQLSKJbC!Yb^U9SwzWOdO_b$*xyS*h$Tee#h{!OJk@kJ{iN2pC@gzRsb%qJaNN*jE zpwXSS+n)9;9_G%UIX}2B8vn*WBriBDDj2B|mX#K{S+vyiCJv@wIRy{5t7n%F5dq z&PBSQD+n^_0Ww817ch|K7TJ0i3I8_)l-Bgiyxb0t_A_rm{jgO5#hngw5dq)DVi&u- zKlt554T_0CXyWtmZS83k~b@fKa-bOr3{feq$1Uba~ zZLR!-Er!xB8iRPPK__$`fp^hvEd^d;+G@3b_O1!A2$lQ>`Oj0uo8Qz^><1qP_2YJf z`g5w-xtcou7c1<4;k~NVHWk;Iko=BoxIUPAMK91Igy^CNU~r}2%Hu89<_{>Amv*?~ zFF78%k2eb8YWaaKWd)|5-??&}q+O@&LRYV6!2xF5HHHuOG zr4~U`B0BI-G2=0?5E+6qohA1b4N~`{Gw2BRYK;?5?$?XWxp|p(@FS!FUA0DNZVi2b z+Dn}G^_D^Gk8FoS*xPcb*hyYa+hUkuK;y8l`WxDrU{RI8<4AChLyzt{;$_d#wx%c+ zxgss|fD%RL&;v7O9;Y!>a0`mDQlN)7DA`-5egsT+2JGZpHam&L587P`eR4Y+xxo#= z8kv}i?TxN$3BRHX1<8=AqxPP<^UEFAbjMl&tpd~cS~DZ;>5qT@dW(Y^-Klyh_<=SL z=Z`x1#a+6>0($qi<(!zSYvED_o%8H~auMG{5I~w&yu3c;6}0ipO4H~xeJN*RWicm| zo(!3VOA{FeEHL0u-^4w99o>A?eFUdD#63g;;aWMSXIaiGga>kLj=`ZI? z)T~)fG-3CzsAK%Ds$i3?d~#T=_epk-;d=%fambmFY1=@}C}|U8Oe={lvyZeKdxPd< z@_#-P!yB}n{Dc9Q#sWBMaT`y68@zEn^Q^Eo6US0?xVmdPZZ$rx?{FlXw9s2TuCvqi zP9j?omR-dbEqMn}ltK+S88rkeoR6XZbJHEr4`U<$m^Cn<0|EU!|8LX%2MJ8$f5T_| zBN|fC=#|Gs_qeP=7C2CB`m;e~@{KtfsNx#%jZgfx^GYLYG#Nk>K+p}+`FNh4Ug9r0 zxN)EQx+Q%cxIY}jCH(&C={>~#uxFMHV}IB)6FyvhRU9zr^6Z>|VpXI$LidqOtVDd& zI!(HpJtSEd**;npW)%mq;G~S*0W!VjS&#j9iyjP8&+)ix(B3;D;raC*byag=bbP%O#rXH!d}F!|k$aSOzTaKE&kGj&pQy4b znx-$KY>EqCF59ldHAr$f;YQE`vx`e$7?6WzMD>BoL?v+SzF&o_3kVS}1W6v(Dn(~H z1-?Oow}qogG&Vj~NC_H^F#^epOuP42*GT7$<3P()yBQK7JW!sd)aEeaG7M}gEuwU+ zCtAqO{Fpy@83e^@5jH-_|H8{q`ekPWlJ#|s13=JV%h^q|%s_cU4^re4|BEjJ;ko)a z0*&0pPKau-S}suuoQmL0j8dIgmEIsg zQURG`0m7yOjbAX^)N1?`V($^cRHlQxVmE(ldGl}a>0UEt>JuItVDvEZyOg2~VG;B= zgYzQIBl!)}jcWfu>HC-b>CqD3Bu!~&#&F?{cpV%*5t_}QDcJA)CvhU!r(LWj z#RcO~Z8Ld_ggj!>$_Z4{(2C|cylOa(_t7eu?Z0_8JR0$NXDPr&n+Lk?)w$3Q*+hhe zhD3e}q**>|a}CqR1BpFBbjK#eb^y>Vv`C{x-^qg#N9#w-Qvu{Y?90|^9d+F!LNE`5 z!WldET+jD7!NjdH}X3x+8aeZJTs3!uoy#I$eGowkTUNaTa6v_+1~RjNw<~U zHC_$r+Onse*t>;Oc8nU?kUGX1T^&!V1egm*Jy=(hp3Eo;=w|a!QO;%T=zDH&Ms4mJ zn44KDjZXxvLLiEj8oW>Zd`l3)fh*!mOK^MU$%}vMz*UyehBE=LY?SKQxk`e6?xU?1 z6M~Xph^S2U6M1YMY>~14Mitp;5xKKVWsFihlW&>`7+1pz`4J2Kjo(8uP^DG?C)1Ql z-7p#d|Mi2+@5B&_mI_!q70Y0{G~G{_E}E z4z#tIB5dzyS0Ih5aL>9P`>?V+VQUL#U>|OqUbV6-XXaQifAu6ynIx~m7<|A%yM8Bp z&0pZNim6j3xKRD2SYRDW&65eaxtNHtCzCSVFuS@$4Uo9^mEiWA#gC%9MsWeZNZT;U&8b4BkT}>oNc#IR;Y{SsilZU>KRle`LuRAzq#K&Qk zg+_3mZ|JP=kKMZuP(Z_V_WQ}@>!8;kqEh=&m-Rtme*tBKk;_|ytP9lYkV&t$t zvVqLReL_aNpm{`oFyjaypwZme6gp|U^GFqzeB;9j8U{{hkz}=Ky8P_FdTb1kx6<@M z_us~pIfX1N7vaq@b{M^a?oWm4!&5SJpDo@LW*`i+;2#S7%!k}qL z2oiQM0_1|(d1k2W3{xlvcgMyEI4r&D^Whyccgf8Dx3Uwr*NKPvCn@{_tO<~8^|b1kZ=~a75v?TzA2#gx zAC;a79^3gBEPc8x`Ni#$Gl4-(WU+-;C$4v}Pq^;z5vL*2GH>{BK&6ni+>=?{^O(!- zQmzk4S$m0|Fx?R@%$?D2V5Kp^{6@U#97vGoeON-1AVJvT7hB74AK{@0EQ$6IoQ;tF zPia>I4)ykiC$gm?Le`X>>|`t1W#3gO27|GUWr##bsO)5kESbo@7P4h&u|>#|B(jr| zy-WUQ?!DAZME`qwdS;%U&ij7nJKuTFch2w3?+D{Wds+zAT1GX~)*uCHu0k87?Ts!k zn7yH8B+R(deppKmi9Qu+c*^?%_XQ4QWY<3eF&SgD@Ed5>zJ2jqU)sm7Q6a*o9~$37b!=S5JG zyn?GIwVHe1uj>$Fzth1QK}~^90)3h` z)sQKpj_1N9rCgX9<~%9I{t%K$c-j9(bzviefF#F9eVcy1?jv`mqg8RpN5jY(LzVUo z%Ux708Ts-cF@y!7kl#NmhwC^E!+r z1y;*-0vW4o$Z*fkx%3P(oF$wksl|u$Wp`vgE%4Tl+SL=*oEOgV?TgCpaTZMtp;q-wa)iF(sk=d{GvoX<+F+7PgsFS#dY>+NjszGr!=eN{s2njZ1z$r;1F-GQNQHEFd$rncSXakZ*7HszgOJ`sn??H@iPi=YZG zI$}NI>8pJoSJZ@F;e%>+-TBg~hOj|0>4PlR2j8dE-$_cNQ|{cKy~Y#Q9W$2FAL-Do z{Ykpxss+i{c7ykaM+NngY@D2fwchO2_OeuHDs-;xc_J2xpLr^6$n%TceV(NrmM5Ky z>0#MHe*B?1?ax+2*?L-{o~Sp3Yf3*Q9E;VnGcEcorJ^ksnD=NpkuvFK=fm0*k` zht_@E@+d09#jS% zF45ep?-h~U1Lstp-jYtaqI3|N!7g@`+VSK99fzX5hK*w|n>fwwk;JG;PrIt{n~!_N z_T{xdpc@j=%&6|Ql!R(@Wg*%TR|a24-M>Wc^)A=qIKiXD=D}H!@JAQy@L(k=`}Y%# zDzWI9DcM?Fly{rF_wFP&PcII@20pgMXGL-#l+M|_a~BqBDN}rR=3hwuBFC9Y12>p zGBcj&MiZ(5nj131Ir^BaX~^bm zdEb(8+}qn-xf9t*f_HTY7-9mRAfBvwEp<%Bz3B9LAw8dA{LQ@N%A>wP`xdj-v2u#; zZl?EniWQ;AdJi$7stld?Px=Nrr9W4&N4a!Kh)>?5SV4{#&7UD7S`TenXux_Aplk>$ z{soW^F`}*RbYa%nG@Ur$z$yttX-piU5d1Fj)LhXrHt#wTxMP7i)R!0w=1=%iHz|8;nU=2+qBacbC01r>j*XO=&uoktR2=f|OF^w9aMp zcQj8bd-$|3VxMIGo<`{POwzddlusw2nRj~Ns;uo-t{dGzJY+a>cfQKK@l^0as@a*A znmO^rJ3K57cqWS8#5sNGCsQ(HGidyw3b2N=sW%i-#}zdi1EYuwr7tc`Q%XZ(9H#2- zWwhdTWT|@d!8#DWYy7gTvlS<)=p@!C4PvOZN}2NtT?CvKLn4pTN#?jp9u4j%(@!nX zpNQ5m9+5F$4k*GeD%{^-oiMNZ_(Q~(g9!K91byum;>N;3;kSo|O3Q28)+yg!jv0UU z*n-JIvQC9gNVZW-KeQ>v^1!%;d*tfq>V=C7*W@oNCqLv1?Vge7C5zK9)+PU|s9f68 z9g=%Ar#~*MXwWZBsFGE13E5d$HBE)N&oF2NXUhV6<(%MNd-R(uoRRLfFlQ?m49R2W z?EHoX?o3;-EL}`}PCA+Hg&I32`+{l~#S10CiONk>ME`6BxjTi}9_c)sS ztP(|+-f=eayc^{Gjmv}3)=T%RKN1)TiB0zCC~1~3`K82IrWVDd9zA;Yb`x36bjoqz zNEHP>v2R>Ci)u}6DctL;_gOAFTRFGiv%7oO#S(9Y#7k6lF4to=CjPZm|6z^s?nAyw z!mrNQ_l;^&MOJXcpZv><$;-Yz8;AQ`Q`U&KuV`%p{<-3-jh8C$E2`<*Jd7^$J|EXp zaTHTPoL!MR(lUF*g^z#Ytcpn2XoweU@9NccrCQE+_V9Zx_6o)dqFnGb#~_DADT&6j zDW&Gasik!R!ZKEbL1sO?@zqdZ#z(O_dzFh@--nBTmr3{GyM!>%()A^sr~PsGpA7s3O^!D z8$D-jO|5=$+$@UEK7<-piYn`IWSBY6X`ba^osz~=n^Be^9%Y@0x_4Xzs^JkqQA~b# zE{)NwzNh5%;ebz$T5VS(pe>SfLHbW~h4!n)sJi;uUp^79(W>-ph2zN3iM5dFtJM4z z{-QMo_50>qCVVoSjCt4`()z@43za0F)Jckp!o*3-FHOZhW5;!MyqMf)vEJ$A*&y0A zD|NC(#rM(W!0IBGI@gGY?K7%^FX-VqCqsKfDDg7H9&lU|Ay}mB?!$e*c!iy|SwOmR zFwsorc9~7x^Y^0DbADZTD|^556Nh*?aX*NUHovXH6MR3tE6TjCa6;Lj7J0x_S~Y4t zQz{*w^X-Be8ze)@f!E6(|Gs}(3R5yoFO-kaRfjLpoHMqChexR4tZSR{^yv@bl(sQM z?jA0ZjZB6ju$29S^j^t^H6la%4HTZEzE+>+`Q65|=G2dC76T|FReI^WWCQd`3z@qeyF4!?DHSG@s2>pwj1-pAPq~Eo2u+q3>ZR65froU}%?Y=$lA)BUH)0XR5APe*VIv zl!G4?m1><^*2v1ry8^~~GxjrZ`1H{X3W}uz<3V$` zx-aT5#C8#s<_1=LjdC|gFsf^nVhE6%CD-^M;Y-{`$V+untu8Q!iF=qK7=zwb<#|~dNIBt`hJMCRjI+ezc@K) zKPS=NL@DBVPkyf4hUw?s5`HQ|WnT}>ktK6^j;-v2#;l8|K41CTh-24M$$udE{4%>A zHP02rw(!(?4F___K*r!x8xaQw#&`1e3v{tr)3H5a74;XFT3`9hTHwF)lZ+foUA^q6>^94vNOqSD?^Xb>>xl6;%a$CI-ts2_#{$_3_{e6c zLx^4b)5Vrr5BNx2c2`*%64#1ySihrqC_6$u?;LZ8)+;s|^3%;_?RyJ)qV%7bRDG;w zD>SBqRg2bo9lE_9@#y90rFT9L4Ds~Sz0>b9uW=w8iKzH)6(i~j$`_HjDr>&39u9p$ zT@`T=Rq}~1r4Ct$X+A3wB^{Kjrzbznc=FTTXs=OSZYFgBUaRJ`?m`ksIlL10`?2%G z!=(dmQv?loUWfZ6pL&a@-Wjk}_I?(8B53_1-*x~D7!mL!CE7tTi zqJX_+TsMV&aAro`!^bFjMi-YWC}<{vSMi2yO8I-a$0>BTi|K7e98?rfBh4oo4JN0W z@9uL+A6=CwF zq{vf>rpW4SLQU?7_lqx&^|YmgsUy`?<)3NCN04Q#6!Ts6c|{)mxx2;eHd9{M%}kfU z>v}VUA2`j7tV<0|G~`@EOR57TGBQi0SoJ5b%*nu_i`&!N#pS*H*VpoNhmbrm0|v)h zNt4zF@k)}Ks${-M$IRz^?3N;#JxnWQxOAaE!dKsoR`PkQqnq);MC+SogBa?azg`_&b7EOrFCjYn<1PlPYeB|cug#Z9m( zKK?Eo!Z2bZ!cb6a!fC52kE0_a!_iO>dihj!2~)tpboYcKT!rpXhqkg2|Yv31LFNrlJ1_~`a|MlWkhUoy9+h3BsW&##(K zxwcjXWe24=?q8V630S$`(4QOA@#(wW=khMg5|1L&iy{nJ&&}Z>j z{_h7My|!m(5@N4rc@q0C=Q`Ry>5JZ5$yS^&L6W!})2h>(tld|dk_T7njqE5DU(AS^ zA$qIa^(?~Wv}Rg??gJcyo;-&q_)mtb6K_;Lv(XM2nfs8IBPmtd$w;j`Fvy9|@A)8_ za(FFq7&(XPmZn)He6vccZ18SWg82QAGKoR$K|5~6)q6SV?XRs{ZR&II^Xc6#b%%P( z$7gu$KXE{wkXul6z)okD#1AT%u_)0jd~vnd#_>fR1zwDeNZ3oU_O|5~k+JC6`09E) z4h7CKQIliAON2Iw#`t;VvPt4{_vEMtuIb$Lcl}YzYBVW z@lPRze3C|gQz>;Qt91_Ghls1H6- zKBM4Ye}b?_SU5rLV1FNR`59ynmIFYoe+$|HvcMl^2s@0nW7j~h1^y}M8kYaoa0b`A zJ0lVHFpRp`?xx(R3PIPs^GjVciUZ8)w-^i<7){$^K+T~J$Str0LmoOgg2nAeIS52@ z6ASG^A`m0!*8gbBhCLft(x%QxC#V@x!3k<<2eU`&0DGnX;rYeW=(g`;4P-0?M~*I^ zf1@P4G!FO`Ir*5*_V}PB>4}q{@VBZnn4qL_+VP*rjw?t!a882v@4QV>M z35p&oVIW$!HW+RXqTC*8``baojse!XGnfSoW^M{Kv-t(}O%1ZB1Pqi<;PTPGrSxB9 zln<5sjwWuR$|8W(_DBV&ISlO#FyabyVOPO!*axgA0ah4bSh42#=T`jN|4QiC z1$z1ib~LMfR*M8<#1D);V9I7e`Dl9X-i{6F*k1TF+49*M@gb1nV-N^X@6Cepk?{U= zp5I9zIqKmzUm$_aK(0Z2Z5EV|aloH5Z7YWIu_uRXz+AMb41q{(lAy+67x$moWD#}_2xmBQYmpco)X5kI5B8xzk`*y%H6Q#ZS_KfL2q*U~ zRr`f}5@Q}18rDD(fwFEEluuml?n!l_rn{B|G{wd|!Mw?bo+mQPc4R~Xh5bd=8C=)0 z#z0oYs)`-%GrL6G$c_GukZpDRJE5C+A@G*A1Oy_wX*)U&d0*_#b~yxc!&BR`1h_~v zDg%~!VQdzW_T90lgJgE6w&-Q@vkHKY&;mk%ncsE4+a1f!Ld0!n6ZjU$0|5LWdR2boxl#=nq4AF+r#btP9gLF{+7bHp8&6n6ma0O zU~@b@ygREkOs!#NNN2R@L;tyl3_Hh{)!iR*5V$66FE$4=!|phC9)c75R(RvV;4lM= z6wE&M2Tw|ONX!5B{szbIhTtOKzQ+Tg6|cbV0_G6B%=%|6+YZ67M`5|;phITCf(Wcz zHVeupgKKvbx?s7nZGWTjHlmbu6C6D)6$$SMe;ICW2}5E`Oz5qLAg@mi$w2x9u41dk z%C0eG5w-{?boTp`fcz4$XtIx&6r1S51_Sga_AI!lx!VSS*$G^n_d6!^YUW&VA&v*| z)p-K!kg`MUmT4oXwohWM@4dBt4;WC#v6E#}E62bCk5t z|I4v+H_?%WCC31LnF0EujKNxV{lBvQuG>6ryiO(ryxJ7l*>dU4|H`{%H=@T_z;klN zaFC+MfW%b~qp>WZ|1T+euGWNF!gPOh?RIlE+Jwsc)NbhGzgBGEmTC+WIAZ^c31AyT z8)=JW$d*Dw5kJtM3WVl?KLjF$VZrs3-CCdxx3oh3Sjfw`fQ7yNFKlSBIcRDs)d3{H zJqddg&+qAO+0ZTqnaY;s8G8G5zD8;Om3mPNBW$v?YsMzr$lVSdB-5 z1tca59S+V;v+%#O3p&?A?>#`__S>)sYuyes1+gImCNN9|=wtf-SOBLI5@x3g&QQU& zV`c3*;35TdXB>=1c8qvPJ+U3@&bqR)rzvR`$Z{DiIL3p4%E7x6>hE;Jjo{8+w58kt z7Yw1zg7PsvxfAY|x_}PSKzS|qRp59r4hZDvCL~&VLr-l7iVn&xW91ya@=139)&-n- z76a_1{O`cP2v&uzKI0o~91;lrEsGtqNqwmk80B9KLby?;#o zc$Kg_t_|kjjpRm&1TGcOB3mX11T3vK3(DtF+IB4ZFekVL99*~z_Ce*HoDf^<9~ufs z#NUPS-%rTj57LzLwnIk$8r2SA4*PZeM|@rKz_&f%m?y||zzM(2g7Uew3$&fJCnfJJI;|&Pb>|lG_z(>jHD$Qr57q2jA#Tu;u;^2eQ>=;n)&}edp~Dgvvo2 z%o}jAt72cM_M>Y26b{z)YS?wL-}>;Qt^gJ8Pw#-hu7~}*?jQ96X>ou2;CB0U7xr&- ze*mn}{{-;6=*Iqu+7Ae6rk^0T4PWe|+&}7HIDw0Cup7Je*e9@m)O-hOZl1@+u7-VH h_(wGu52%K5dRS8h52Pg!h#~kBiGIK$22OiJ{s#rxy;1-G literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..a3c414838713504399b6d6f14d80f2819f7730b0 GIT binary patch literal 48791 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 L%J~=G{`CI<+YfEJ literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar new file mode 100644 index 0000000000000000000000000000000000000000..9bc9a3f05cded90a5eb50afa9d58e61f9dea1bdd GIT binary patch literal 121509 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 iR2C)(9tunsvZS%+t}R%x7^ZIsnp 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ℑxqJ%n+D+cm27gpq`Ek#qHoKI`af7Fy|; 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 Tlw`yJVFMg6+#dvvlOX;dJX;_6 literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom new file mode 100644 index 00000000000..5bd1fc5d399 --- /dev/null +++ b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.pom @@ -0,0 +1,90 @@ + + + 4.0.0 + com.stepstone.stepper + material-stepper + 4.3.1 + aar + Android Material Stepper + This library allows to use Material steppers inside Android applications. + https://github.com/stepstone-tech/android-material-stepper + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + zawadz88 + Piotr Zawadzki + piotr.zawadzki@stepstone.com + + + + https://github.com/stepstone-tech/android-material-stepper.git + https://github.com/stepstone-tech/android-material-stepper.git + https://github.com/stepstone-tech/android-material-stepper + + + + com.android.support + appcompat-v7 + 25.4.0 + compile + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.7.21 + test + + + com.squareup.assertj + assertj-android + 1.1.1 + test + + + org.robolectric + robolectric + 3.3.1 + test + + + httpclient + org.apache.httpcomponents + + + commons-logging + commons-logging + + + + + org.jetbrains.kotlin + kotlin-stdlib-jre7 + 1.1.4-3 + test + + + org.jetbrains.kotlin + kotlin-reflect + 1.1.4-3 + test + + + com.nhaarman + mockito-kotlin + 1.4.0 + test + + + From 347b8142e0ee43176bac857d136b7164f80ee6ba Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 23 Aug 2024 08:37:20 +0700 Subject: [PATCH 129/262] chore(common/models): copies original data-gen's license for attribution --- .../wordbreakers/tools/data-compiler/LICENSE | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 common/models/wordbreakers/tools/data-compiler/LICENSE diff --git a/common/models/wordbreakers/tools/data-compiler/LICENSE b/common/models/wordbreakers/tools/data-compiler/LICENSE new file mode 100644 index 00000000000..14c39a294ae --- /dev/null +++ b/common/models/wordbreakers/tools/data-compiler/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019 National Research Council Canada +Copyright (c) 2024 Eddie Antonio Santos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From ecaef394ceccb32327ecbf38f469ccd34f2723c6 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 23 Aug 2024 08:37:39 +0700 Subject: [PATCH 130/262] docs(common/models): fixes relative path in doc-comment --- common/models/wordbreakers/tools/data-compiler/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/wordbreakers/tools/data-compiler/index.ts b/common/models/wordbreakers/tools/data-compiler/index.ts index 28c52be1aa5..d2effa58562 100644 --- a/common/models/wordbreakers/tools/data-compiler/index.ts +++ b/common/models/wordbreakers/tools/data-compiler/index.ts @@ -19,7 +19,7 @@ const require = createRequire(import.meta.url); * * For internal use only. Please keep away from children. * - * The generated file is saved to ../src/gen/WordBreakProperty.ts + * The generated file is saved to ../../src/main/default/data.inc.ts */ const MAX_CODE_POINT = 0x10FFFF; From d461c4872a43aa7b16abc08cc1abd78a8a2ea9aa Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 23 Aug 2024 07:01:33 +0200 Subject: [PATCH 131/262] chore(common): use `npm install` in emsdk update --- resources/locate_emscripten.inc.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/locate_emscripten.inc.sh b/resources/locate_emscripten.inc.sh index f678e8449e6..f20ac5d0e91 100644 --- a/resources/locate_emscripten.inc.sh +++ b/resources/locate_emscripten.inc.sh @@ -69,5 +69,6 @@ _select_emscripten_version_with_emsdk() { git pull ./emsdk install "$KEYMAN_MIN_VERSION_EMSCRIPTEN" ./emsdk activate "$KEYMAN_MIN_VERSION_EMSCRIPTEN" + npm install popd > /dev/null } From 47b952f9609594c86c2c050d6b3bd6aad03dbfb3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 23 Aug 2024 07:10:48 +0200 Subject: [PATCH 132/262] chore(common): npm install should be in emscripten folder --- resources/locate_emscripten.inc.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/locate_emscripten.inc.sh b/resources/locate_emscripten.inc.sh index f20ac5d0e91..3d470f75772 100644 --- a/resources/locate_emscripten.inc.sh +++ b/resources/locate_emscripten.inc.sh @@ -69,6 +69,7 @@ _select_emscripten_version_with_emsdk() { git pull ./emsdk install "$KEYMAN_MIN_VERSION_EMSCRIPTEN" ./emsdk activate "$KEYMAN_MIN_VERSION_EMSCRIPTEN" + cd upstream/emscripten npm install popd > /dev/null } From 2927f57c04fc779d917149ea9fda2ebcddfa9d3c Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:41:31 +1000 Subject: [PATCH 133/262] feat(windows): missed checked in file --- common/windows/delphi/general/RegistryKeys.pas | 1 + 1 file changed, 1 insertion(+) diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 1557de58657..0553b823065 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -115,6 +115,7 @@ interface SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU + SRegValue_UseRightModifierHotKey = 'use right modifier for hotkey'; // CU SRegValue_ReleaseShiftKeysAfterKeyPress = 'release shift keys after key press'; // CU SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true From 046bd25f63fa2b1a6dab9f4f7963b56bba372217 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 23 Aug 2024 07:47:53 +0200 Subject: [PATCH 134/262] chore(developer): improve build order of operations so builds work on new machines --- developer/src/tike/build.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/developer/src/tike/build.sh b/developer/src/tike/build.sh index 1ab9dbe320d..fd81ca692d6 100755 --- a/developer/src/tike/build.sh +++ b/developer/src/tike/build.sh @@ -19,7 +19,7 @@ source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" WIN32_TARGET="$WIN32_TARGET_PATH/tike.exe" builder_describe_outputs \ - configure:project /resources/build/win/delphi_environment_generated.inc.sh \ + configure:project /developer/src/tike/xml/layoutbuilder/keymanweb-osk.ttf \ build:project /developer/src/tike/$WIN32_TARGET #------------------------------------------------------------------------------------------------------------------- @@ -31,6 +31,7 @@ function do_configure() { mkdir -p "$DEVELOPER_PROGRAM" cp "$KEYMAN_ROOT/common/schemas/kps/kps.xsd" "$DEVELOPER_PROGRAM" cp "$KEYMAN_ROOT/common/resources/fonts/keymanweb-osk.ttf" "$DEVELOPER_ROOT/src/tike/xml/layoutbuilder/keymanweb-osk.ttf" + run_in_vs_env rc icons.rc } function do_monaco_copy() { @@ -52,8 +53,6 @@ function do_monaco_copy() { pushd "$DEVELOPER_ROOT/src/tike/xml/app/lib/sentry" replaceVersionStrings_Mkver init.js.in init.js popd - - run_in_vs_env rc icons.rc } KEYMANCORE_DLL=keymancore-2.dll From e402ea19c7d8bb2880e96c71a4db846c74a5570c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 23 Aug 2024 15:44:09 +0700 Subject: [PATCH 135/262] chore(android): Update deprecation comment --- android/KMAPro/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index 07a61850c93..87d5ed9bedd 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -15,7 +15,7 @@ buildscript { classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0' classpath 'name.remal:gradle-plugins:1.5.0' - // From jcenter() which could be sunset in future + // From jcenter() which was deprecated August 2024 // https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ classpath 'com.stepstone.stepper:material-stepper:4.3.1@aar' } From 74f586574590feb5b31e351e81a41a112de72a00 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 23 Aug 2024 15:10:43 +0200 Subject: [PATCH 136/262] docs(developer): update build documentation to refer to build.sh Fixes: #12270 --- developer/src/README.md | 40 +++++++++++++++++++++++++++++++++++++++- windows/src/README.md | 37 ++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/developer/src/README.md b/developer/src/README.md index 7e7ea9d336d..d789b63f08c 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -2,6 +2,42 @@ This is the current home for Keyman Developer. +## Build Prerequisites + +* See [Windows Build Environment Configuration](../../docs/build/windows.md). + +## Building Keyman Developer + +1. Start 'Git Bash' (part of Git for Windows). +2. Run `developer/src/build.sh`. +3. Artifacts from a successful build will be placed in **developer/bin** folder. + +*Note*: running `build.sh` will currently reset the packages and path settings +in your Delphi environment. If you use Delphi for other projects, you should +consider building Keyman under a login user dedicated to it, or in a VM. + +Type `build.sh` to see build targets and actions. Common build actions are: + +* `build.sh build` +: builds Keyman Developer + +* `build.sh clean` +: remove temporary files and build artifacts + +* `build.sh publish` +: makes a release of all Keyman Developer projects + +* `build.sh install` +: install some or all components to Program Files (requires elevated command prompt). + +### Building without Delphi + +It is possible to build all components that do _not_ require Delphi. Currently a +few components are Delphi-based (TIKE, setup, a few others), but you may be able +to get away without building them. In this situation, we recommend copying the +relevant Delphi-built components into developer/bin folders from a compatible +installed version of Keyman Developer for testing and debugging purposes. + # Folders ## common @@ -106,4 +142,6 @@ testing and packing keyboards for distribution. ## tools -Various build-time tools for Keyman Developer. \ No newline at end of file +Various build-time tools for Keyman Developer. + + diff --git a/windows/src/README.md b/windows/src/README.md index 1d057482bb7..1e66443ce71 100644 --- a/windows/src/README.md +++ b/windows/src/README.md @@ -1,40 +1,39 @@ -# Keyman for Windows and Keyman Developer +# Keyman for Windows ## Build Prerequisites * See [Windows Build Environment Configuration](../../docs/build/windows.md). -## Building Keyman for Windows and Keyman Developer +## Building Keyman for Windows -1. Start 'Developer Command Prompt for VS 2019'. -2. Run `nmake build` from the **windows/src** folder. +1. Start 'Git Bash' (part of Git for Windows). +2. Run `windows/src/build.sh`. 3. Artifacts from a successful build will be placed in **windows/bin** folder. -*Note*: running `nmake build` will currently reset the packages and path settings +*Note*: running `build.sh` will currently reset the packages and path settings in your Delphi environment. If you use Delphi for other projects, you should consider building Keyman under a login user dedicated to it, or in a VM. -Type `nmake` to see build targets. Common build targets are: +Type `build.sh` to see build targets and actions. Common build actions are: -* `nmake build` -: builds Keyman for Windows and Keyman Developer +* `build.sh build` +: builds Keyman for Windows -* `nmake clean` +* `build.sh clean` : remove temporary files and build artifacts -* `nmake release` +* `build.sh publish` : makes a release of all Keyman Windows projects -* `nmake install` +* `build.sh install` : install some or all components to Program Files (requires elevated command prompt). ### Building without Delphi -It is possible to build all components that do _not_ require Delphi by adding -the environment variable `NODELPHI=1` before starting the build. Currently many -components are Delphi-based, but if you are working just in Keyman Core, the -compiler, or Keyman Engine's C++ components, you may be able to get away without -building them. In this situation, we recommend copying the relevant Delphi-built +It is possible to build all components that do _not_ require Delphi. Currently +many components are Delphi-based, but if you are working just in Keyman Core, or +Keyman Engine's C++ components, you may be able to get away without building +them. In this situation, we recommend copying the relevant Delphi-built components into windows/bin folders from a compatible installed version of Keyman for testing and debugging purposes. @@ -45,8 +44,8 @@ build, you will need to obtain valid code signing certificates. See Certificates, below. Official release builds for Keyman are built in the Keyman project's CI environment. -1. Start 'Developer Command Prompt for VS 2019'. -2. Run `nmake release` from the **windows/src** folder. +1. Start 'Git Bash'. +2. Run `windows/src/build.sh publish`. 3. Artifacts from a successful build will be placed in **windows/release** folder. 4. **buildtools/help-keyman-com.sh** will push updated documentation to @@ -76,7 +75,7 @@ You do not need to install the **KeymanTest** certificates, only the **KeymanTestCA** certificates. If you have not already installed the **KeymanTestCA** certificates using the -`nmake` command above, to manually install the Keyman **KeymanTestCA** +`build.sh` command above, to manually install the Keyman **KeymanTestCA** certificates, do the following in **common/windows/delphi/tools/certificates**: 1. Open the certificate and click 'Install certificate...' to open the From 8457f9527a51a734b8fa8afb77ad1ba16a72a3eb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 23 Aug 2024 17:53:48 +0200 Subject: [PATCH 137/262] chore(developer): extend timeouts for lm compiler tests to 5 secs Fixes: #12271 --- developer/src/kmc-model/test/test-compile-model.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/developer/src/kmc-model/test/test-compile-model.ts b/developer/src/kmc-model/test/test-compile-model.ts index e3074d697ac..7de9d6d5dac 100644 --- a/developer/src/kmc-model/test/test-compile-model.ts +++ b/developer/src/kmc-model/test/test-compile-model.ts @@ -9,6 +9,8 @@ import { KeymanFileTypes } from '@keymanapp/common-types'; describe('LexicalModelCompiler', function () { let callbacks = new TestCompilerCallbacks(); + this.timeout(5000); + // Try to compile ALL of the correct models. const MODELS = [ 'example.qaa.sencoten', From 9a8b6d1279e9070caf21103cdae6aa3db5e8c72e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 23 Aug 2024 18:12:06 +0200 Subject: [PATCH 138/262] chore(windows): remove remaining unused Makefiles Fixes: #12247 --- common/windows/delphi/Defines.mak | 299 ------------------ common/windows/delphi/Header.mak | 59 ---- common/windows/delphi/Target.mak | 14 - resources/build/win/environment.inc.sh | 4 + windows/src/build.sh | 1 + windows/src/support/Makefile | 38 --- windows/src/support/build.sh | 22 ++ windows/src/support/charident/Makefile | 20 -- windows/src/support/etl2log/Makefile | 22 -- windows/src/support/etl2log/build.sh | 40 +++ windows/src/support/kdebug/Makefile | 21 -- windows/src/support/keycodetester/Makefile | 15 - windows/src/support/km_yim/Makefile | 24 -- windows/src/support/kmkb0045/Makefile | 19 -- windows/src/support/oskbulkrenderer/Makefile | 24 -- windows/src/support/oskbulkrenderer/build.sh | 39 +++ windows/src/support/richedit_examine/Makefile | 15 - windows/src/support/texteditor/Makefile | 36 --- windows/src/support/texteditor/build.sh | 44 +++ windows/src/support/windowinfo/Makefile | 15 - .../manual-tests/integration-tests/Makefile | 26 -- .../integration-tests/keymanapi/Makefile | 23 -- .../test/manual-tests/regressiontest/Makefile | 16 - .../regressiontest/tests/Makefile | 11 - .../manual-tests/test_httpuploader/Makefile | 18 -- 25 files changed, 150 insertions(+), 715 deletions(-) delete mode 100644 common/windows/delphi/Defines.mak delete mode 100644 common/windows/delphi/Header.mak delete mode 100644 common/windows/delphi/Target.mak delete mode 100644 windows/src/support/Makefile create mode 100755 windows/src/support/build.sh delete mode 100644 windows/src/support/charident/Makefile delete mode 100644 windows/src/support/etl2log/Makefile create mode 100755 windows/src/support/etl2log/build.sh delete mode 100644 windows/src/support/kdebug/Makefile delete mode 100644 windows/src/support/keycodetester/Makefile delete mode 100644 windows/src/support/km_yim/Makefile delete mode 100644 windows/src/support/kmkb0045/Makefile delete mode 100644 windows/src/support/oskbulkrenderer/Makefile create mode 100755 windows/src/support/oskbulkrenderer/build.sh delete mode 100644 windows/src/support/richedit_examine/Makefile delete mode 100644 windows/src/support/texteditor/Makefile create mode 100755 windows/src/support/texteditor/build.sh delete mode 100644 windows/src/support/windowinfo/Makefile delete mode 100644 windows/src/test/manual-tests/integration-tests/Makefile delete mode 100644 windows/src/test/manual-tests/integration-tests/keymanapi/Makefile delete mode 100644 windows/src/test/manual-tests/regressiontest/Makefile delete mode 100644 windows/src/test/manual-tests/regressiontest/tests/Makefile delete mode 100644 windows/src/test/manual-tests/test_httpuploader/Makefile diff --git a/common/windows/delphi/Defines.mak b/common/windows/delphi/Defines.mak deleted file mode 100644 index 50210e5fea1..00000000000 --- a/common/windows/delphi/Defines.mak +++ /dev/null @@ -1,299 +0,0 @@ -# DEBUG=1 - -# TODO: this should be a shared Defines.mak for common,developer,windows. So we -# need to move any project-specific stuff into a defines-windows.mak, -# defines-etc.mak - -# -# Paths -# - -!IFNDEF KEYMAN_ROOT -!ERROR KEYMAN_ROOT must be defined! -!ENDIF - -# TODO: COMMON_ROOT should be common\windows -COMMON_ROOT=$(KEYMAN_ROOT)\common\windows\delphi -WINDOWS_ROOT=$(KEYMAN_ROOT)\windows -OUTLIB=$(WINDOWS_ROOT)\lib -COMMON_OUTLIB=$(KEYMAN_ROOT)\windows\lib -COMMON_BIN=$(KEYMAN_ROOT)\windows\bin - -# INCLUDE=$(ROOT)\src\global\inc;$(INCLUDE) - -!IFDEF DEBUG -GO_FAST=1 -MAKEFLAG_DEBUG="DEBUG=$(DEBUG)" -DELPHI_MSBUILD_FLAG_DEBUG="/p:Config=Debug" -!ELSE -!IFDEF TEAMCITY_PR_NUMBER -GO_FAST=1 -!ENDIF -DELPHI_MSBUILD_FLAG_DEBUG="/p:Config=Release" -!ENDIF - -!IFDEF USERDEFINES -MAKEFLAG_USERDEFINES="USERDEFINES=$(USERDEFINES)" -!ENDIF - -!IFDEF SC_TIMESTAMP -MAKEFLAG_SC_TIMESTAMP="SC_TIMESTAMP=$(SC_TIMESTAMP)" -!ENDIF - -!IFDEF BUILDHELP -MAKEFLAG_BUILDHELP="BUILDHELP=$(BUILDHELP)" -!ENDIF - -!IFDEF LINT -MAKEFLAG_LINT="LINT=$(LINT)" -!ENDIF - -!IFDEF NOUI -MAKEFLAG_QUIET="NOUI=$(NOUI)" -!ELSE -!IFDEF QUIET -MAKEFLAG_QUIET="QUIET=$(QUIET)" -!ENDIF -!ENDIF - -!IFDEF RELEASE_OEM -MAKEFLAG_RELEASE_OEM="RELEASE_OEM=$(RELEASE_OEM)" -!ENDIF - -# -# USERDEFINES allows the developer to specify overrides for various settings. We need a variable -# because Makefiles cannot test for file existence -# - -# TODO: can we eliminate this? - -!ifdef USERDEFINES -!include $(WINDOWS_ROOT)\src\UserDefines.mak -!endif - -# -# Delphi Compiler Configuration - Delphi 10.3.2 -# - -!IFNDEF DELPHI_VERSION -DELPHI_VERSION=20.0 -!ENDIF - -DCC32PATH=C:\Program Files (x86)\Embarcadero\Studio\$(DELPHI_VERSION)\bin - -# -# Pass local configuration through to sub-instances of MAKE -# - -MAKE="nmake" /C $(MAKEFLAG_QUICK_BUILD_KEYMAN) $(MAKEFLAG_USERDEFINES) $(MAKEFLAG_DEBUG) $(MAKEFLAG_BUILDHELP) $(MAKEFLAG_BUILDRTF) $(MAKEFLAG_SC_TIMESTAMP) $(MAKEFLAG_LINT) $(MAKEFLAG_QUIET) $(MAKEFLAG_RELEASE_OEM) - -# -# Delphi build commands -# - -!IFDEF DEBUG -TARGET_PATH=Debug -!ELSE -TARGET_PATH=Release -!ENDIF - -!IFDEF LINT -DELPHIWARNINGS=-W+MESSAGE_DIRECTIVE -W+IMPLICIT_STRING_CAST -W+IMPLICIT_STRING_CAST_LOSS -W+EXPLICIT_STRING_CAST -W+EXPLICIT_STRING_CAST_LOSS -W+CVT_WCHAR_TO_ACHAR -W+CVT_NARROWING_STRING_LOST -W+CVT_ACHAR_TO_WCHAR -W+CVT_WIDENING_STRING_LOST -W+UNICODE_TO_LOCALE -W+LOCALE_TO_UNICODE -W+IMPLICIT_VARIANTS -!ELSE -DELPHIWARNINGS=-W-MESSAGE_DIRECTIVE -W-IMPLICIT_STRING_CAST -W-IMPLICIT_STRING_CAST_LOSS -W-EXPLICIT_STRING_CAST -W-EXPLICIT_STRING_CAST_LOSS -W-CVT_WCHAR_TO_ACHAR -W-CVT_NARROWING_STRING_LOST -W-CVT_ACHAR_TO_WCHAR -W-CVT_WIDENING_STRING_LOST -W-UNICODE_TO_LOCALE -W-LOCALE_TO_UNICODE -W-IMPLICIT_VARIANTS -W-IMPLICIT_INTEGER_CAST_LOSS -W-IMPLICIT_CONVERSION_LOSS -W-COMBINING_SIGNED_UNSIGNED64 -W-COMBINING_SIGNED_UNSIGNED64 -!ENDIF - -DELPHIDPRPARAMS=-Q -B -GD -H -VT -^$C+ -^$D+ -^$J+ -^$L+ -^$O+ -^$Q- -^$R- -^$W+ -^$Y+ -E. $(DELPHIWARNINGS) -I$(DELPHIINCLUDES) -U$(DELPHIINCLUDES) -R$(DELPHIINCLUDES) -NSVcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win -NU.\obj\Win32\$(TARGET_PATH) -E.\bin\Win32\$(TARGET_PATH) -DELPHIDPRPARAMS64=-Q -B -GD -H -VT -^$C+ -^$D+ -^$J+ -^$L+ -^$O+ -^$Q- -^$R- -^$W+ -^$Y+ -E. $(DELPHIWARNINGS) -I$(DELPHIINCLUDES) -U$(DELPHIINCLUDES) -R$(DELPHIINCLUDES) -NSVcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win -NU.\obj\Win64\$(TARGET_PATH) -E.\bin\Win64\$(TARGET_PATH) -DELPHIDPKPARAMS=-Q -B -GD -VT -^$C+ -^$D+ -^$J+ -^$L+ -^$O+ -^$Q- -^$R- -^$W+ -^$Y+ -E. $(DELPHIWARNINGS) -I$(DELPHIINCLUDES) -U$(DELPHIINCLUDES) -R$(DELPHIINCLUDES) -NSVcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win -LE$(OUTLIB) -LN$(OUTLIB) -NSData -NUobj\Win32\$(TARGET_PATH) - -COMMON_DELPHIDPKPARAMS=-Q -B -GD -VT -^$C+ -^$D+ -^$J+ -^$L+ -^$O+ -^$Q- -^$R- -^$W+ -^$Y+ -E. $(DELPHIWARNINGS) -I$(DELPHIINCLUDES) -U$(DELPHIINCLUDES) -R$(DELPHIINCLUDES) -NSVcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win -LE$(COMMON_OUTLIB) -LN$(COMMON_OUTLIB) -NSData -NUobj\Win32\$(TARGET_PATH) - -# we are using cmd /c because dcc32/dcc64 are failing on direct execution -# from nmake -DCC32=cmd /c "$(DCC32PATH)\dcc32.exe" $(DELPHIDPRPARAMS) -DCC32DPK=cmd /c "$(DCC32PATH)\dcc32.exe" $(DELPHIDPKPARAMS) -COMMON_DCC32DPK=cmd /c "$(DCC32PATH)\dcc32.exe" $(COMMON_DELPHIDPKPARAMS) -DCC64=cmd /c "$(DCC32PATH)\dcc64.exe" $(DELPHIDPRPARAMS64) -N0x64\ -Ex64\ - -# -# Delphi MSBuild related commands and macros -# - -DELPHI_MSBUILD="$(COMMON_ROOT)\tools\msbuild-wrapper.bat" "$(DCC32PATH)" $(DELPHI_MSBUILD_FLAG_DEBUG) - -!IFDEF NODELPHI -DCC32=echo skipping -DCC32DPK=echo skipping -DCC64=echo skipping -DELPHI_MSBUILD=echo skipping -!ENDIF - -# Visual C++ x86, x64 -WIN32_TARGET_PATH=bin\Win32\$(TARGET_PATH) -X64_TARGET_PATH=bin\x64\$(TARGET_PATH) - -# Delphi x86, x64 -# WIN32_TARGET_PATH=... -WIN64_TARGET_PATH=bin\Win64\$(TARGET_PATH) - -# -# Shared devtools app for common Delphi source manipulation -# - -DEVTOOLS=$(COMMON_ROOT)\tools\devtools\$(WIN32_TARGET_PATH)\devtools.exe - -# -# Other program build commands -# - -BRCC32=rc.exe - -HHC="C:\Program Files (x86)\HTML Help Workshop\hhc.exe" -NMAKE=nmake.exe -CL=cl.exe -MSBUILD=msbuild.exe -# /maxcpucount see https://devblogs.microsoft.com/cppblog/precompiled-header-pch-issues-and-recommendations/ -MT=mt.exe -VCBUILD=error - -!IFDEF DEBUG -MSBUILD_BUILD=/t:Build /p:Configuration=Debug -MSBUILD_CLEAN=/t:Clean /p:Configuration=Debug -!ELSE -MSBUILD_BUILD=/t:Rebuild /p:Configuration=Release -MSBUILD_CLEAN=/t:Clean /p:Configuration=Release -!ENDIF - -COPY=copy -ISXBUILD=C:\PROGRA~1\INSTALLSHIELD\Express\System\IsExpCmdBld -WZZIPPATH="C:\program files\7-zip\7z.exe" -!IFDEF GO_FAST -WZZIP=$(WZZIPPATH) a -mx1 -!ELSE -WZZIP=$(WZZIPPATH) a -mx9 -!ENDIF -WZUNZIP=$(WZZIPPATH) e - -# we are using cmd /c because tds2dbg is failing on direct execution -# from nmake -TDS2DBG=cmd /c $(KEYMAN_ROOT)\common\windows\bin\tools\tds2dbg -SENTRYTOOL=$(COMMON_ROOT)\tools\sentrytool\$(WIN32_TARGET_PATH)\sentrytool.exe -SENTRYTOOL_DELPHIPREP=$(SENTRYTOOL) delphiprep -r $(KEYMAN_ROOT) -i $(DELPHIINCLUDES) - -WIXPATH="c:\program files (x86)\WiX Toolset v3.11\bin" -WIXCANDLE=$(WIXPATH)\candle.exe -wx -nologo - -!IFDEF LINT -WIXLIGHTLINT= -!ELSE -# we suppress ICE82 because it reports spurious errors with merge module keymanengine to do with duplicate sequence numbers. Safely ignored. -WIXLIGHTLINT= -sice:ICE82 -sice:ICE80 -!ENDIF - -!IFDEF GO_FAST -# for debug builds, we turn off compression because it is so hideously slow -# for test builds, we also turn off compression -WIXLIGHTCOMPRESSION=-dcl:none -!ELSE -WIXLIGHTCOMPRESSION=-dcl:high -!ENDIF - -WIXLIGHT=$(WIXPATH)\light.exe -wx -nologo $(WIXLIGHTLINT) $(WIXLIGHTCOMPRESSION) - -WIXLIT=$(WIXPATH)\lit.exe -wx -nologo -WIXHEAT=$(WIXPATH)\heat.exe - -LINKPATH=link.exe - -# -# Certificates and code signing -# - -!ifdef SIGNCODE_BUILD -MAKE=$(MAKE) "SIGNCODE_BUILD=$(SIGNCODE_BUILD)" -!else -MAKE=$(MAKE) -!endif - -# -# To get a .pfx from a .spc and .pvk, run pvk2pfx.exe -# - -!IFNDEF SC_PFX_SHA1 -SC_PFX_SHA1="$(COMMON_ROOT)\tools\certificates\keymantest-sha1.pfx" -!ENDIF - -!IFNDEF SC_PFX_SHA256 -SC_PFX_SHA256="$(COMMON_ROOT)\tools\certificates\keymantest-sha256.pfx" -!ENDIF - -!IFNDEF SC_URL -SC_URL="https://keyman.com/" -!ENDIF - -!IFNDEF SC_PWD -SC_PWD="" -!ENDIF - -SIGNCODE=@"$(KEYMAN_ROOT)\common\windows\signtime.bat" signtool.exe $(SC_PFX_SHA1) $(SC_PFX_SHA256) $(SC_URL) $(SC_PWD) - -# -# On some computers, the PLATFORM environment variable is set to x86. This can break msbuild -# with our projects. This may be resolvable in the future, but for now the easy fix is ... -# - -PLATFORM=Win32 - -# -# mkver commands. mkver determines tag from the local build environment variables -# in the same way as /resources/build/build-utils.sh. -# - -!ifdef GIT_BASH_FOR_KEYMAN -MKVER_SH=$(GIT_BASH_FOR_KEYMAN) $(KEYMAN_ROOT)\common\windows\mkver.sh -!else -MKVER_SH=start /wait $(KEYMAN_ROOT)\common\windows\mkver.sh -!endif - -MKVER_M=$(MKVER_SH) manifest.in manifest.xml -MKVER_U=$(MKVER_SH) - -# -# Symstore -# - -# KEYMAN_SYMSTOREPATH defaults to sibling folder "symbols". If it is not present, -# then we won't attempt to write symbols to the store. -!IFNDEF KEYMAN_SYMSTOREPATH -KEYMAN_SYMSTOREPATH=$(KEYMAN_ROOT)\..\symbols -!ENDIF - -# Nearly matches algorithm from resources/build/build-utils.sh -# For now, we'll use it only for SYMSTORE, where it is for reference -# only. Thus using the variable name __VERSION_WITH_TAG. Issues: -# 1. always appends tier, even for stable -# 2. test builds will append a branch name for master/beta/stable-x.y -# 3. this is only available for `make symbols` (VERSION_WIN, VERSION_TIER -# are defined in Targets.mak only here) -# Fixing this properly would be possible but take a fair bit more -# work than I want to do just now. The intent is to make it possible to find -# symbols in the symstore index which we can purge later on. -__VERSION_WITH_TAG=$(VERSION_WIN)-$(VERSION_TIER) -!IFNDEF TEAMCITY_VERSION -__VERSION_WITH_TAG=$(__VERSION_WITH_TAG)-local -!ELSE -!IFDEF TEAMCITY_PR_NUMBER -__VERSION_WITH_TAG=$(__VERSION_WITH_TAG)-test-$(TEAMCITY_PR_NUMBER) -!ENDIF -!ENDIF - -# This command depends on VERSION_WIN and VERSION_TIER being defined, through -# `make symbols` (i.e. don't call `make wrap-symbols`) -SYMSTORE="C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symstore.exe" add \ - /s "$(KEYMAN_SYMSTOREPATH)" \ - /v "$(VERSION_WIN)" \ - /c "Version: $(__VERSION_WITH_TAG)" \ - /compress /f - -CLEAN=-del /S /Q diff --git a/common/windows/delphi/Header.mak b/common/windows/delphi/Header.mak deleted file mode 100644 index 6c78b3724e3..00000000000 --- a/common/windows/delphi/Header.mak +++ /dev/null @@ -1,59 +0,0 @@ -# -# Header.mak - used for makefiles which are in parent folders -# - -HEADER_MAK=1 - -!IFNDEF TARGETS -!ERROR You must define the targets before including the Header.mak file! -!ENDIF - -!IFNDEF KEYMAN_ROOT -!ERROR KEYMAN_ROOT must be defined! -!ENDIF - -# This path will need to be updated if the root path changes - -!include $(KEYMAN_ROOT)\common\windows\delphi\Defines.mak - -# -# The targets build, signcode, symbols are standard -# targets for all projects -# - -build: $(BUILDPREREQ) - $(MAKE) "TARGET=build" $(TARGETS) - -!IFNDEF NOTARGET_SIGNCODE -signcode: - $(MAKE) "TARGET=signcode" $(TARGETS) - -symbols: - $(MAKE) "TARGET=symbols" $(TARGETS) -!ELSE -signcode: - rem no signcode required - -symbols: - rem no symbols required -!ENDIF - -build-release: -!IFDEF RELEASE_TARGETS - $(MAKE) "TARGET=build-release" $(RELEASE_TARGETS) -!ELSE - @rem -!ENDIF - -clean: - $(MAKE) "TARGET=clean" $(TARGETS) $(CLEANS) - -install: - $(MAKE) "TARGET=install" $(TARGETS) - -test-manifest: -!IFDEF MANIFESTS - $(MAKE) "TARGET=test-manifest" $(MANIFESTS) -!ELSE - $(MAKE) "TARGET=test-manifest" $(TARGETS) -!ENDIF \ No newline at end of file diff --git a/common/windows/delphi/Target.mak b/common/windows/delphi/Target.mak deleted file mode 100644 index 777bd84beb6..00000000000 --- a/common/windows/delphi/Target.mak +++ /dev/null @@ -1,14 +0,0 @@ -!CMDSWITCHES +S - -def-clean: - $(CLEAN) *.err *.stat *.dproj.local *.Build.CppClean.Log *.suo *.jdbg *.dbg *.dcu *.~* *.dsk *.exe *.rsm *.ncb *.opt *.pch *.plg *.aps *.001 *.sbr *.dep *.drc *.bak *.pdb *.lib *.cod *.ilk *.tds vc80.idb *.map *.bsc version.res manifest.xml manifest.res >nul 2>nul - $(CLEAN) ExcMagic.Debug *.wixpdb *.identcache *.embed.manifest *.embed.manifest.res *.intermediate.manifest error.log >nul 2>nul - if exist bin rd /s/q bin - if exist obj rd /s/q obj - -!CMDSWITCHES -S - -# This virtual rule forces targets which are folders, e.g. `kmshell` to -# always execute -.virtual: - rem always execute diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index d2b351fcbc5..620e96146a9 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -23,8 +23,10 @@ source "$KEYMAN_ROOT/resources/build/win/environment_generated.inc.sh" WINDOWS_ROOT="$KEYMAN_ROOT/windows" WINDOWS_PROGRAM_APP="$WINDOWS_ROOT/bin/desktop" WINDOWS_PROGRAM_ENGINE="$WINDOWS_ROOT/bin/engine" +WINDOWS_PROGRAM_SUPPORT="$WINDOWS_ROOT/bin/support" WINDOWS_DEBUGPATH_APP="$WINDOWS_ROOT/debug/desktop" WINDOWS_DEBUGPATH_ENGINE="$WINDOWS_ROOT/debug/engine" +WINDOWS_DEBUGPATH_SUPPORT="$WINDOWS_ROOT/debug/suppor" COMMON_ROOT="$KEYMAN_ROOT/common/windows/delphi" OUTLIB="$WINDOWS_ROOT/lib" @@ -141,6 +143,8 @@ create-windows-output-folders() { mkdir -p "$OUTLIB" mkdir -p "$WINDOWS_PROGRAM_APP" mkdir -p "$WINDOWS_PROGRAM_ENGINE" + mkdir -p "$WINDOWS_PROGRAM_SUPPORT" mkdir -p "$WINDOWS_DEBUGPATH_APP" mkdir -p "$WINDOWS_DEBUGPATH_ENGINE" + mkdir -p "$WINDOWS_DEBUGPATH_SUPPORT" } \ No newline at end of file diff --git a/windows/src/build.sh b/windows/src/build.sh index a1fc4d0e1a5..4ef6da65b86 100755 --- a/windows/src/build.sh +++ b/windows/src/build.sh @@ -18,6 +18,7 @@ builder_describe \ ":engine Keyman Engine for Windows" \ ":desktop Keyman for Windows" \ ":components=global/delphi Delphi components" \ + ":support Support tools" \ ":test=test/unit-tests Shared unit tests" \ ":fv=../../oem/firstvoices/windows/src/inst OEM FirstVoices for Windows app" diff --git a/windows/src/support/Makefile b/windows/src/support/Makefile deleted file mode 100644 index 56609e21e48..00000000000 --- a/windows/src/support/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -# -# Support Makefile -# - -!ifdef NODELPHI -TARGETS=etl2log texteditor -!else -TARGETS=oskbulkrenderer etl2log texteditor -!endif -CLEANS=clean-support - -!include ..\Header.mak - -# ---------------------------------------------------------------------- - -oskbulkrenderer: .virtual - cd $(ROOT)\src\support\oskbulkrenderer - $(MAKE) $(TARGET) - -etl2log: .virtual - cd $(ROOT)\src\support\etl2log - $(MAKE) $(TARGET) - -texteditor: .virtual - cd $(ROOT)\src\support\texteditor - $(MAKE) $(TARGET) - -# ---------------------------------------------------------------------- - -clean-support: - cd $(ROOT)\src\support - -del version.txt - -!include ..\Target.mak - -# ---------------------------------------------------------------------- -# EOF -# ---------------------------------------------------------------------- diff --git a/windows/src/support/build.sh b/windows/src/support/build.sh new file mode 100755 index 00000000000..f9f20f1716e --- /dev/null +++ b/windows/src/support/build.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +builder_describe \ + "Keyman for Windows support tools" \ + \ + clean \ + configure \ + build \ + test \ + \ + :oskbulkrenderer \ + :etl2log \ + :texteditor + +builder_parse "$@" + +builder_run_child_actions clean configure build test publish install diff --git a/windows/src/support/charident/Makefile b/windows/src/support/charident/Makefile deleted file mode 100644 index cff67416776..00000000000 --- a/windows/src/support/charident/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# -# charident Makefile -# - -!include ..\..\Defines.mak - -build: version.res dirs - $(DCC32) charident.dpr - $(COPY) charident.exe $(PROGRAM)\support - -clean: def-clean - -signcode: - $(SIGNCODE) /d "Character Identifier" $(PROGRAM)\support\charident.exe - -wrap-symbols: - $(SYMSTORE) $(PROGRAM)\support\charident.exe /t keyman-windows -#TODO: $(SYMSTORE) $(DEBUGPATH)\support\charident.dbg /t keyman-windows - -!include ..\..\Target.mak diff --git a/windows/src/support/etl2log/Makefile b/windows/src/support/etl2log/Makefile deleted file mode 100644 index 48bdb1a592c..00000000000 --- a/windows/src/support/etl2log/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# -# etl2log Makefile -# - -!include ..\..\Defines.mak - -build: version.res dirs - $(MSBUILD) etl2log.vcxproj $(MSBUILD_BUILD) - $(COPY) $(WIN32_TARGET_PATH)\etl2log.exe $(PROGRAM)\support - $(COPY) $(WIN32_TARGET_PATH)\etl2log.pdb $(DEBUGPATH)\support - -clean: def-clean - $(MSBUILD) etl2log.vcxproj $(MSBUILD_CLEAN) - -signcode: - $(SIGNCODE) /d "Keyman Engine Tools" $(PROGRAM)\support\etl2log.exe - -wrap-symbols: - $(SYMSTORE) $(PROGRAM)\support\etl2log.exe /t keyman-windows - $(SYMSTORE) $(DEBUGPATH)\support\etl2log.pdb /t keyman-windows - -!include ..\..\Target.mak diff --git a/windows/src/support/etl2log/build.sh b/windows/src/support/etl2log/build.sh new file mode 100755 index 00000000000..e2fdee1acd9 --- /dev/null +++ b/windows/src/support/etl2log/build.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +builder_describe "etl2log" \ + clean configure build test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" +WIN32_TARGET="$WIN32_TARGET_PATH/etl2log.exe" + +builder_describe_outputs \ + configure:project /resources/build/win/delphi_environment_generated.inc.sh \ + build:project /windows/src/support/etl2log/$WIN32_TARGET + +#------------------------------------------------------------------------------------------------------------------- + +function do_clean() { + vs_msbuild etl2log.vcxproj //t:Clean + clean_windows_project_files +} + +function do_build() { + create-windows-output-folders + build_version.res + vs_msbuild etl2log.vcxproj //t:Build "//p:Platform=Win32" + cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" + cp "$WIN32_TARGET_PATH/etl2log.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" +} + +builder_run_action clean:project do_clean +builder_run_action configure:project configure_windows_build_environment +builder_run_action build:project do_build +# builder_run_action test:project do_test diff --git a/windows/src/support/kdebug/Makefile b/windows/src/support/kdebug/Makefile deleted file mode 100644 index 1a13a18ebe4..00000000000 --- a/windows/src/support/kdebug/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# -# KDebug Makefile -# - -!include ..\..\Defines.mak - -!ifdef DEBUG - NMCFG=kdebug - Win32 Debug -!else - NMCFG=kdebug - Win32 Release -!endif - -install: dirs build - -build: version.res - $(NMAKE) /f kdebug.mak "CFG=$(NMCFG)" - -clean: def-clean - $(NMAKE) /f kdebug.mak "CFG=$(NMCFG)" CLEAN - -!include ..\..\Target.mak diff --git a/windows/src/support/keycodetester/Makefile b/windows/src/support/keycodetester/Makefile deleted file mode 100644 index 38687abaebd..00000000000 --- a/windows/src/support/keycodetester/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# -# Keycodetester Makefile -# - -!include ..\..\Defines.mak - -install: dirs build - $(COPY) keycodetester.exe $(PROGRAM)\bldutil - -build: - $(DCC32) keycodetester.dpr - -clean: def-clean - -!include ..\..\Target.mak diff --git a/windows/src/support/km_yim/Makefile b/windows/src/support/km_yim/Makefile deleted file mode 100644 index 1aef5f80f02..00000000000 --- a/windows/src/support/km_yim/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -# -# TODO: remove this project -# - -!include ..\..\Defines.mak - -build: -# version.res - $(DCC32) km_yim.dpr - rem $(TDSPACK) $(PROGRAM)\desktop\km_yim.exe km_yim.tds - rem $(TDS2DBG) $(PROGRAM)\desktop\km_yim.exe - $(WZZIP) inst_km_yim.zip km_yim.exe -# $(WZSE) inst_km_yim -setup -t inst_km_yim.dialog.txt -st "Tavultesoft Keyman Desktop Yahoo Messenger Addin" -c km_yim.exe - -clean: def-clean - if exist inst_km_yim.zip del inst_km_yim.zip - -signcode: - $(SIGNCODE) /d "Tavultesoft Keyman Desktop Yahoo Messenger Addin" inst_km_yim.exe - -wrap-symbols: - rem - -!include ..\..\Target.mak diff --git a/windows/src/support/kmkb0045/Makefile b/windows/src/support/kmkb0045/Makefile deleted file mode 100644 index 3e914c8acda..00000000000 --- a/windows/src/support/kmkb0045/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# -# TODO: remove this project -# - -!include ..\..\Defines.mak - -build: version.res manifest.res - $(DCC32) kmkb0045.dpr - $(SENTRYTOOL_DELPHIPREP) kmkb0045.exe -dpr kmkb0045.dpr - $(TDS2DBG) kmkb0045.exe - $(COPY) kmkb0045.exe $(PROGRAM)\support - -clean: def-clean - -signcode: - $(SIGNCODE) /d "Tavultesoft KMKB0045" $(PROGRAM)\support\kmkb0045.exe - - -!include ..\..\Target.mak diff --git a/windows/src/support/oskbulkrenderer/Makefile b/windows/src/support/oskbulkrenderer/Makefile deleted file mode 100644 index 65b8e02ba63..00000000000 --- a/windows/src/support/oskbulkrenderer/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -# -# oskbulkrenderer Makefile -# - -!include ..\..\Defines.mak - -build: version.res dirs - $(DELPHI_MSBUILD) oskbulkrenderer.dproj "/p:Platform=Win32" - - $(SENTRYTOOL_DELPHIPREP) $(WIN32_TARGET_PATH)\oskbulkrenderer.exe -dpr oskbulkrenderer.dpr - $(TDS2DBG) $(WIN32_TARGET_PATH)\oskbulkrenderer.exe - $(COPY) $(WIN32_TARGET_PATH)\oskbulkrenderer.exe $(PROGRAM)\support - if exist $(WIN32_TARGET_PATH)\oskbulkrenderer.dbg $(COPY) $(WIN32_TARGET_PATH)\oskbulkrenderer.dbg $(DEBUGPATH)\support - -clean: def-clean - -rd /s/q Win32 - -signcode: - rem Not signing this utility - -wrap-symbols: - @rem - -!include ..\..\Target.mak diff --git a/windows/src/support/oskbulkrenderer/build.sh b/windows/src/support/oskbulkrenderer/build.sh new file mode 100755 index 00000000000..76fa8a40328 --- /dev/null +++ b/windows/src/support/oskbulkrenderer/build.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +builder_describe "oskbuildrenderer" \ + clean configure build test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" +WIN32_TARGET="$WIN32_TARGET_PATH/oskbulkrenderer.exe" + +builder_describe_outputs \ + configure:project /resources/build/win/delphi_environment_generated.inc.sh \ + build:project /windows/src/support/oskbulkrenderer/$WIN32_TARGET + +#------------------------------------------------------------------------------------------------------------------- + +function do_clean() { + clean_windows_project_files +} + +function do_build() { + create-windows-output-folders + build_version.res + delphi_msbuild oskbulkrenderer.dproj "//p:Platform=Win32" + + cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" +} + +builder_run_action clean:project do_clean +builder_run_action configure:project configure_windows_build_environment +builder_run_action build:project do_build +# builder_run_action test:project do_test diff --git a/windows/src/support/richedit_examine/Makefile b/windows/src/support/richedit_examine/Makefile deleted file mode 100644 index adacba59019..00000000000 --- a/windows/src/support/richedit_examine/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# -# richedit_examine Makefile -# - -!include ..\..\Defines.mak - -install: dirs build - $(COPY) richedit_examine.exe $(PROGRAM)\bldutil - -build: version.res - $(DCC32) richedit_examine.dpr - -clean: def-clean - -!include ..\..\Target.mak diff --git a/windows/src/support/texteditor/Makefile b/windows/src/support/texteditor/Makefile deleted file mode 100644 index 473bf57598e..00000000000 --- a/windows/src/support/texteditor/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# -# Keyman Text Editor Makefile -# - -!include ..\..\Defines.mak - -build: - $(MSBUILD) editor.vcxproj $(MSBUILD_BUILD) /p:Platform=x86 - $(MSBUILD) editor.vcxproj $(MSBUILD_BUILD) /p:Platform=x64 - $(COPY) $(WIN32_TARGET_PATH)\editor32.exe $(PROGRAM)\support - $(COPY) $(WIN32_TARGET_PATH)\editor32.pdb $(DEBUGPATH)\support - $(COPY) $(X64_TARGET_PATH)\editor64.exe $(PROGRAM)\support - $(COPY) $(X64_TARGET_PATH)\editor64.pdb $(DEBUGPATH)\support - -clean: def-clean - $(MSBUILD) $(MSBUILD_CLEAN) editor.sln - -signcode: - $(SIGNCODE) /d "Text Editor for testing Keyman" $(PROGRAM)\support\editor32.exe - $(SIGNCODE) /d "Text Editor for testing Keyman" $(PROGRAM)\support\editor64.exe - -install: - $(COPY) $(PROGRAM)\support\editor32.exe "$(INSTALLPATH_KEYMANENGINE)" - $(COPY) $(PROGRAM)\support\editor64.exe "$(INSTALLPATH_KEYMANENGINE)" - -wrap-symbols: - $(SYMSTORE) $(PROGRAM)\support\editor32.exe /t keyman-windows - $(SYMSTORE) $(PROGRAM)\support\editor64.exe /t keyman-windows - $(SYMSTORE) $(DEBUGPATH)\support\editor32.pdb /t keyman-windows - $(SYMSTORE) $(DEBUGPATH)\support\editor64.pdb /t keyman-windows - - -!include ..\..\Target.mak -# ---------------------------------------------------------------------- -# EOF -# ---------------------------------------------------------------------- diff --git a/windows/src/support/texteditor/build.sh b/windows/src/support/texteditor/build.sh new file mode 100755 index 00000000000..312ee1d0f44 --- /dev/null +++ b/windows/src/support/texteditor/build.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +builder_describe "texteditor" \ + clean configure build test + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" +WIN32_TARGET="$WIN32_TARGET_PATH/editor32.exe" +X64_TARGET="$X64_TARGET_PATH/editor64.exe" + +builder_describe_outputs \ + configure:project /resources/build/win/delphi_environment_generated.inc.sh \ + build:project /windows/src/support/texteditor/$WIN32_TARGET + +#------------------------------------------------------------------------------------------------------------------- + +function do_clean() { + vs_msbuild Editor.vcxproj //t:Clean + clean_windows_project_files +} + +function do_build() { + create-windows-output-folders + # build_version.res + vs_msbuild Editor.vcxproj //t:Build "//p:Platform=Win32" + vs_msbuild Editor.vcxproj //t:Build "//p:Platform=x64" + cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" + cp "$X64_TARGET" "$WINDOWS_PROGRAM_SUPPORT" + cp "$WIN32_TARGET_PATH/editor32.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + cp "$X64_TARGET_PATH/editor64.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" +} + +builder_run_action clean:project do_clean +builder_run_action configure:project configure_windows_build_environment +builder_run_action build:project do_build +# builder_run_action test:project do_test diff --git a/windows/src/support/windowinfo/Makefile b/windows/src/support/windowinfo/Makefile deleted file mode 100644 index ac7da2c1826..00000000000 --- a/windows/src/support/windowinfo/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# -# windowinfo Makefile -# - -!include ..\..\Defines.mak - -install: dirs build - $(COPY) windowinfo.exe $(PROGRAM)\bldutil - -build: version.res - $(DCC32) windowinfo.dpr - -clean: def-clean - -!include ..\..\Target.mak diff --git a/windows/src/test/manual-tests/integration-tests/Makefile b/windows/src/test/manual-tests/integration-tests/Makefile deleted file mode 100644 index 093dc44140e..00000000000 --- a/windows/src/test/manual-tests/integration-tests/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# -# Integration Tests Makefile. -# - -# ---------------------------------------------------------------------- - -TARGETS=keymanapi - -test: - $(MAKE) "TARGET=test" $(TARGETS) - -!include ..\..\Header.mak - -# ---------------------------------------------------------------------- - -keymanapi: - cd $(ROOT)\src\test\integration-tests\keymanapi - $(MAKE) $(TARGET) - -# ---------------------------------------------------------------------- - -!include ..\..\Target.mak - -# ---------------------------------------------------------------------- -# EOF -# ---------------------------------------------------------------------- diff --git a/windows/src/test/manual-tests/integration-tests/keymanapi/Makefile b/windows/src/test/manual-tests/integration-tests/keymanapi/Makefile deleted file mode 100644 index 7becbb044d3..00000000000 --- a/windows/src/test/manual-tests/integration-tests/keymanapi/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# -# Integration tests for keymanapi -# - -!include ..\..\..\Defines.mak - -!ifdef EXCLUDE_ELEVATED -TEST_MODE=--exclude:Elevated -!endif - -test: build - $(WIN32_TARGET_PATH)\keymanapi_test.exe -b -exit:continue $(TEST_MODE) - $(WIN64_TARGET_PATH)\keymanapi_test.exe -b -exit:continue $(TEST_MODE) - -build: - $(DELPHI_MSBUILD) "/p:Platform=Win32" keymanapi_test.dproj - $(DELPHI_MSBUILD) "/p:Platform=Win64" keymanapi_test.dproj - -clean: def-clean - -rd /s/q Win32 - -rd /s/q Win64 - -!include ..\..\..\Target.mak diff --git a/windows/src/test/manual-tests/regressiontest/Makefile b/windows/src/test/manual-tests/regressiontest/Makefile deleted file mode 100644 index dbfa70078c0..00000000000 --- a/windows/src/test/manual-tests/regressiontest/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -# -# REGRESSIONTEST Makefile -# - - -!include ..\..\Defines.mak - -build: dirs - $(DCC32) regressiontest.dpr - $(COPY) regressiontest.exe $(PROGRAM)\support - -clean: def-clean - cd tests - $(MAKE) clean - -!include ..\..\Target.mak diff --git a/windows/src/test/manual-tests/regressiontest/tests/Makefile b/windows/src/test/manual-tests/regressiontest/tests/Makefile deleted file mode 100644 index 01f87053998..00000000000 --- a/windows/src/test/manual-tests/regressiontest/tests/Makefile +++ /dev/null @@ -1,11 +0,0 @@ - -!include ..\..\..\Defines.mak - -all: - for %d in (*.kmn) do $(PROGRAM)\bin\kmc.cmd build "%d" - -.kmn.kmx: - &$(PROGRAM)\bin\kmc.cmd build $** - -clean: - -del *.kmx diff --git a/windows/src/test/manual-tests/test_httpuploader/Makefile b/windows/src/test/manual-tests/test_httpuploader/Makefile deleted file mode 100644 index b6250f4bfd2..00000000000 --- a/windows/src/test/manual-tests/test_httpuploader/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# -# test_httpuploader Makefile -# - -!include ..\..\Defines.mak - -build: version.res manifest.res - $(DCC32) test_httpuploader.dpr - -icons: - rc icons.rc - -clean: def-clean - -signcode: - rem no signcode - -!include ..\..\Target.mak From 91465f22b3770d163f27f388d32e2b4d161cd30a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 23 Aug 2024 14:03:25 -0400 Subject: [PATCH 139/262] auto: increment master version to 18.0.97 --- HISTORY.md | 9 +++++++++ VERSION.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 2ee27cd1989..bf768c67d5d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Keyman Version History +## 18.0.96 alpha 2024-08-23 + +* fix(android): Fix navigation arrows in Info Activity for RTL (#12244) +* fix(web): fix documentation-keyboard spacebar-text scaling (#12232) +* fix(android): Use increment and decrement arrows on longpress delay menu (#12242) +* fix(android): Add RTL assets for adjusting keyboard height menu (#12261) +* chore(common): use `npm install` in emsdk update (#12269) +* docs: refresh windows.md (#12248) + ## 18.0.95 alpha 2024-08-22 * chore(common): allow build agents to automatically select emsdk version, and enable support for 3.1.60+ (#12243) diff --git a/VERSION.md b/VERSION.md index 998f9bf6fd3..2558c437be2 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.96 \ No newline at end of file +18.0.97 \ No newline at end of file From 6bfa8a438f76efa2e5f03bc1dad7e629a3602890 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 23 Aug 2024 15:30:40 +0200 Subject: [PATCH 140/262] refactor(linux): cleanup API of kvk2ldml.py Make methods private that are only used inside of kvk2ldml.py. --- linux/keyman-config/keyman_config/kvk2ldml.py | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index 5b3ab85ae8b..3b3aee281bb 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -164,43 +164,43 @@ class NKey: } -def bytecheck(value, check): +def _bytecheck(value, check): if bytes([value & check[0]]) == check: return True else: return False -def get_nkey(file, fileContent, offset): +def _get_nkey(file, fileContent, offset): nkey = NKey() data = struct.unpack_from(" 256: @@ -224,7 +224,7 @@ def get_nstring(file, fileContent, offset): return stringdata.decode('utf-16'), offset + 2 + (2 * stringlength[0]) -def get_nbitmap(file, fileContent, offset): +def _get_nbitmap(file, fileContent, offset): bitmap = None bitmaplength = struct.unpack_from(" Date: Fri, 23 Aug 2024 22:36:54 +0200 Subject: [PATCH 141/262] fix(linux): add `keymanFacename` to .ldml file This change adds the `keymanFacename` attribute based on UnicodeFont or AnsiFont to the root element of the generated .ldml file so that onboard-keyman can display the proper font for the osk. Fixes: #12019 --- linux/keyman-config/keyman_config/kvk2ldml.py | 6 +++++- linux/keyman-config/tests/test_kvk2ldml.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 linux/keyman-config/tests/test_kvk2ldml.py diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index 3b3aee281bb..f57cfe2dd47 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -349,7 +349,11 @@ def convert_ldml(kvkData): else: keymaps["shift"] = (uskey,) - ldml = etree.Element("keyboard", locale="zzz-keyman") + if kvkData.UnicodeFont: + font = kvkData.UnicodeFont.name + else: + font = kvkData.AnsiFont.name + ldml = etree.Element("keyboard", locale="zzz-keyman", keymanFacename=font) etree.SubElement(ldml, "version", platform="11") names = etree.SubElement(ldml, "names") names.append(etree.Element("name", value="ZZZ")) diff --git a/linux/keyman-config/tests/test_kvk2ldml.py b/linux/keyman-config/tests/test_kvk2ldml.py new file mode 100644 index 00000000000..b3107642a0d --- /dev/null +++ b/linux/keyman-config/tests/test_kvk2ldml.py @@ -0,0 +1,14 @@ +import unittest + +from keyman_config.kvk2ldml import KVKData, NFont, convert_ldml + +class Kvk2LdmlTests(unittest.TestCase): + def test_convert_ldml__adds_keymanFacename(self): + kvkData = KVKData() + kvkData.AssociatedKeyboard = 'khmer_angkor' + kvkData.UnicodeFont = NFont() + kvkData.UnicodeFont.name='KbdKhmr' + + ldml = convert_ldml(kvkData) + self.assertEqual(ldml.get('locale'), "zzz-keyman") + self.assertEqual(ldml.get('keymanFacename'), 'KbdKhmr') From d028124ca993e307d592b15f8e4e6c7fd2d74044 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 24 Aug 2024 00:15:33 +0200 Subject: [PATCH 142/262] fix(developer): find last matching key in LDML key bag when building KVK Fixes: #12056 --- .../src/compiler/visual-keyboard-compiler.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 6182e6e2c8f..4c51078af85 100644 --- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts @@ -3,6 +3,23 @@ import { LDMLKeyboard, CompilerCallbacks } from "@keymanapp/developer-utils"; import { KeysCompiler } from "./keys.js"; import { LdmlCompilerMessages } from "./ldml-compiler-messages.js"; +// This is a partial polyfill for findLast, so not polluting Array.prototype +// +// TODO: remove and replace with Array.prototype.findLast when it is +// well-supported +function findLast(arr: any, callback: any) { + if (!arr) { + return undefined; + } + const len = arr.length >>> 0; + for (let i = len - 1; i >= 0; i--) { + if (callback(arr[i], i, arr)) { + return arr[i]; + } + } + return undefined; +} + export class LdmlKeyboardVisualKeyboardCompiler { public constructor(private callbacks: CompilerCallbacks) { } @@ -57,7 +74,8 @@ export class LdmlKeyboardVisualKeyboardCompiler { const keyId = key; x++; - let keydef = source.keyboard3.keys?.key?.find(x => x.id == key); + //@ts-ignore + let keydef = findLast(source.keyboard3.keys?.key, x => x.id == key); if (!keydef) { this.callbacks.reportMessage( From 16ce5b0408b598293f8388b96c4fd49031e4864f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Sat, 24 Aug 2024 10:07:36 +0800 Subject: [PATCH 143/262] fix(linux): get oskFont from `kmp.json` instead of UnicodeFont --- .../keyman_config/install_kmp.py | 2 +- linux/keyman-config/keyman_config/kvk2ldml.py | 25 ++++++---- linux/keyman-config/km-kvk2ldml | 3 +- linux/keyman-config/tests/test_kvk2ldml.py | 47 +++++++++++++++++-- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index d82c8e06e57..fed67952ca5 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -177,8 +177,8 @@ def _install_files(self, keyboards, files): # Special handling to convert kvk into LDML logging.info("Converting %s to LDML and installing both as as keyman file", f['name']) - ldml = convert_kvk_to_ldml(fpath) name, ext = os.path.splitext(f['name']) + ldml = convert_kvk_to_ldml(name, fpath) ldmlfile = os.path.join(self.packageDir, f"{name}.ldml") output_ldml(ldmlfile, ldml) elif ftype == KMFileTypes.KM_ICON: diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index f57cfe2dd47..d80fc2c1077 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -1,11 +1,14 @@ #!/usr/bin/python3 import logging +import os import struct import sys from lxml import etree +from keyman_config.kmpmetadata import parsemetadata + # .kvk file format # KVK files are variable length files with variable sized structures. @@ -311,7 +314,7 @@ def _get_modifer(key): return modifier -def convert_ldml(kvkData): +def convert_ldml(keyboardName, kvkData, kmpJsonFilename): keymaps = {} for key in kvkData.Keys: @@ -349,11 +352,16 @@ def convert_ldml(kvkData): else: keymaps["shift"] = (uskey,) - if kvkData.UnicodeFont: - font = kvkData.UnicodeFont.name - else: - font = kvkData.AnsiFont.name - ldml = etree.Element("keyboard", locale="zzz-keyman", keymanFacename=font) + info, system, options, keyboards, files = parsemetadata(kmpJsonFilename) + + ldml = etree.Element("keyboard", locale="zzz-keyman") + for keyboard in keyboards: + if keyboard['id'] != keyboardName: + continue + if 'oskFont' in keyboard: + ldml.set('keymanFacename', keyboard['oskFont']) + break + etree.SubElement(ldml, "version", platform="11") names = etree.SubElement(ldml, "names") names.append(etree.Element("name", value="ZZZ")) @@ -406,6 +414,7 @@ def parse_kvk_file(kvkfile): return kvkData -def convert_kvk_to_ldml(kvkfile): +def convert_kvk_to_ldml(name, kvkfile): kvkData = parse_kvk_file(kvkfile) - return convert_ldml(kvkData) + kmpJsonFilename = os.path.join(os.path.dirname(kvkfile), 'kmp.json') + return convert_ldml(name, kvkData, kmpJsonFilename) diff --git a/linux/keyman-config/km-kvk2ldml b/linux/keyman-config/km-kvk2ldml index d669dfa72cd..34c4e76fdf5 100755 --- a/linux/keyman-config/km-kvk2ldml +++ b/linux/keyman-config/km-kvk2ldml @@ -56,7 +56,8 @@ def main(): try: with open(outputfile, 'wb') as ldmlfile: - ldml = convert_ldml(kvkData) + kmpJsonFilename = os.path.join(os.path.dirname(args.kvkfile), 'kmp.json') + ldml = convert_ldml(name, kvkData, kmpJsonFilename) output_ldml(ldmlfile, ldml) except PermissionError: logging.error(f'km-kvk2ldml: error, permission denied writing file `{outputfile}`') diff --git a/linux/keyman-config/tests/test_kvk2ldml.py b/linux/keyman-config/tests/test_kvk2ldml.py index b3107642a0d..51b30ab6446 100644 --- a/linux/keyman-config/tests/test_kvk2ldml.py +++ b/linux/keyman-config/tests/test_kvk2ldml.py @@ -1,14 +1,53 @@ +import os +import tempfile import unittest from keyman_config.kvk2ldml import KVKData, NFont, convert_ldml class Kvk2LdmlTests(unittest.TestCase): + def _createKmpJson(self, packagedir): + kmpJsonFilename = os.path.join(packagedir, 'kmp.json') + with open(kmpJsonFilename, 'w') as file: + file.write('''{ + "system": { + "keymanDeveloperVersion": "18.0", + "fileVersion": "7.0" + }, + "files": [ { + "name": "khmer_angkor.kmx", + "description": "Keyboard Khmer Angkor" + }, { + "name": "kmp.json", + "description": "Package information (JSON)" + } ], + "keyboards": [ { + "name": "Khmer Angkor", + "id": "khmer_angkor", + "version": "1.5", + "oskFont": "KbdKhmr.ttf", + "languages": [ { + "name": "Central Khmer (Khmer, Cambodia)", + "id": "km" + } ]} + ]}''') + return kmpJsonFilename + def test_convert_ldml__adds_keymanFacename(self): + # Setup + keyboardName = 'khmer_angkor' kvkData = KVKData() - kvkData.AssociatedKeyboard = 'khmer_angkor' + kvkData.AssociatedKeyboard = keyboardName kvkData.UnicodeFont = NFont() - kvkData.UnicodeFont.name='KbdKhmr' + kvkData.UnicodeFont.name='DontUseThis!' + + workdir = tempfile.TemporaryDirectory() + kmpJsonFilename = self._createKmpJson(workdir.name) - ldml = convert_ldml(kvkData) + # Execute + ldml = convert_ldml(keyboardName, kvkData, kmpJsonFilename) + + # Verify self.assertEqual(ldml.get('locale'), "zzz-keyman") - self.assertEqual(ldml.get('keymanFacename'), 'KbdKhmr') + self.assertEqual(ldml.get('keymanFacename'), 'KbdKhmr.ttf') + + workdir.cleanup() From c99e833c8e78e3ec76ca1da84aef06ae3631ef33 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Sat, 24 Aug 2024 10:25:57 +0800 Subject: [PATCH 144/262] fix(linux): Add font name without file extension --- linux/keyman-config/keyman_config/kvk2ldml.py | 3 ++- linux/keyman-config/tests/test_kvk2ldml.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index d80fc2c1077..4109e37def8 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -359,7 +359,8 @@ def convert_ldml(keyboardName, kvkData, kmpJsonFilename): if keyboard['id'] != keyboardName: continue if 'oskFont' in keyboard: - ldml.set('keymanFacename', keyboard['oskFont']) + font, ext = os.path.splitext(keyboard['oskFont']) + ldml.set('keymanFacename', font) break etree.SubElement(ldml, "version", platform="11") diff --git a/linux/keyman-config/tests/test_kvk2ldml.py b/linux/keyman-config/tests/test_kvk2ldml.py index 51b30ab6446..cd46d10e1fd 100644 --- a/linux/keyman-config/tests/test_kvk2ldml.py +++ b/linux/keyman-config/tests/test_kvk2ldml.py @@ -48,6 +48,6 @@ def test_convert_ldml__adds_keymanFacename(self): # Verify self.assertEqual(ldml.get('locale'), "zzz-keyman") - self.assertEqual(ldml.get('keymanFacename'), 'KbdKhmr.ttf') + self.assertEqual(ldml.get('keymanFacename'), 'KbdKhmr') workdir.cleanup() From f5066174cd929eef02d127ed747a698fe16344e6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 24 Aug 2024 10:28:48 +0800 Subject: [PATCH 145/262] fix(developer): make LDML import path consistent for all bundlings of kmc The default import path is relative to ldml-keyboard-xml-reader.js. The bundling of kmc with Keyman Developer reduces the directory tree depth, so we move the import path to be a subdirectory of the immediate parent directory, rather than two levels up, so that `C:\Program Files\Keyman\Keyman Developer\kmc` can refer to `C:\Program Files\Keyman\Keyman Developer\import` when kmc is bundled. Fixes: #12279 --- developer/src/common/web/utils/build.sh | 4 ++-- .../utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh index 8829765237e..2209463e184 100755 --- a/developer/src/common/web/utils/build.sh +++ b/developer/src/common/web/utils/build.sh @@ -35,9 +35,9 @@ function copy_cldr_imports() { # TODO-LDML: developer/src/inst/download.in.mak needs these also... CLDR_PATH=$(dirname "$CLDR_INFO_PATH") CLDR_VER=$(basename "$CLDR_PATH") - mkdir -p "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER" + mkdir -p "$THIS_SCRIPT_PATH/build/src/types/import/$CLDR_VER" # TODO-LDML: When these are copied, the DOCTYPE will break due to the wrong path. We don't use the DTD so it should be OK. - cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER/" + cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/types/import/$CLDR_VER/" done } diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 162cac4aacc..ddeaf719abd 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -23,7 +23,7 @@ export class LDMLKeyboardXMLSourceFileReader { } static get defaultImportsURL(): [string,string] { - return ['../../import/', import.meta.url]; + return ['../import/', import.meta.url]; } readImportFile(version: string, subpath: string): Uint8Array { From c8cbc2fefe44c89e76bca8b086f592754b1882a3 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Sat, 24 Aug 2024 11:31:23 +0800 Subject: [PATCH 146/262] fix(linux): get font facename from .ttf file --- common/resources/fonts/KbdKhmr.ttf | Bin 0 -> 129124 bytes linux/debian/control | 1 + linux/keyman-config/keyman_config/kvk2ldml.py | 15 ++++++++++++++- linux/keyman-config/tests/test_kvk2ldml.py | 6 +++++- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 common/resources/fonts/KbdKhmr.ttf diff --git a/common/resources/fonts/KbdKhmr.ttf b/common/resources/fonts/KbdKhmr.ttf new file mode 100644 index 0000000000000000000000000000000000000000..cceeaba52fe59c7a2e41b3226bc2897fe7aa6608 GIT binary patch literal 129124 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 3.5), python3-dbus, + python3-fonttools, python3-gi, python3-lxml, python3-magic, diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index 4109e37def8..dbacfef47cd 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -5,6 +5,7 @@ import struct import sys +from fontTools import ttLib from lxml import etree from keyman_config.kmpmetadata import parsemetadata @@ -314,6 +315,17 @@ def _get_modifer(key): return modifier +def _fontFacename(fontFilename): + """Get the facename from fontFilename's TTF tables""" + # From https://github.com/mcfletch/ttfquery/blob/master/ttfquery/describe.py + FONT_SPECIFIER_NAME_ID = 4 + font = ttLib.TTFont(fontFilename) + for record in font['name'].names: + if record.nameID == FONT_SPECIFIER_NAME_ID: + return record.string + return '' + + def convert_ldml(keyboardName, kvkData, kmpJsonFilename): keymaps = {} @@ -359,7 +371,8 @@ def convert_ldml(keyboardName, kvkData, kmpJsonFilename): if keyboard['id'] != keyboardName: continue if 'oskFont' in keyboard: - font, ext = os.path.splitext(keyboard['oskFont']) + fontFile = os.path.join(os.path.dirname(kmpJsonFilename), keyboard['oskFont']) + font = _fontFacename(fontFile) ldml.set('keymanFacename', font) break diff --git a/linux/keyman-config/tests/test_kvk2ldml.py b/linux/keyman-config/tests/test_kvk2ldml.py index cd46d10e1fd..0be6c497012 100644 --- a/linux/keyman-config/tests/test_kvk2ldml.py +++ b/linux/keyman-config/tests/test_kvk2ldml.py @@ -1,4 +1,6 @@ import os +import shutil +import sys import tempfile import unittest @@ -24,7 +26,7 @@ def _createKmpJson(self, packagedir): "name": "Khmer Angkor", "id": "khmer_angkor", "version": "1.5", - "oskFont": "KbdKhmr.ttf", + "oskFont": "KbdKhmrTest.ttf", "languages": [ { "name": "Central Khmer (Khmer, Cambodia)", "id": "km" @@ -42,6 +44,8 @@ def test_convert_ldml__adds_keymanFacename(self): workdir = tempfile.TemporaryDirectory() kmpJsonFilename = self._createKmpJson(workdir.name) + shutil.copyfile(os.path.join(sys.path[0], '../../../common/resources/fonts/KbdKhmr.ttf'), + os.path.join(workdir.name, 'KbdKhmrTest.ttf')) # Execute ldml = convert_ldml(keyboardName, kvkData, kmpJsonFilename) From 50228a8266599fbb34580b6c7321a1a727635949 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Sat, 24 Aug 2024 12:14:47 +0800 Subject: [PATCH 147/262] fix(linux): properly deal with types of names Names can be Unicode strings or byte arrays. This change allows for both kinds. Also properly deal with the case when we can't get the name from the .ttf file. And finally change the test so that we don't need the `KbdKhmr.ttf` font. --- common/resources/fonts/KbdKhmr.ttf | Bin 129124 -> 0 bytes linux/keyman-config/keyman_config/kvk2ldml.py | 7 ++++--- linux/keyman-config/tests/test_kvk2ldml.py | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 common/resources/fonts/KbdKhmr.ttf diff --git a/common/resources/fonts/KbdKhmr.ttf b/common/resources/fonts/KbdKhmr.ttf deleted file mode 100644 index cceeaba52fe59c7a2e41b3226bc2897fe7aa6608..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129124 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 Date: Sat, 24 Aug 2024 12:08:29 +0800 Subject: [PATCH 148/262] fix(core): properly support 'other' modifier state with `uint32_t` type While the modifier state property in core's API is 16-bit, internally ldml_processor supports the modifier flag LDML_KEYS_MOD_OTHER with a value of `0x10000`, which requires widening the value (we match the 32-bit size of the KMX_DWORD value from KMX+). Note: this is not yet well unit-tested. Relates-to: #11072 Fixes: #12057 --- core/src/ldml/ldml_processor.cpp | 2 +- core/src/ldml/ldml_vkeys.cpp | 2 +- core/src/ldml/ldml_vkeys.hpp | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp index f90fcfaa946..fcadda629cb 100644 --- a/core/src/ldml/ldml_processor.cpp +++ b/core/src/ldml/ldml_processor.cpp @@ -85,7 +85,7 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector } else { str = keyEntry->get_to_string(); } - keys.add((km_core_virtual_key)kmapEntry->vkey, (uint16_t)kmapEntry->mod, str); + keys.add((km_core_virtual_key)kmapEntry->vkey, kmapEntry->mod, str); } } // else: no keys! but still valid. Just, no keys. diff --git a/core/src/ldml/ldml_vkeys.cpp b/core/src/ldml/ldml_vkeys.cpp index 07a2ff08ad9..f79f82141fc 100644 --- a/core/src/ldml/ldml_vkeys.cpp +++ b/core/src/ldml/ldml_vkeys.cpp @@ -17,7 +17,7 @@ vkeys::vkeys() : vkey_to_string() { } void -vkeys::add(km_core_virtual_key vk, uint16_t modifier_state, std::u16string output) { +vkeys::add(km_core_virtual_key vk, km_core_ldml_modifier_state modifier_state, std::u16string output) { // construct key const vkey_id id(vk, modifier_state); // assign the string diff --git a/core/src/ldml/ldml_vkeys.hpp b/core/src/ldml/ldml_vkeys.hpp index ed3368dc7e7..6a54d770653 100644 --- a/core/src/ldml/ldml_vkeys.hpp +++ b/core/src/ldml/ldml_vkeys.hpp @@ -19,10 +19,17 @@ namespace km { namespace core { namespace ldml { +/** + * LDML keyboards have 32-bit modifier flags in order to support + * LDML_KEYS_MOD_OTHER (0x10000), unlike the Core APIs which have only 16 bit + * modifier flags. + */ +typedef uint32_t km_core_ldml_modifier_state; + /** * identifier for keybag lookup */ -typedef std::pair vkey_id; +typedef std::pair vkey_id; /** * LDML Class to manage all things key related: vkey remapping and vkey to string @@ -35,9 +42,9 @@ class vkeys { vkeys(); /** - * add a vkey to the bag + * add a vkey to the bag. */ - void add(km_core_virtual_key vk, uint16_t modifier_state, std::u16string output); + void add(km_core_virtual_key vk, km_core_ldml_modifier_state ldml_modifier_state, std::u16string output); /** * Lookup a vkey, returns an empty string if not found From b0e8b903740f307bbcccdee3a1274a998f30f204 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Sat, 24 Aug 2024 14:03:24 -0400 Subject: [PATCH 149/262] auto: increment master version to 18.0.98 --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index bf768c67d5d..904e03468a2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 18.0.97 alpha 2024-08-24 + +* refactor(linux): cleanup API of kvk2ldml.py (#12276) +* chore(common): adjust build settings for windows clean builds (#12264) +* chore(windows): remove remaining unused Makefiles (#12274) +* docs(developer): update build documentation to refer to build.sh (#12272) + ## 18.0.96 alpha 2024-08-23 * fix(android): Fix navigation arrows in Info Activity for RTL (#12244) diff --git a/VERSION.md b/VERSION.md index 2558c437be2..48b8081cd1f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.97 \ No newline at end of file +18.0.98 \ No newline at end of file From 7dbf4d09d1a7e18b95e6eaabff4472aae9cd798d Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 26 Aug 2024 09:25:59 +0700 Subject: [PATCH 150/262] fix(android): Remove use of mavenLocal() --- android/KMAPro/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index 87d5ed9bedd..ab7158c8d88 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -3,7 +3,6 @@ buildscript { repositories { google() //jcenter() deprecated August 2024 - mavenLocal() mavenCentral() flatDir { dirs "kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/" From be24a7d6056e6d651555379dfb39285f63cf4dbf Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 26 Aug 2024 09:26:08 +0700 Subject: [PATCH 151/262] chore(common/models): moves data-compiler artifacts to /tools subfolder --- common/models/wordbreakers/build.sh | 2 +- .../models/wordbreakers/tools/data-compiler/tsconfig.json | 4 ++-- package-lock.json | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/models/wordbreakers/build.sh b/common/models/wordbreakers/build.sh index 0cad38b1fed..725436c60eb 100755 --- a/common/models/wordbreakers/build.sh +++ b/common/models/wordbreakers/build.sh @@ -34,7 +34,7 @@ function do_configure() { # default wordbreaker. We rarely update the backing data, but it # is needed _before_ the `build` action's compilation step. tsc -b tools/data-compiler/tsconfig.json - node ./build/data-compiler/obj/index.js + node ./build/tools/data-compiler/obj/index.js } function do_build() { diff --git a/common/models/wordbreakers/tools/data-compiler/tsconfig.json b/common/models/wordbreakers/tools/data-compiler/tsconfig.json index 74a8bb005e9..577d1174dfa 100644 --- a/common/models/wordbreakers/tools/data-compiler/tsconfig.json +++ b/common/models/wordbreakers/tools/data-compiler/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "baseUrl": "./", - "outDir": "../../build/data-compiler/obj", - "tsBuildInfoFile": "../../build/data-compiler/obj/tsconfig.tsbuildinfo", + "outDir": "../../build/tools/data-compiler/obj", + "tsBuildInfoFile": "../../build/tools/data-compiler/obj/tsconfig.tsbuildinfo", "rootDir": "./", "module": "node16", "moduleResolution": "node16" diff --git a/package-lock.json b/package-lock.json index cee7d8528b7..a835d0cbd82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -404,7 +404,7 @@ "eventemitter3": "^5.0.0", "restructure": "^3.0.1", "sax": ">=0.6.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "xmlbuilder": "~11.0.0" }, "devDependencies": { @@ -1153,7 +1153,7 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "semver": "^7.5.2" + "semver": "^7.5.4" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -1942,7 +1942,7 @@ "open": "^8.4.0", "restructure": "^3.0.1", "sax": ">=0.6.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "ws": "^8.17.1", "xmlbuilder": "~11.0.0" }, @@ -14630,7 +14630,7 @@ "devDependencies": { "@types/semver": "^7.1.0", "@types/yargs": "^17.0.26", - "semver": "^7.5.2" + "semver": "^7.5.4" } }, "resources/build/version/node_modules/ansi-regex": { From f3461b3307284b42ac789d02b0393d4684764a9b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 26 Aug 2024 12:59:48 +0700 Subject: [PATCH 152/262] chore(windows): fix typo in environment.inc.sh Co-authored-by: 58423624+rc-swag@users.noreply.github.com --- resources/build/win/environment.inc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 620e96146a9..71c18f305b7 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -26,7 +26,7 @@ WINDOWS_PROGRAM_ENGINE="$WINDOWS_ROOT/bin/engine" WINDOWS_PROGRAM_SUPPORT="$WINDOWS_ROOT/bin/support" WINDOWS_DEBUGPATH_APP="$WINDOWS_ROOT/debug/desktop" WINDOWS_DEBUGPATH_ENGINE="$WINDOWS_ROOT/debug/engine" -WINDOWS_DEBUGPATH_SUPPORT="$WINDOWS_ROOT/debug/suppor" +WINDOWS_DEBUGPATH_SUPPORT="$WINDOWS_ROOT/debug/support" COMMON_ROOT="$KEYMAN_ROOT/common/windows/delphi" OUTLIB="$WINDOWS_ROOT/lib" From 6353f926ba31f8d776a05cfd395770f580cd2dfd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 26 Aug 2024 09:09:00 +0200 Subject: [PATCH 153/262] chore: Update developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts --- developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4c51078af85..6d76bc403ea 100644 --- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts @@ -4,7 +4,7 @@ import { KeysCompiler } from "./keys.js"; import { LdmlCompilerMessages } from "./ldml-compiler-messages.js"; // This is a partial polyfill for findLast, so not polluting Array.prototype -// +// https://medium.com/@stheodorejohn/findlast-method-polyfill-in-javascript-bridging-browser-gaps-c3baf6aabae1 // TODO: remove and replace with Array.prototype.findLast when it is // well-supported function findLast(arr: any, callback: any) { From 8c959f7eb2697895e4e94bda9531fd0af736e435 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 26 Aug 2024 14:35:54 +0700 Subject: [PATCH 154/262] chore(android): Cleanup stray debug statements in console --- android/KMEA/app/src/main/assets/android-host.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 62d396aae0b..5e5d7241235 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -117,7 +117,7 @@ function notifyHost(event, params) { function setLongpressDelay(delay) { if (keyman.osk) { keyman.osk.gestureParams.longpress.waitLength = delay; - console.debug('setLongpressDelay('+delay+')'); + console_debug('setLongpressDelay('+delay+')'); } else { window.console.log('setLongpressDelay error: keyman.osk undefined'); } @@ -304,7 +304,7 @@ function updateKMSelectionRange(start, end) { console_debug('result:\n' + build_context_string(context)); } else { - console.debug('range unchanged'); + console_debug('range unchanged'); } } From 7dabd30caebc3d3589910b756fd7f9a03ab6ab1d Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 13 Aug 2024 19:32:12 +0200 Subject: [PATCH 155/262] =?UTF-8?q?refactor(web):=20move=20`gesture-recogn?= =?UTF-8?q?izer`=20=E2=86=92=20`gesture-processor`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves `common/web/gesture-recognizer/` → `web/src/engine/osk/gesture-processor`. Fixes: #12151 --- package-lock.json | 5 +- package.json | 1 + tsconfig.json | 4 +- web/README.md | 2 +- web/ci.sh | 2 +- web/src/engine/osk/build.sh | 2 +- .../engine/osk/gesture-processor}/.c8rc.json | 0 .../engine/osk/gesture-processor}/.gitignore | 0 .../engine/osk/gesture-processor}/README.md | 2 +- .../engine/osk/gesture-processor}/build.sh | 50 +++++++++--------- .../gesture-processor}/docs/host-page-viz.png | Bin .../docs/recognizer-configuration.md | 0 .../osk/gesture-processor}/package.json | 1 + .../gestureRecognizerConfiguration.ts | 0 .../engine/configuration/paddedZoneSource.ts | 0 .../configuration/recognitionZoneSource.ts | 0 .../configuration/viewportZoneSource.ts | 0 .../configuration/zoneBoundaryChecker.ts | 0 .../src/engine/gestureRecognizer.ts | 0 .../headless/asyncClosureDispatchQueue.ts | 0 .../engine/headless/cumulativePathStats.ts | 0 .../src/engine/headless/gestureDebugPath.ts | 0 .../src/engine/headless/gestureDebugSource.ts | 0 .../src/engine/headless/gesturePath.ts | 0 .../src/engine/headless/gestureSource.ts | 0 .../src/engine/headless/gestures/index.ts | 0 .../gestures/matchers/gestureMatcher.ts | 0 .../gestures/matchers/gestureSequence.ts | 0 .../headless/gestures/matchers/index.ts | 0 .../gestures/matchers/matcherSelector.ts | 0 .../headless/gestures/matchers/pathMatcher.ts | 0 .../headless/gestures/specs/contactModel.ts | 0 .../headless/gestures/specs/gestureModel.ts | 0 .../gestures/specs/gestureModelDefs.ts | 0 .../engine/headless/gestures/specs/index.ts | 0 .../gestures/specs/modelDefValidator.ts | 0 .../headless/gestures/specs/pathModel.ts | 0 .../src/engine/headless/inputEngineBase.ts | 0 .../src/engine/headless/inputSample.ts | 0 .../src/engine/headless/segmentClassifier.ts | 0 .../engine/headless/touchpointCoordinator.ts | 0 .../gesture-processor}/src/engine/index.ts | 0 .../src/engine/inputEventEngine.ts | 0 .../src/engine/mouseEventEngine.ts | 0 .../gesture-processor}/src/engine/mutable.ts | 0 .../src/engine/nonoptional.ts | 0 .../src/engine/reportError.ts | 0 .../src/engine/touchEventEngine.ts | 0 .../src/test/auto/browser/cases/canary.def.ts | 0 .../test/auto/browser/cases/gestureHost.css | 0 .../auto/browser/cases/host-page.spec.html | 0 .../auto/browser/cases/ignoredInputs.def.ts | 0 .../cases/recordedCoordSequences.def.ts | 0 .../browser/web-test-runner.CI.config.mjs | 0 .../auto/browser/web-test-runner.config.mjs | 2 +- .../asyncClosureDispatchQueue.spec.ts | 0 .../test/auto/headless/gesturePath.spec.ts | 0 .../test/auto/headless/gestureSource.spec.ts | 0 .../headless/gestures/gestureMatcher.spec.ts | 0 .../gestures/gestureModelDefs.spec.ts | 0 .../headless/gestures/gestureSequence.spec.ts | 0 .../headless/gestures/isolatedGestureSpecs.ts | 0 .../headless/gestures/isolatedPathSpecs.ts | 0 .../headless/gestures/matcherSelector.spec.ts | 0 .../headless/gestures/pathMatcher.spec.ts | 0 .../gestures/touchpointCoordinator.spec.ts | 0 .../src/test/auto/headless/pathStats.spec.ts | 0 .../test/resources/@types/promiseStatus.d.ts | 0 .../test/resources/assertSegmentSimilarity.js | 0 .../test/resources/assertingPromiseStatus.ts | 0 .../test/resources/json/canaryRecording.json | 0 .../json/receiver/basicMultitouch.json | 0 .../json/receiver/desktopRoamAndReturn.json | 0 .../json/receiver/embeddedBorderCancel.json | 0 .../json/receiver/hardBorderCancel.json | 0 .../receiver/mobileProximityApproach.json | 0 .../json/receiver/mobileSafeZoneCancel.json | 0 .../json/receiver/popupLongRoamingEnd.json | 0 .../json/receiver/popupSafePersistence.json | 0 .../json/receiver/popupShimCancel.json | 0 .../json/segmentation/flick_ne_se.json | 0 .../json/segmentation/longpress_to_ne.json | 0 .../json/segmentation/nonstationary_hold.json | 0 .../json/segmentation/quick_small_square.json | 0 .../json/segmentation/simple_ne_move.json | 0 .../src/test/resources/sequenceAssertions.ts | 0 .../resources/simulateMultiSourceInput.ts | 0 .../gesture-processor}/src/test/tsconfig.json | 0 .../osk/gesture-processor}/src/tools/build.sh | 19 ++++--- .../src/tools/host-fixture/extract-fixture.sh | 0 .../src/tools/host-fixture/extractor.cjs | 0 .../src/tools/host-fixture/gestureHost.css | 0 .../src/tools/host-fixture/host-fixture.html | 0 .../src/tools/recorder/src/index.html | 0 .../src/tools/recorder/src/pageStyle.css | 0 .../src/tools/recorder/src/recorder.mjs | 0 .../src/tools/recorder/update-index.cjs | 0 .../unit-test-resources/build-bundler.js | 0 .../src/fixtureLayoutConfiguration.ts | 0 .../src/headlessInputEngine.ts | 0 .../src/hostFixtureLayoutController.ts | 0 .../tools/unit-test-resources/src/index.ts | 0 .../unit-test-resources/src/inputRecording.ts | 0 .../src/inputSequenceSimulator.ts | 0 .../unit-test-resources/src/jsonObject.ts | 0 .../src/sequenceRecorder.ts | 0 .../src/touchpathTurtle.ts | 0 .../tools/unit-test-resources/tsconfig.json | 2 +- .../src/engine/osk/gesture-processor}/test.sh | 6 +-- .../osk/gesture-processor}/tsconfig.json | 8 +-- web/src/engine/osk/tsconfig.json | 2 +- web/src/test/manual/build.sh | 8 +-- .../gesture-recognizer/hostConfiguration.mjs | 2 +- 113 files changed, 62 insertions(+), 56 deletions(-) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/.c8rc.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/.gitignore (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/README.md (78%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/build.sh (59%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/docs/host-page-viz.png (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/docs/recognizer-configuration.md (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/package.json (95%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/configuration/gestureRecognizerConfiguration.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/configuration/paddedZoneSource.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/configuration/recognitionZoneSource.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/configuration/viewportZoneSource.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/configuration/zoneBoundaryChecker.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/gestureRecognizer.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/asyncClosureDispatchQueue.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/cumulativePathStats.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestureDebugPath.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestureDebugSource.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gesturePath.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestureSource.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/index.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/matchers/gestureMatcher.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/matchers/gestureSequence.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/matchers/index.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/matchers/matcherSelector.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/matchers/pathMatcher.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/specs/contactModel.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/specs/gestureModel.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/specs/gestureModelDefs.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/specs/index.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/specs/modelDefValidator.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/gestures/specs/pathModel.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/inputEngineBase.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/inputSample.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/segmentClassifier.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/headless/touchpointCoordinator.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/index.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/inputEventEngine.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/mouseEventEngine.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/mutable.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/nonoptional.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/reportError.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/engine/touchEventEngine.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/cases/canary.def.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/cases/gestureHost.css (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/cases/host-page.spec.html (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/cases/ignoredInputs.def.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/cases/recordedCoordSequences.def.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/web-test-runner.CI.config.mjs (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/browser/web-test-runner.config.mjs (96%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gesturePath.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestureSource.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/gestureMatcher.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/gestureModelDefs.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/gestureSequence.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/isolatedGestureSpecs.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/isolatedPathSpecs.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/matcherSelector.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/pathMatcher.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/auto/headless/pathStats.spec.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/@types/promiseStatus.d.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/assertSegmentSimilarity.js (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/assertingPromiseStatus.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/canaryRecording.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/basicMultitouch.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/desktopRoamAndReturn.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/embeddedBorderCancel.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/hardBorderCancel.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/mobileProximityApproach.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/mobileSafeZoneCancel.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/popupLongRoamingEnd.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/popupSafePersistence.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/receiver/popupShimCancel.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/segmentation/flick_ne_se.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/segmentation/longpress_to_ne.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/segmentation/nonstationary_hold.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/segmentation/quick_small_square.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/json/segmentation/simple_ne_move.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/sequenceAssertions.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/resources/simulateMultiSourceInput.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/test/tsconfig.json (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/build.sh (78%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/host-fixture/extract-fixture.sh (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/host-fixture/extractor.cjs (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/host-fixture/gestureHost.css (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/host-fixture/host-fixture.html (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/recorder/src/index.html (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/recorder/src/pageStyle.css (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/recorder/src/recorder.mjs (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/recorder/update-index.cjs (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/build-bundler.js (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/headlessInputEngine.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/hostFixtureLayoutController.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/index.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/inputRecording.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/inputSequenceSimulator.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/jsonObject.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/sequenceRecorder.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/src/touchpathTurtle.ts (100%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/src/tools/unit-test-resources/tsconfig.json (84%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/test.sh (93%) rename {common/web/gesture-recognizer => web/src/engine/osk/gesture-processor}/tsconfig.json (61%) diff --git a/package-lock.json b/package-lock.json index cee7d8528b7..515b2fefc86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "common/web/*", "common/tools/hextobin", "web", + "web/src/engine/osk/gesture-processor", "web/src/engine/predictive-text/*" ], "dependencies": { @@ -159,7 +160,7 @@ }, "devDependencies": {} }, - "common/web/gesture-recognizer": { + "web/src/engine/osk/gesture-processor": { "name": "@keymanapp/gesture-recognizer", "dependencies": { "eventemitter3": "^5.0.0" @@ -2795,7 +2796,7 @@ "link": true }, "node_modules/@keymanapp/gesture-recognizer": { - "resolved": "common/web/gesture-recognizer", + "resolved": "web/src/engine/osk/gesture-processor", "link": true }, "node_modules/@keymanapp/hextobin": { diff --git a/package.json b/package.json index 3f0012db10c..23970ceff23 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "common/web/*", "common/tools/hextobin", "web", + "web/src/engine/osk/gesture-processor", "web/src/engine/predictive-text/*" ], "dependencies": { diff --git a/tsconfig.json b/tsconfig.json index 1a02e20ff15..fc9c15a34f6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,6 @@ { "path": "./common/models/wordbreakers/tsconfig.json" }, { "path": "./common/tools/hextobin/" }, - { "path": "./common/web/gesture-recognizer/tsconfig.json" }, - { "path": "./common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json" }, { "path": "./common/web/keyman-version" }, { "path": "./common/web/recorder/tsconfig.json" }, { "path": "./common/web/sentry-manager/src/tsconfig.json" }, @@ -43,6 +41,8 @@ { "path": "./web/src/engine/predictive-text/types/" }, { "path": "./web/src/engine/predictive-text/worker-main/tsconfig.all.json" }, { "path": "./web/src/engine/predictive-text/worker-thread" }, + { "path": "./web/src/engine/osk/gesture-processor/tsconfig.json" }, + { "path": "./web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/tsconfig.json" }, // { "path": "./web/tools/recorder/tsconfig.json" }, // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, ] diff --git a/web/README.md b/web/README.md index f09fe519bbb..9e7d896c7bc 100644 --- a/web/README.md +++ b/web/README.md @@ -94,7 +94,7 @@ graph TD; LMWorker-->Wordbreakers; LMLayer["@keymanapp/lexical-model-layer
(/web/src/engine/predictive-text/worker-main)"]; LMLayer-->LMWorker; - Gestures["@keymanapp/gesture-recognizer
(/common/web/gesture-recognizer)"]; + Gestures["@keymanapp/gesture-recognizer
(/web/src/engine/osk/gesture-recognizer)"]; Gestures-->WebUtils; subgraph PredText["PredText: WebWorker + its interface"] diff --git a/web/ci.sh b/web/ci.sh index 5e26a1e30e2..a466417275b 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -86,7 +86,7 @@ if builder_start_action test; then # No --reporter option exists yet for the headless modules. "$KEYMAN_ROOT/web/src/engine/keyboard/build.sh" test $OPTIONS - "$KEYMAN_ROOT/common/web/gesture-recognizer/test.sh" $OPTIONS + "$KEYMAN_ROOT/web/src/engine/osk/gesture-processor/test.sh" $OPTIONS ./build.sh test $OPTIONS diff --git a/web/src/engine/osk/build.sh b/web/src/engine/osk/build.sh index 0d676a2d3ef..e54a75bad1e 100755 --- a/web/src/engine/osk/build.sh +++ b/web/src/engine/osk/build.sh @@ -14,10 +14,10 @@ SUBPROJECT_NAME=engine/osk builder_describe "Builds the Keyman Engine for Web's On-Screen Keyboard package (OSK)." \ "@/web/src/engine/keyboard build" \ - "@/common/web/gesture-recognizer build" \ "@/web/src/engine/interfaces build" \ "@/web/src/engine/dom-utils build" \ "@/web/src/engine/events build" \ + "@/web/src/engine/osk/gesture-processor" \ "clean" \ "configure" \ "build" \ diff --git a/common/web/gesture-recognizer/.c8rc.json b/web/src/engine/osk/gesture-processor/.c8rc.json similarity index 100% rename from common/web/gesture-recognizer/.c8rc.json rename to web/src/engine/osk/gesture-processor/.c8rc.json diff --git a/common/web/gesture-recognizer/.gitignore b/web/src/engine/osk/gesture-processor/.gitignore similarity index 100% rename from common/web/gesture-recognizer/.gitignore rename to web/src/engine/osk/gesture-processor/.gitignore diff --git a/common/web/gesture-recognizer/README.md b/web/src/engine/osk/gesture-processor/README.md similarity index 78% rename from common/web/gesture-recognizer/README.md rename to web/src/engine/osk/gesture-processor/README.md index cbb310092d7..3b941963de5 100644 --- a/common/web/gesture-recognizer/README.md +++ b/web/src/engine/osk/gesture-processor/README.md @@ -6,6 +6,6 @@ KeymanWeb, seen within this repo at /web/. ## Minimum Compilation Requirements -See the repo-level [document for build configuration](../../../docs/build/index.md) for details on how to +See the repo-level [document for build configuration](../../../../../docs/build/index.md) for details on how to configure your build environment. - Follow the Keyman Engine for Web (KeymanWeb) requirements where appropriate. \ No newline at end of file diff --git a/common/web/gesture-recognizer/build.sh b/web/src/engine/osk/gesture-processor/build.sh similarity index 59% rename from common/web/gesture-recognizer/build.sh rename to web/src/engine/osk/gesture-processor/build.sh index a27cd3f8d16..37cd7856235 100755 --- a/common/web/gesture-recognizer/build.sh +++ b/web/src/engine/osk/gesture-processor/build.sh @@ -3,13 +3,15 @@ ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "$(dirname "$THIS_SCRIPT")/../../../resources/build/builder.inc.sh" +. "$(dirname "$THIS_SCRIPT")/../../../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" +BUILD_DIR="/web/src/engine/osk/gesture-processor/build" + ################################ Main script ################################ builder_describe "Builds the gesture-recognition model for Web-based on-screen keyboards" \ @@ -25,8 +27,8 @@ builder_describe "Builds the gesture-recognition model for Web-based on-screen k builder_describe_outputs \ configure /node_modules \ - build:module /common/web/gesture-recognizer/build/lib/index.mjs \ - build:tools /common/web/gesture-recognizer/build/tools/lib/index.mjs + build:module "${BUILD_DIR}/lib/index.mjs" \ + build:tools "${BUILD_DIR}/tools/lib/index.mjs" builder_parse "$@" @@ -41,38 +43,36 @@ function do_configure() { playwright install } -builder_run_action configure do_configure - -if builder_start_action clean; then - rm -rf build/ - builder_finish_action success clean -fi - -if builder_start_action build:module; then +function do_build_module() { # Build tsc --build $builder_verbose - $BUNDLE_CMD "${KEYMAN_ROOT}/common/web/gesture-recognizer/build/obj/index.js" \ - --out "${KEYMAN_ROOT}/common/web/gesture-recognizer/build/lib/index.mjs" \ + $BUNDLE_CMD "${KEYMAN_ROOT}/${BUILD_DIR}/obj/index.js" \ + --out "${KEYMAN_ROOT}/${BUILD_DIR}/lib/index.mjs" \ --format esm +} - builder_finish_action success build:module -fi - -if builder_start_action build:tools; then +function do_build_tools() { src/tools/build.sh build - builder_finish_action success build:tools -fi +} -if builder_start_action test:module; then +function do_test_module() { if builder_has_option --ci; then ./test.sh --ci else ./test.sh fi - builder_finish_action success test:module -fi +} + +function do_test_tools() { + if ! builder_has_action test:module; then + echo "The $(builder_term test:tools) action is currently a no-op." + fi +} -if builder_has_action test:tools && ! builder_has_action test:module; then - echo "The $(builder_term test:tools) action is currently a no-op." -fi \ No newline at end of file +builder_run_action configure do_configure +builder_run_action clean rm -rf build/ intermediate/ +builder_run_action build:module do_build_module +builder_run_action build:tools do_build_tools +builder_run_action test:module do_test_module +builder_run_action test:tools do_test_tools diff --git a/common/web/gesture-recognizer/docs/host-page-viz.png b/web/src/engine/osk/gesture-processor/docs/host-page-viz.png similarity index 100% rename from common/web/gesture-recognizer/docs/host-page-viz.png rename to web/src/engine/osk/gesture-processor/docs/host-page-viz.png diff --git a/common/web/gesture-recognizer/docs/recognizer-configuration.md b/web/src/engine/osk/gesture-processor/docs/recognizer-configuration.md similarity index 100% rename from common/web/gesture-recognizer/docs/recognizer-configuration.md rename to web/src/engine/osk/gesture-processor/docs/recognizer-configuration.md diff --git a/common/web/gesture-recognizer/package.json b/web/src/engine/osk/gesture-processor/package.json similarity index 95% rename from common/web/gesture-recognizer/package.json rename to web/src/engine/osk/gesture-processor/package.json index 66cf248c0fb..9ec0606ebd9 100644 --- a/common/web/gesture-recognizer/package.json +++ b/web/src/engine/osk/gesture-processor/package.json @@ -16,6 +16,7 @@ "test": "gosh ./test.sh" }, "dependencies": { + "@keymanapp/web-utils": "*", "eventemitter3": "^5.0.0" }, "main": "./build/obj/index.js", diff --git a/common/web/gesture-recognizer/src/engine/configuration/gestureRecognizerConfiguration.ts b/web/src/engine/osk/gesture-processor/src/engine/configuration/gestureRecognizerConfiguration.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/configuration/gestureRecognizerConfiguration.ts rename to web/src/engine/osk/gesture-processor/src/engine/configuration/gestureRecognizerConfiguration.ts diff --git a/common/web/gesture-recognizer/src/engine/configuration/paddedZoneSource.ts b/web/src/engine/osk/gesture-processor/src/engine/configuration/paddedZoneSource.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/configuration/paddedZoneSource.ts rename to web/src/engine/osk/gesture-processor/src/engine/configuration/paddedZoneSource.ts diff --git a/common/web/gesture-recognizer/src/engine/configuration/recognitionZoneSource.ts b/web/src/engine/osk/gesture-processor/src/engine/configuration/recognitionZoneSource.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/configuration/recognitionZoneSource.ts rename to web/src/engine/osk/gesture-processor/src/engine/configuration/recognitionZoneSource.ts diff --git a/common/web/gesture-recognizer/src/engine/configuration/viewportZoneSource.ts b/web/src/engine/osk/gesture-processor/src/engine/configuration/viewportZoneSource.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/configuration/viewportZoneSource.ts rename to web/src/engine/osk/gesture-processor/src/engine/configuration/viewportZoneSource.ts diff --git a/common/web/gesture-recognizer/src/engine/configuration/zoneBoundaryChecker.ts b/web/src/engine/osk/gesture-processor/src/engine/configuration/zoneBoundaryChecker.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/configuration/zoneBoundaryChecker.ts rename to web/src/engine/osk/gesture-processor/src/engine/configuration/zoneBoundaryChecker.ts diff --git a/common/web/gesture-recognizer/src/engine/gestureRecognizer.ts b/web/src/engine/osk/gesture-processor/src/engine/gestureRecognizer.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/gestureRecognizer.ts rename to web/src/engine/osk/gesture-processor/src/engine/gestureRecognizer.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/asyncClosureDispatchQueue.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/asyncClosureDispatchQueue.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/cumulativePathStats.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/cumulativePathStats.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/cumulativePathStats.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/cumulativePathStats.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestureDebugPath.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestureDebugPath.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestureDebugPath.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestureDebugPath.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestureDebugSource.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestureDebugSource.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestureDebugSource.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestureDebugSource.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gesturePath.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gesturePath.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gesturePath.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gesturePath.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestureSource.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestureSource.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestureSource.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestureSource.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/index.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/index.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/index.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/index.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/gestureMatcher.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/gestureMatcher.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureSequence.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/gestureSequence.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureSequence.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/gestureSequence.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/index.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/index.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/matchers/index.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/index.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/matcherSelector.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/matcherSelector.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/pathMatcher.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/pathMatcher.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/matchers/pathMatcher.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/matchers/pathMatcher.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/contactModel.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/contactModel.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/specs/contactModel.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/contactModel.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModel.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/gestureModel.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModel.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/gestureModel.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModelDefs.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/gestureModelDefs.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModelDefs.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/gestureModelDefs.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/index.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/index.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/specs/index.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/index.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/modelDefValidator.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/modelDefValidator.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/specs/modelDefValidator.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/modelDefValidator.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/pathModel.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/pathModel.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/gestures/specs/pathModel.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/gestures/specs/pathModel.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/inputEngineBase.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/inputEngineBase.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/inputEngineBase.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/inputEngineBase.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/inputSample.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/inputSample.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/inputSample.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/inputSample.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/segmentClassifier.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/segmentClassifier.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/segmentClassifier.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/segmentClassifier.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/touchpointCoordinator.ts b/web/src/engine/osk/gesture-processor/src/engine/headless/touchpointCoordinator.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/headless/touchpointCoordinator.ts rename to web/src/engine/osk/gesture-processor/src/engine/headless/touchpointCoordinator.ts diff --git a/common/web/gesture-recognizer/src/engine/index.ts b/web/src/engine/osk/gesture-processor/src/engine/index.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/index.ts rename to web/src/engine/osk/gesture-processor/src/engine/index.ts diff --git a/common/web/gesture-recognizer/src/engine/inputEventEngine.ts b/web/src/engine/osk/gesture-processor/src/engine/inputEventEngine.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/inputEventEngine.ts rename to web/src/engine/osk/gesture-processor/src/engine/inputEventEngine.ts diff --git a/common/web/gesture-recognizer/src/engine/mouseEventEngine.ts b/web/src/engine/osk/gesture-processor/src/engine/mouseEventEngine.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/mouseEventEngine.ts rename to web/src/engine/osk/gesture-processor/src/engine/mouseEventEngine.ts diff --git a/common/web/gesture-recognizer/src/engine/mutable.ts b/web/src/engine/osk/gesture-processor/src/engine/mutable.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/mutable.ts rename to web/src/engine/osk/gesture-processor/src/engine/mutable.ts diff --git a/common/web/gesture-recognizer/src/engine/nonoptional.ts b/web/src/engine/osk/gesture-processor/src/engine/nonoptional.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/nonoptional.ts rename to web/src/engine/osk/gesture-processor/src/engine/nonoptional.ts diff --git a/common/web/gesture-recognizer/src/engine/reportError.ts b/web/src/engine/osk/gesture-processor/src/engine/reportError.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/reportError.ts rename to web/src/engine/osk/gesture-processor/src/engine/reportError.ts diff --git a/common/web/gesture-recognizer/src/engine/touchEventEngine.ts b/web/src/engine/osk/gesture-processor/src/engine/touchEventEngine.ts similarity index 100% rename from common/web/gesture-recognizer/src/engine/touchEventEngine.ts rename to web/src/engine/osk/gesture-processor/src/engine/touchEventEngine.ts diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts b/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/canary.def.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/canary.def.ts diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/gestureHost.css b/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/gestureHost.css similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/gestureHost.css rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/gestureHost.css diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/host-page.spec.html b/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/host-page.spec.html similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/host-page.spec.html rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/host-page.spec.html diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts b/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/ignoredInputs.def.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/ignoredInputs.def.ts diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.def.ts b/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/recordedCoordSequences.def.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.def.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/recordedCoordSequences.def.ts diff --git a/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs b/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.CI.config.mjs similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.CI.config.mjs diff --git a/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs b/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs similarity index 96% rename from common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs rename to web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs index 0d5bac85080..4487912e0d9 100644 --- a/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs +++ b/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs @@ -34,7 +34,7 @@ export default { // Rewrites short-hand paths for test resources, making them fully relative to the repo root. function rewriteResourcePath(context, next) { if(context.url.startsWith('/resources/')) { - context.url = '/common/web/gesture-recognizer/src/test' + context.url; + context.url = '/web/src/engine/osk/gesture-processor/src/test' + context.url; } return next(); diff --git a/common/web/gesture-recognizer/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gesturePath.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gesturePath.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gesturePath.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gesturePath.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestureSource.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestureSource.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestureSource.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestureSource.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/gestureMatcher.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureMatcher.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/gestureMatcher.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureMatcher.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/gestureModelDefs.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureModelDefs.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/gestureModelDefs.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureModelDefs.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/gestureSequence.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureSequence.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/gestureSequence.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureSequence.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedGestureSpecs.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedGestureSpecs.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedGestureSpecs.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedGestureSpecs.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedPathSpecs.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedPathSpecs.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedPathSpecs.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedPathSpecs.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/matcherSelector.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/matcherSelector.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/matcherSelector.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/matcherSelector.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/pathMatcher.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/pathMatcher.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/pathMatcher.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/pathMatcher.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts diff --git a/common/web/gesture-recognizer/src/test/auto/headless/pathStats.spec.ts b/web/src/engine/osk/gesture-processor/src/test/auto/headless/pathStats.spec.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/auto/headless/pathStats.spec.ts rename to web/src/engine/osk/gesture-processor/src/test/auto/headless/pathStats.spec.ts diff --git a/common/web/gesture-recognizer/src/test/resources/@types/promiseStatus.d.ts b/web/src/engine/osk/gesture-processor/src/test/resources/@types/promiseStatus.d.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/@types/promiseStatus.d.ts rename to web/src/engine/osk/gesture-processor/src/test/resources/@types/promiseStatus.d.ts diff --git a/common/web/gesture-recognizer/src/test/resources/assertSegmentSimilarity.js b/web/src/engine/osk/gesture-processor/src/test/resources/assertSegmentSimilarity.js similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/assertSegmentSimilarity.js rename to web/src/engine/osk/gesture-processor/src/test/resources/assertSegmentSimilarity.js diff --git a/common/web/gesture-recognizer/src/test/resources/assertingPromiseStatus.ts b/web/src/engine/osk/gesture-processor/src/test/resources/assertingPromiseStatus.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/assertingPromiseStatus.ts rename to web/src/engine/osk/gesture-processor/src/test/resources/assertingPromiseStatus.ts diff --git a/common/web/gesture-recognizer/src/test/resources/json/canaryRecording.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/canaryRecording.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/canaryRecording.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/canaryRecording.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/basicMultitouch.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/basicMultitouch.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/basicMultitouch.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/basicMultitouch.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/desktopRoamAndReturn.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/desktopRoamAndReturn.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/desktopRoamAndReturn.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/desktopRoamAndReturn.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/embeddedBorderCancel.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/embeddedBorderCancel.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/embeddedBorderCancel.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/embeddedBorderCancel.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/hardBorderCancel.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/hardBorderCancel.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/hardBorderCancel.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/hardBorderCancel.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/mobileProximityApproach.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileProximityApproach.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/mobileProximityApproach.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileProximityApproach.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/mobileSafeZoneCancel.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileSafeZoneCancel.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/mobileSafeZoneCancel.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileSafeZoneCancel.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/popupLongRoamingEnd.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupLongRoamingEnd.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/popupLongRoamingEnd.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupLongRoamingEnd.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/popupSafePersistence.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupSafePersistence.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/popupSafePersistence.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupSafePersistence.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/receiver/popupShimCancel.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupShimCancel.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/receiver/popupShimCancel.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupShimCancel.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/segmentation/flick_ne_se.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/flick_ne_se.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/segmentation/flick_ne_se.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/flick_ne_se.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/segmentation/longpress_to_ne.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/longpress_to_ne.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/segmentation/longpress_to_ne.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/longpress_to_ne.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/segmentation/nonstationary_hold.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/nonstationary_hold.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/segmentation/nonstationary_hold.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/nonstationary_hold.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/segmentation/quick_small_square.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/quick_small_square.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/segmentation/quick_small_square.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/quick_small_square.json diff --git a/common/web/gesture-recognizer/src/test/resources/json/segmentation/simple_ne_move.json b/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/simple_ne_move.json similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/json/segmentation/simple_ne_move.json rename to web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/simple_ne_move.json diff --git a/common/web/gesture-recognizer/src/test/resources/sequenceAssertions.ts b/web/src/engine/osk/gesture-processor/src/test/resources/sequenceAssertions.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/sequenceAssertions.ts rename to web/src/engine/osk/gesture-processor/src/test/resources/sequenceAssertions.ts diff --git a/common/web/gesture-recognizer/src/test/resources/simulateMultiSourceInput.ts b/web/src/engine/osk/gesture-processor/src/test/resources/simulateMultiSourceInput.ts similarity index 100% rename from common/web/gesture-recognizer/src/test/resources/simulateMultiSourceInput.ts rename to web/src/engine/osk/gesture-processor/src/test/resources/simulateMultiSourceInput.ts diff --git a/common/web/gesture-recognizer/src/test/tsconfig.json b/web/src/engine/osk/gesture-processor/src/test/tsconfig.json similarity index 100% rename from common/web/gesture-recognizer/src/test/tsconfig.json rename to web/src/engine/osk/gesture-processor/src/test/tsconfig.json diff --git a/common/web/gesture-recognizer/src/tools/build.sh b/web/src/engine/osk/gesture-processor/src/tools/build.sh similarity index 78% rename from common/web/gesture-recognizer/src/tools/build.sh rename to web/src/engine/osk/gesture-processor/src/tools/build.sh index 657a8ab1bf1..ca951721b61 100755 --- a/common/web/gesture-recognizer/src/tools/build.sh +++ b/web/src/engine/osk/gesture-processor/src/tools/build.sh @@ -3,29 +3,32 @@ ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "$(dirname "$THIS_SCRIPT")/../../../../../resources/build/builder.inc.sh" +. "$(dirname "$THIS_SCRIPT")/../../../../../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" +BASE_DIR="/web/src/engine/osk/gesture-processor" +BUILD_DIR="${BASE_DIR}/build" + ################################ Main script ################################ -builder_describe "Testing-oriented tools for the Gesture Recognizer module of web-based Keyman OSKs" \ +builder_describe "Testing-oriented tools for the Gesture Processor module of web-based Keyman OSKs" \ "clean" \ "build" \ ":fixture The HTML-element fixture and CSS fixture used for both user-testing and unit-testing" \ ":recorder The web page used for recording input sequences for use in unit-testing" \ - ":test-module The TS library used to interface with the main gesture-recognizer module for tests" + ":test-module The TS library used to interface with the main gesture-processor module for tests" builder_parse "$@" builder_describe_outputs \ configure /node_modules \ - build:fixture /common/web/gesture-recognizer/build/tools/host-fixture.html \ - build:recorder /common/web/gesture-recognizer/src/tools/recorder/build/recorder.mjs \ - build:test-module /common/web/gesture-recognizer/build/tools/lib/index.mjs + build:fixture "${BUILD_DIR}/tools/host-fixture.html" \ + build:recorder "${BASE_DIR}/src/tools/recorder/build/recorder.mjs" \ + build:test-module "${BUILD_DIR}/tools/lib/index.mjs" # TODO: build if out-of-date if test is specified # TODO: configure if npm has not been run, and build is specified @@ -76,8 +79,8 @@ fi if builder_start_action build:test-module; then tsc -b "$THIS_SCRIPT_PATH/unit-test-resources/tsconfig.json" - $BUNDLE_CMD "${KEYMAN_ROOT}/common/web/gesture-recognizer/build/tools/obj/index.js" \ - --out "${KEYMAN_ROOT}/common/web/gesture-recognizer/build/tools/lib/index.mjs" \ + $BUNDLE_CMD "${KEYMAN_ROOT}/${BUILD_DIR}/tools/obj/index.js" \ + --out "${KEYMAN_ROOT}/${BUILD_DIR}/tools/lib/index.mjs" \ --format "esm" builder_finish_action success build:test-module diff --git a/common/web/gesture-recognizer/src/tools/host-fixture/extract-fixture.sh b/web/src/engine/osk/gesture-processor/src/tools/host-fixture/extract-fixture.sh similarity index 100% rename from common/web/gesture-recognizer/src/tools/host-fixture/extract-fixture.sh rename to web/src/engine/osk/gesture-processor/src/tools/host-fixture/extract-fixture.sh diff --git a/common/web/gesture-recognizer/src/tools/host-fixture/extractor.cjs b/web/src/engine/osk/gesture-processor/src/tools/host-fixture/extractor.cjs similarity index 100% rename from common/web/gesture-recognizer/src/tools/host-fixture/extractor.cjs rename to web/src/engine/osk/gesture-processor/src/tools/host-fixture/extractor.cjs diff --git a/common/web/gesture-recognizer/src/tools/host-fixture/gestureHost.css b/web/src/engine/osk/gesture-processor/src/tools/host-fixture/gestureHost.css similarity index 100% rename from common/web/gesture-recognizer/src/tools/host-fixture/gestureHost.css rename to web/src/engine/osk/gesture-processor/src/tools/host-fixture/gestureHost.css diff --git a/common/web/gesture-recognizer/src/tools/host-fixture/host-fixture.html b/web/src/engine/osk/gesture-processor/src/tools/host-fixture/host-fixture.html similarity index 100% rename from common/web/gesture-recognizer/src/tools/host-fixture/host-fixture.html rename to web/src/engine/osk/gesture-processor/src/tools/host-fixture/host-fixture.html diff --git a/common/web/gesture-recognizer/src/tools/recorder/src/index.html b/web/src/engine/osk/gesture-processor/src/tools/recorder/src/index.html similarity index 100% rename from common/web/gesture-recognizer/src/tools/recorder/src/index.html rename to web/src/engine/osk/gesture-processor/src/tools/recorder/src/index.html diff --git a/common/web/gesture-recognizer/src/tools/recorder/src/pageStyle.css b/web/src/engine/osk/gesture-processor/src/tools/recorder/src/pageStyle.css similarity index 100% rename from common/web/gesture-recognizer/src/tools/recorder/src/pageStyle.css rename to web/src/engine/osk/gesture-processor/src/tools/recorder/src/pageStyle.css diff --git a/common/web/gesture-recognizer/src/tools/recorder/src/recorder.mjs b/web/src/engine/osk/gesture-processor/src/tools/recorder/src/recorder.mjs similarity index 100% rename from common/web/gesture-recognizer/src/tools/recorder/src/recorder.mjs rename to web/src/engine/osk/gesture-processor/src/tools/recorder/src/recorder.mjs diff --git a/common/web/gesture-recognizer/src/tools/recorder/update-index.cjs b/web/src/engine/osk/gesture-processor/src/tools/recorder/update-index.cjs similarity index 100% rename from common/web/gesture-recognizer/src/tools/recorder/update-index.cjs rename to web/src/engine/osk/gesture-processor/src/tools/recorder/update-index.cjs diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/build-bundler.js b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/build-bundler.js similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/build-bundler.js rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/build-bundler.js diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/headlessInputEngine.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/headlessInputEngine.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/headlessInputEngine.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/headlessInputEngine.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/hostFixtureLayoutController.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/hostFixtureLayoutController.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/hostFixtureLayoutController.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/hostFixtureLayoutController.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/index.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/index.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/index.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/index.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputRecording.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/inputRecording.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputRecording.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/inputRecording.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/inputSequenceSimulator.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/inputSequenceSimulator.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/jsonObject.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/jsonObject.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/jsonObject.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/jsonObject.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/sequenceRecorder.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/sequenceRecorder.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/sequenceRecorder.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/sequenceRecorder.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/touchpathTurtle.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/touchpathTurtle.ts similarity index 100% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/src/touchpathTurtle.ts rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/touchpathTurtle.ts diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/tsconfig.json similarity index 84% rename from common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json rename to web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/tsconfig.json index 72541592115..34d1929af17 100644 --- a/common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json +++ b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.kmw-main-base.json", + "extends": "../../../../../../../tsconfig.base.json", "compilerOptions": { "baseUrl": "./", "lib": [ "dom", "es6" ], diff --git a/common/web/gesture-recognizer/test.sh b/web/src/engine/osk/gesture-processor/test.sh similarity index 93% rename from common/web/gesture-recognizer/test.sh rename to web/src/engine/osk/gesture-processor/test.sh index 03d0e32fa16..f1c50eeed49 100755 --- a/common/web/gesture-recognizer/test.sh +++ b/web/src/engine/osk/gesture-processor/test.sh @@ -3,7 +3,7 @@ ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" -. "$(dirname "$THIS_SCRIPT")/../../../resources/build/build-utils.sh" +. "$(dirname "$THIS_SCRIPT")/../../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE . $KEYMAN_ROOT/resources/shellHelperFunctions.sh @@ -13,8 +13,8 @@ cd "$(dirname $THIS_SCRIPT)" ################################ Main script ################################ -builder_describe "Runs all tests for the gesture-recognizer module" \ - "@/common/web/gesture-recognizer" \ +builder_describe "Runs all tests for the gesture-processor module" \ + "@../gesture-processor" \ "test+" \ ":headless Runs headless user tests" \ ":browser Runs browser-based user tests" \ diff --git a/common/web/gesture-recognizer/tsconfig.json b/web/src/engine/osk/gesture-processor/tsconfig.json similarity index 61% rename from common/web/gesture-recognizer/tsconfig.json rename to web/src/engine/osk/gesture-processor/tsconfig.json index c4ff34e963f..0b2d3a87b9d 100644 --- a/common/web/gesture-recognizer/tsconfig.json +++ b/web/src/engine/osk/gesture-processor/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.kmw-main-base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "baseUrl": "./", "lib": [ "dom", "es6" ], @@ -8,8 +8,8 @@ "tsBuildInfoFile": "./build/obj/tsconfig.tsbuildinfo", }, "include": ["./src/engine/**/*.ts"], - "exclude": ["./src/test/**/*.ts", "./src/tools/**/*.ts"], "references": [ - { "path": "../utils" } - ] + { "path": "../../../../../common/web/utils" }, + ], + "exclude": ["./src/test/**/*.ts", "./src/tools/**/*.ts"] } diff --git a/web/src/engine/osk/tsconfig.json b/web/src/engine/osk/tsconfig.json index 0f24be8efa0..629443ed50c 100644 --- a/web/src/engine/osk/tsconfig.json +++ b/web/src/engine/osk/tsconfig.json @@ -12,7 +12,7 @@ "include": [ "src/**/*.ts" ], "references": [ - { "path": "../../../../common/web/gesture-recognizer" }, + { "path": "./gesture-processor" }, { "path": "../interfaces" }, { "path": "../dom-utils" }, { "path": "../events" } diff --git a/web/src/test/manual/build.sh b/web/src/test/manual/build.sh index 046cefe1520..905a799ab5d 100755 --- a/web/src/test/manual/build.sh +++ b/web/src/test/manual/build.sh @@ -49,11 +49,11 @@ function do_copy() { cp "$SENTRY_SRC" "$KEYMAN_ROOT/$DEST/sentry-bundle.min.js" cp "$SENTRY_MAP" "$KEYMAN_ROOT/$DEST/sentry-bundle.min.js.map" - GESTURE_RECOGNIZER_BUILD="$KEYMAN_ROOT/common/web/gesture-recognizer/build/lib/." - GESTURE_RECOGNIZER_TARGET="$KEYMAN_ROOT/web/build/engine/gesture-recognizer/lib/" + GESTURE_PROCESSOR_BUILD="$KEYMAN_ROOT/web/src/engine/osk/gesture-processor/build/lib/." + GESTURE_PROCESSOR_TARGET="$KEYMAN_ROOT/web/build/engine/gesture-processor/lib/" - mkdir -p "$GESTURE_RECOGNIZER_TARGET" - cp -a "$GESTURE_RECOGNIZER_BUILD" "$GESTURE_RECOGNIZER_TARGET" + mkdir -p "$GESTURE_PROCESSOR_TARGET" + cp -a "$GESTURE_PROCESSOR_BUILD" "$GESTURE_PROCESSOR_TARGET" } builder_run_action clean rm -rf "$KEYMAN_ROOT/$DEST" diff --git a/web/src/test/manual/web/gesture-recognizer/hostConfiguration.mjs b/web/src/test/manual/web/gesture-recognizer/hostConfiguration.mjs index 7bd1426a587..2e40a296401 100644 --- a/web/src/test/manual/web/gesture-recognizer/hostConfiguration.mjs +++ b/web/src/test/manual/web/gesture-recognizer/hostConfiguration.mjs @@ -1,6 +1,6 @@ // -- BEGIN: Code for controlling the layout-simulation elements -- -import { GestureRecognizer } from '../../../../../build/engine/gesture-recognizer/lib/index.mjs'; +import { GestureRecognizer } from '../../../../../build/engine/gesture-processor/lib/index.mjs'; function updateConfig() { let layout = document.config.screen; From b1fb8122c2f2df887cd193ee7bac167dc64fe966 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 15 Aug 2024 19:40:44 +0200 Subject: [PATCH 156/262] refactor(web): move `gesture-processor` tests under `web/src/test` `gesture-processor/src/test/auto/browser/web-test-runner.config.mjs` run the tests on iOS and Android. However, if we enable these browsers in `web/src/test/auto/dom/web-test-runner.config.mjs` we get failing tests in osk, therefore these browsers are commented for now. --- package-lock.json | 35 ++++---- web/build.sh | 5 ++ web/common.inc.sh | 8 +- web/package.json | 1 + web/src/engine/keyboard/build.sh | 6 -- web/src/engine/osk/gesture-processor/build.sh | 25 ++---- .../engine/osk/gesture-processor/package.json | 5 +- .../browser/web-test-runner.CI.config.mjs | 13 --- .../auto/browser/web-test-runner.config.mjs | 69 --------------- .../gesture-processor/src/test/tsconfig.json | 30 ------- .../osk/gesture-processor/src/tools/build.sh | 55 ++++++------ .../tools/unit-test-resources/src/index.ts | 1 + web/src/engine/osk/gesture-processor/test.sh | 84 ------------------- .../cases/gesture-processor/canary.spec.ts} | 16 ++-- .../cases/gesture-processor}/gestureHost.css | 0 .../gesture-processor}/host-page.spec.html | 8 +- .../gesture-processor/ignoredInputs.spec.ts} | 20 +++-- .../recordedCoordSequences.spec.ts} | 23 ++--- .../cases/keyboard/domKeyboardLoader.spec.ts | 12 +-- .../test/auto/dom/web-test-runner.config.mjs | 37 ++++++-- .../asyncClosureDispatchQueue.spec.ts | 0 .../gesture-processor}/gesturePath.spec.ts | 4 +- .../gesture-processor}/gestureSource.spec.ts | 0 .../gestures/gestureMatcher.spec.ts | 6 +- .../gestures/gestureModelDefs.spec.ts | 0 .../gestures/gestureSequence.spec.ts | 6 +- .../gestures/isolatedGestureSpecs.ts | 0 .../gestures/isolatedPathSpecs.ts | 0 .../gestures/matcherSelector.spec.ts | 6 +- .../gestures/pathMatcher.spec.ts | 2 +- .../gestures/touchpointCoordinator.spec.ts | 6 +- .../osk/gesture-processor}/pathStats.spec.ts | 0 .../auto}/resources/@types/promiseStatus.d.ts | 0 .../resources/assertSegmentSimilarity.js | 0 .../auto}/resources/assertingPromiseStatus.ts | 0 .../auto}/resources/json/canaryRecording.json | 0 .../json/receiver/basicMultitouch.json | 0 .../json/receiver/desktopRoamAndReturn.json | 0 .../json/receiver/embeddedBorderCancel.json | 0 .../json/receiver/hardBorderCancel.json | 0 .../receiver/mobileProximityApproach.json | 0 .../json/receiver/mobileSafeZoneCancel.json | 0 .../json/receiver/popupLongRoamingEnd.json | 0 .../json/receiver/popupSafePersistence.json | 0 .../json/receiver/popupShimCancel.json | 0 .../json/segmentation/flick_ne_se.json | 0 .../json/segmentation/longpress_to_ne.json | 0 .../json/segmentation/nonstationary_hold.json | 0 .../json/segmentation/quick_small_square.json | 0 .../json/segmentation/simple_ne_move.json | 0 .../auto}/resources/sequenceAssertions.ts | 0 .../resources/simulateMultiSourceInput.ts | 0 web/src/test/auto/tsconfig.json | 3 +- 53 files changed, 157 insertions(+), 329 deletions(-) delete mode 100644 web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.CI.config.mjs delete mode 100644 web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs delete mode 100644 web/src/engine/osk/gesture-processor/src/test/tsconfig.json delete mode 100755 web/src/engine/osk/gesture-processor/test.sh rename web/src/{engine/osk/gesture-processor/src/test/auto/browser/cases/canary.def.ts => test/auto/dom/cases/gesture-processor/canary.spec.ts} (87%) rename web/src/{engine/osk/gesture-processor/src/test/auto/browser/cases => test/auto/dom/cases/gesture-processor}/gestureHost.css (100%) rename web/src/{engine/osk/gesture-processor/src/test/auto/browser/cases => test/auto/dom/cases/gesture-processor}/host-page.spec.html (90%) rename web/src/{engine/osk/gesture-processor/src/test/auto/browser/cases/ignoredInputs.def.ts => test/auto/dom/cases/gesture-processor/ignoredInputs.spec.ts} (82%) rename web/src/{engine/osk/gesture-processor/src/test/auto/browser/cases/recordedCoordSequences.def.ts => test/auto/dom/cases/gesture-processor/recordedCoordSequences.spec.ts} (88%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/asyncClosureDispatchQueue.spec.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gesturePath.spec.ts (99%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestureSource.spec.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/gestureMatcher.spec.ts (99%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/gestureModelDefs.spec.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/gestureSequence.spec.ts (99%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/isolatedGestureSpecs.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/isolatedPathSpecs.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/matcherSelector.spec.ts (99%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/pathMatcher.spec.ts (99%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/gestures/touchpointCoordinator.spec.ts (99%) rename web/src/{engine/osk/gesture-processor/src/test/auto/headless => test/auto/headless/engine/osk/gesture-processor}/pathStats.spec.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/@types/promiseStatus.d.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/assertSegmentSimilarity.js (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/assertingPromiseStatus.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/canaryRecording.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/basicMultitouch.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/desktopRoamAndReturn.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/embeddedBorderCancel.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/hardBorderCancel.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/mobileProximityApproach.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/mobileSafeZoneCancel.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/popupLongRoamingEnd.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/popupSafePersistence.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/receiver/popupShimCancel.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/segmentation/flick_ne_se.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/segmentation/longpress_to_ne.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/segmentation/nonstationary_hold.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/segmentation/quick_small_square.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/json/segmentation/simple_ne_move.json (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/sequenceAssertions.ts (100%) rename web/src/{engine/osk/gesture-processor/src/test => test/auto}/resources/simulateMultiSourceInput.ts (100%) diff --git a/package-lock.json b/package-lock.json index 515b2fefc86..05f215f3160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -160,19 +160,6 @@ }, "devDependencies": {} }, - "web/src/engine/osk/gesture-processor": { - "name": "@keymanapp/gesture-recognizer", - "dependencies": { - "eventemitter3": "^5.0.0" - }, - "devDependencies": { - "@keymanapp/resources-gosh": "*", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "promise-status-async": "^1.2.10", - "typescript": "^5.4.5" - } - }, "common/web/keyman-version": { "name": "@keymanapp/keyman-version", "license": "MIT", @@ -405,7 +392,7 @@ "eventemitter3": "^5.0.0", "restructure": "^3.0.1", "sax": ">=0.6.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "xmlbuilder": "~11.0.0" }, "devDependencies": { @@ -1154,7 +1141,7 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "semver": "^7.5.2" + "semver": "^7.5.4" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -1943,7 +1930,7 @@ "open": "^8.4.0", "restructure": "^3.0.1", "sax": ">=0.6.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "ws": "^8.17.1", "xmlbuilder": "~11.0.0" }, @@ -14631,7 +14618,7 @@ "devDependencies": { "@types/semver": "^7.1.0", "@types/yargs": "^17.0.26", - "semver": "^7.5.2" + "semver": "^7.5.4" } }, "resources/build/version/node_modules/ansi-regex": { @@ -14765,6 +14752,20 @@ "mocha": "^10.0.0" } }, + "web/src/engine/osk/gesture-processor": { + "name": "@keymanapp/gesture-recognizer", + "dependencies": { + "@keymanapp/web-utils": "*", + "eventemitter3": "^5.0.0" + }, + "devDependencies": { + "@keymanapp/resources-gosh": "*", + "mocha": "^10.0.0", + "mocha-teamcity-reporter": "^4.0.0", + "promise-status-async": "^1.2.10", + "typescript": "^5.4.5" + } + }, "web/src/engine/predictive-text/types": { "name": "@keymanapp/lm-message-types", "license": "MIT", diff --git a/web/build.sh b/web/build.sh index 16d3d112356..445c4b2e63d 100755 --- a/web/build.sh +++ b/web/build.sh @@ -109,6 +109,11 @@ precompile() { build_action() { builder_echo "Building auto tests..." + + # The currently-bundled declaration file for gesture-processor generates + # errors when compiling against it with current tsc versions. + rm -f "${KEYMAN_ROOT}/node_modules/promise-status-async/lib/index.d.ts" + tsc --project "${KEYMAN_ROOT}/web/src/test/auto/tsconfig.json" for dir in \ diff --git a/web/common.inc.sh b/web/common.inc.sh index 9457fa5a416..8edfb9f4fa3 100644 --- a/web/common.inc.sh +++ b/web/common.inc.sh @@ -10,6 +10,8 @@ BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" # ### Parameters # # * 1: `product` the product's source path under src/ +# * 2: `src_dir` the source directory. Optional. Default: ${KEYMAN_ROOT}/web/src +# * 3: `build_dir` the build directory. Optional. Default: ${KEYMAN_ROOT}/web/build # # ### Example # @@ -22,11 +24,13 @@ function compile() { fi local COMPILE_TARGET="$1" + local SRC_DIR=${2:-"${KEYMAN_ROOT}/web/src"} + local BUILD_DIR=${3:-"${KEYMAN_ROOT}/web/build"} - tsc -b "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" + tsc -b "${SRC_DIR}/$COMPILE_TARGET" # So... tsc does declaration-bundling on its own pretty well, at least for local development. - tsc --emitDeclarationOnly --outFile "${KEYMAN_ROOT}/web/build/$COMPILE_TARGET/lib/index.d.ts" -p "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" + tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/$COMPILE_TARGET/lib/index.d.ts" -p "${SRC_DIR}/$COMPILE_TARGET" } function _copy_dir_if_exists() { diff --git a/web/package.json b/web/package.json index 96a11c0cd3e..5c6684afd30 100644 --- a/web/package.json +++ b/web/package.json @@ -89,6 +89,7 @@ } }, "imports": { + "#gesture-tools": "./src/engine/osk/gesture-processor/build/tools/obj/index.js", "#recorder": "./build/tools/testing/recorder/obj/index.js" }, "repository": { diff --git a/web/src/engine/keyboard/build.sh b/web/src/engine/keyboard/build.sh index 226a7d9d754..7b04e478a09 100755 --- a/web/src/engine/keyboard/build.sh +++ b/web/src/engine/keyboard/build.sh @@ -66,12 +66,6 @@ function do_build() { --format esm \ --platform node - # # Tests - # builder_echo "Bundle tests" - # ${BUNDLE_CMD} "${BUILD_DIR}/tests/dom/cases/domKeyboardLoader.spec.js" \ - # --out "${BUILD_DIR}/tests/dom/domKeyboardLoader.spec.mjs" \ - # --format esm - # Declaration bundling. builder_echo "Declaration bundling" tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/lib/index.d.ts" diff --git a/web/src/engine/osk/gesture-processor/build.sh b/web/src/engine/osk/gesture-processor/build.sh index 37cd7856235..f3bafab5ed5 100755 --- a/web/src/engine/osk/gesture-processor/build.sh +++ b/web/src/engine/osk/gesture-processor/build.sh @@ -6,7 +6,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$(dirname "$THIS_SCRIPT")/../../../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +SUBPROJECT_NAME=engine/osk/gesture-processor + +. "${KEYMAN_ROOT}/web/common.inc.sh" +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" @@ -37,10 +40,6 @@ builder_parse "$@" function do_configure() { verify_npm_setup - - # Configure Web browser-engine testing environments. As is, this should only - # make changes when we update the dependency, even on our CI build agents. - playwright install } function do_build_module() { @@ -52,18 +51,6 @@ function do_build_module() { --format esm } -function do_build_tools() { - src/tools/build.sh build -} - -function do_test_module() { - if builder_has_option --ci; then - ./test.sh --ci - else - ./test.sh - fi -} - function do_test_tools() { if ! builder_has_action test:module; then echo "The $(builder_term test:tools) action is currently a no-op." @@ -73,6 +60,6 @@ function do_test_tools() { builder_run_action configure do_configure builder_run_action clean rm -rf build/ intermediate/ builder_run_action build:module do_build_module -builder_run_action build:tools do_build_tools -builder_run_action test:module do_test_module +builder_run_action build:tools src/tools/build.sh build +builder_run_action test:module test-headless "${SUBPROJECT_NAME}" builder_run_action test:tools do_test_tools diff --git a/web/src/engine/osk/gesture-processor/package.json b/web/src/engine/osk/gesture-processor/package.json index 9ec0606ebd9..fd8e7fcddfb 100644 --- a/web/src/engine/osk/gesture-processor/package.json +++ b/web/src/engine/osk/gesture-processor/package.json @@ -8,12 +8,9 @@ "promise-status-async": "^1.2.10", "typescript": "^5.4.5" }, - "imports": { - "#tools": "./build/tools/obj/index.js" - }, "scripts": { "build": "gosh ./build.sh", - "test": "gosh ./test.sh" + "test": "gosh ./build.sh test" }, "dependencies": { "@keymanapp/web-utils": "*", diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.CI.config.mjs b/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.CI.config.mjs deleted file mode 100644 index d18a8a9cb09..00000000000 --- a/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.CI.config.mjs +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-check -import BASE_CONFIG from './web-test-runner.config.mjs'; -import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; -import { sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; - -/** @type {import('@web/test-runner').TestRunnerConfig} */ -export default { - ...BASE_CONFIG, - reporters: [ - teamcityReporter(), /* custom-written, for CI-friendly reports */ - sessionStabilityReporter({ciMode: true}) - ] -} \ No newline at end of file diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs b/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs deleted file mode 100644 index 4487912e0d9..00000000000 --- a/web/src/engine/osk/gesture-processor/src/test/auto/browser/web-test-runner.config.mjs +++ /dev/null @@ -1,69 +0,0 @@ -// @ts-check -import { devices, playwrightLauncher } from '@web/test-runner-playwright'; -import { defaultReporter, summaryReporter } from '@web/test-runner'; -import { LauncherWrapper, sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; -import named from '@keymanapp/common-test-resources/test-runner-rename-browser.mjs' -import { esbuildPlugin } from '@web/dev-server-esbuild'; -import { importMapsPlugin } from '@web/dev-server-import-maps'; -import { dirname, resolve } from 'path'; -import { fileURLToPath } from 'url'; - -const dir = dirname(fileURLToPath(import.meta.url)); -const KEYMAN_ROOT = resolve(dir, '../../../../../../../'); - -/** @type {import('@web/test-runner').TestRunnerConfig} */ -export default { - // debug: true, - browsers: [ - new LauncherWrapper(playwrightLauncher({ product: 'chromium' })), - new LauncherWrapper(playwrightLauncher({ product: 'firefox' })), - playwrightLauncher({ product: 'webkit', concurrency: 1}), - named(new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1, createBrowserContext({browser}) { - return browser.newContext({...devices['iPhone X'] }); - }})), 'iOS Phone (emulated)'), - named(new LauncherWrapper(playwrightLauncher({ product: 'chromium' , createBrowserContext({browser}) { - return browser.newContext({...devices['Pixel 4'] }) - }})), 'Android Phone (emulated)'), - ], - concurrency: 10, - nodeResolve: true, - files: [ - '**/*.spec.html' - ], - middleware: [ - // Rewrites short-hand paths for test resources, making them fully relative to the repo root. - function rewriteResourcePath(context, next) { - if(context.url.startsWith('/resources/')) { - context.url = '/web/src/engine/osk/gesture-processor/src/test' + context.url; - } - - return next(); - } - ], - plugins: [ - esbuildPlugin({ ts: true, target: 'auto'}), - importMapsPlugin({ - inject: { - importMap: { - // Redirects `eventemitter3` imports to the bundled ESM library. The standard import is an - // ESM wrapper around the CommonJS implementation, and WTR fails when it hits the CommonJS. - imports: { - 'eventemitter3': '/node_modules/eventemitter3/dist/eventemitter3.esm.js' - } - } - } - }) - ], - reporters: [ - summaryReporter({}), /* local-dev mocha-style */ - sessionStabilityReporter({}), - defaultReporter({}) - ], - /* - Un-comment the next two lines for easy interactive debugging; it'll launch the - test page in your preferred browser. - */ - // open: true, - // manual: true, - rootDir: KEYMAN_ROOT -} \ No newline at end of file diff --git a/web/src/engine/osk/gesture-processor/src/test/tsconfig.json b/web/src/engine/osk/gesture-processor/src/test/tsconfig.json deleted file mode 100644 index 4eea3ef803f..00000000000 --- a/web/src/engine/osk/gesture-processor/src/test/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -/* - * VS Code Intellisense needs this helper in order to properly use subpath imports in the - * test specs found under the `headless` subfolder. Otherwise, it'll report errors while - * editing - even if the tests themselves actually work. - */ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "../../build/test/", - // Not needed when testing via Node, and when `true` it seems to desync preset breakpoints - // worse than when `false`. - "importHelpers": false, - - "allowJs": true, - - "baseUrl": "../../", - "tsBuildInfoFile": "../../build/test/tsconfig.tsbuildinfo", - "rootDir": "./", - "typeRoots": ["./resources/@types", "../../../../../node_modules/@types"] - }, - "include": [ - "./auto/headless/**/*.ts", - "./resources/**/*.ts" - ], - // Undo the base config's exclude. - "exclude": ["../../../../../node_modules/promise-status-async/lib/index.d.ts"], - "references": [ - {"path": "../../"} - ] -} diff --git a/web/src/engine/osk/gesture-processor/src/tools/build.sh b/web/src/engine/osk/gesture-processor/src/tools/build.sh index ca951721b61..1813ab62a88 100755 --- a/web/src/engine/osk/gesture-processor/src/tools/build.sh +++ b/web/src/engine/osk/gesture-processor/src/tools/build.sh @@ -6,6 +6,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$(dirname "$THIS_SCRIPT")/../../../../../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" @@ -33,34 +34,18 @@ builder_describe_outputs \ # TODO: build if out-of-date if test is specified # TODO: configure if npm has not been run, and build is specified -if builder_start_action clean:recorder; then - rm -rf ./recorder/build - builder_finish_action success clean:recorder -fi - -if builder_start_action clean:fixture; then +function do_clean_fixture() { rm -f ../../build/tools/host-fixture.html rm -f ../../build/tools/gestureHost.css - builder_finish_action success clean:fixture -fi +} -if builder_start_action clean:test-module; then +function do_clean_testmodule() { rm -rf ../../build/tools/*.ts* rm -rf ../../build/tools/*.js* - builder_finish_action success clean:test-module -fi - -if builder_start_action build:fixture; then - if [ ! -d ../../build/tools ]; then - mkdir -p ../../build/tools - fi - ./host-fixture/extract-fixture.sh > ../../build/tools/host-fixture.html - cp ./host-fixture/gestureHost.css ../../build/tools/gestureHost.css - builder_finish_action success build:fixture -fi +} -if builder_start_action build:recorder; then - if [ ! -d recorder/build ]; then +function do_build_recorder() { + if [[ ! -d recorder/build ]]; then mkdir -p recorder/build fi cp recorder/src/pageStyle.css recorder/build/pageStyle.css @@ -73,15 +58,27 @@ if builder_start_action build:recorder; then pushd recorder >/dev/null node update-index.cjs build/index.html popd >/dev/null - builder_finish_action success build:recorder -fi +} -if builder_start_action build:test-module; then - tsc -b "$THIS_SCRIPT_PATH/unit-test-resources/tsconfig.json" +function do_build_fixture() { + if [[ ! -d ../../build/tools ]]; then + mkdir -p ../../build/tools + fi + ./host-fixture/extract-fixture.sh > ../../build/tools/host-fixture.html + cp ./host-fixture/gestureHost.css ../../build/tools/gestureHost.css +} + +function do_build_testmodule() { + compile "" "${THIS_SCRIPT_PATH}/unit-test-resources" "${KEYMAN_ROOT}/${BUILD_DIR}/tools" $BUNDLE_CMD "${KEYMAN_ROOT}/${BUILD_DIR}/tools/obj/index.js" \ --out "${KEYMAN_ROOT}/${BUILD_DIR}/tools/lib/index.mjs" \ --format "esm" - - builder_finish_action success build:test-module -fi \ No newline at end of file +} + +builder_run_action clean:recorder rm -rf ./recorder/build +builder_run_action clean:fixture do_clean_fixture +builder_run_action clean:test-module do_clean_testmodule +builder_run_action build:recorder do_build_recorder +builder_run_action build:fixture do_build_fixture +builder_run_action build:test-module do_build_testmodule diff --git a/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/index.ts b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/index.ts index e3bf02cc37c..dfc9740330d 100644 --- a/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/index.ts +++ b/web/src/engine/osk/gesture-processor/src/tools/unit-test-resources/src/index.ts @@ -1,6 +1,7 @@ export * from './fixtureLayoutConfiguration.js'; export * from './headlessInputEngine.js'; export * from './hostFixtureLayoutController.js'; +export * from './inputRecording.js'; export * from './inputSequenceSimulator.js'; export * from './sequenceRecorder.js'; export * from './touchpathTurtle.js'; \ No newline at end of file diff --git a/web/src/engine/osk/gesture-processor/test.sh b/web/src/engine/osk/gesture-processor/test.sh deleted file mode 100755 index f1c50eeed49..00000000000 --- a/web/src/engine/osk/gesture-processor/test.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash - -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" -. "$(dirname "$THIS_SCRIPT")/../../../../../resources/build/build-utils.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -. $KEYMAN_ROOT/resources/shellHelperFunctions.sh - -# This script runs from its own folder -cd "$(dirname $THIS_SCRIPT)" - -################################ Main script ################################ - -builder_describe "Runs all tests for the gesture-processor module" \ - "@../gesture-processor" \ - "test+" \ - ":headless Runs headless user tests" \ - ":browser Runs browser-based user tests" \ - "--ci Uses CI-based test configurations & emits CI-friendly test reports" - -builder_parse "$@" - -# TODO: build if out-of-date if test is specified -# TODO: configure if npm has not been run, and build is specified - -# START - Script parameter configuration -REPORT_STYLE="local" # Default setting. - -if builder_has_option --ci; then - REPORT_STYLE="ci" - - echo "Replacing user-friendly test reports & configurations with CI-friendly versions." -fi - -# END - Script parameter configuration - -test-headless ( ) { - # During debugging, "--slow 0" allows reporting the duration of ALL tests, not just the ones that run long. - # Can be useful... but probably shouldn't be the default. - MOCHA_FLAGS= - - if [ $REPORT_STYLE == "ci" ]; then - MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter" - fi - - # The currently-bundled declaration file for this package generates errors when compiling against it - # with current tsc versions. - rm -f "${KEYMAN_ROOT}/node_modules/promise-status-async/lib/index.d.ts" - - tsc -b ./src/test/tsconfig.json - c8 mocha --recursive $MOCHA_FLAGS ./build/test/auto/headless/ -} - -test-browser ( ) { - local WTR_DEBUG= - local WTR_CONFIG= - if [[ $# -eq 1 && $1 == "debug" ]]; then - WTR_DEBUG=" --manual" - elif [ $REPORT_STYLE != "local" ]; then - WTR_CONFIG=.CI - fi - - web-test-runner --config src/test/auto/browser/web-test-runner${WTR_CONFIG}.config.mjs ${WTR_DEBUG} -} - -if builder_start_action test:headless; then - test-headless - builder_finish_action success test:headless -fi - -if builder_start_action test:browser; then - if builder_has_option --debug; then - echo "Running browser-based unit tests in debug-mode configuration..." - echo - echo "${COLOR_YELLOW}You must manually terminate this mode (CTRL-C) for the script to exit.${COLOR_RESET}" - sleep 2 - test-browser debug - else - test-browser - fi - builder_finish_action success test:browser -fi \ No newline at end of file diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/canary.def.ts b/web/src/test/auto/dom/cases/gesture-processor/canary.spec.ts similarity index 87% rename from web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/canary.def.ts rename to web/src/test/auto/dom/cases/gesture-processor/canary.spec.ts index 241977b3221..78fbcf6f793 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/canary.def.ts +++ b/web/src/test/auto/dom/cases/gesture-processor/canary.spec.ts @@ -2,10 +2,14 @@ import { assert } from 'chai'; import sinon from 'sinon'; import { + DeviceLayoutClass, FixtureLayoutConfiguration, HostFixtureLayoutController, - InputSequenceSimulator -} from '#tools'; + InputSequenceSimulator, + ReceiverLayoutClass, + RoamingLayoutClass, + SafeLayoutClass +} from '#gesture-tools'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; @@ -75,11 +79,11 @@ describe("'Canary' checks", function() { it("InputSequenceSimulator.replayTouchSample", async function() { let playbackEngine = new InputSequenceSimulator(this.controller); - let layout = new FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + let layout = new FixtureLayoutConfiguration("screen2" as DeviceLayoutClass, "bounds1" as RoamingLayoutClass, "full" as ReceiverLayoutClass, "safe-loose" as SafeLayoutClass); this.controller.layoutConfiguration = layout; let fireEvent = () => { - playbackEngine.replayTouchSamples(/*relative coord:*/ [ { sample: {targetX: 10, targetY: 10}, identifier: 1}], + playbackEngine.replayTouchSamples(/*relative coord:*/ [ { sample: {targetX: 10, targetY: 10, t: 0}, identifier: 1}], /*state:*/ "start", /*recentTouches:*/ [], ); @@ -101,11 +105,11 @@ describe("'Canary' checks", function() { it("InputSequenceSimulator.replayMouseSample", async function() { let playbackEngine = new InputSequenceSimulator(this.controller); - let layout = new FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + let layout = new FixtureLayoutConfiguration("screen2" as DeviceLayoutClass, "bounds1" as RoamingLayoutClass, "full" as ReceiverLayoutClass, "safe-loose" as SafeLayoutClass); this.controller.layoutConfiguration = layout; let fireEvent = () => { - playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: 15, targetY: 15}, + playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: 15, targetY: 15, t: 0}, /*state:*/ "start" ); } diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/gestureHost.css b/web/src/test/auto/dom/cases/gesture-processor/gestureHost.css similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/gestureHost.css rename to web/src/test/auto/dom/cases/gesture-processor/gestureHost.css diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/host-page.spec.html b/web/src/test/auto/dom/cases/gesture-processor/host-page.spec.html similarity index 90% rename from web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/host-page.spec.html rename to web/src/test/auto/dom/cases/gesture-processor/host-page.spec.html index c4a4d7a47e8..ce3aae6c484 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/host-page.spec.html +++ b/web/src/test/auto/dom/cases/gesture-processor/host-page.spec.html @@ -1,6 +1,6 @@ + imports for the .spec.js test suites. --> @@ -18,9 +18,9 @@ import { runTests } from '@web/test-runner-mocha'; runTests(async() => { - await import('./canary.def.ts'); - await import('./ignoredInputs.def.ts'); - await import('./recordedCoordSequences.def.ts'); + await import('./canary.spec.ts'); + await import('./ignoredInputs.spec.ts'); + await import('./recordedCoordSequences.spec.ts'); }); diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/ignoredInputs.def.ts b/web/src/test/auto/dom/cases/gesture-processor/ignoredInputs.spec.ts similarity index 82% rename from web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/ignoredInputs.def.ts rename to web/src/test/auto/dom/cases/gesture-processor/ignoredInputs.spec.ts index b107fdea5d2..a288f925a37 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/ignoredInputs.def.ts +++ b/web/src/test/auto/dom/cases/gesture-processor/ignoredInputs.spec.ts @@ -5,8 +5,12 @@ import { FixtureLayoutConfiguration, HostFixtureLayoutController, InputSequenceSimulator, - SequenceRecorder -} from '#tools'; + SequenceRecorder, + DeviceLayoutClass, + RoamingLayoutClass, + ReceiverLayoutClass, + SafeLayoutClass +} from '#gesture-tools'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; @@ -28,19 +32,20 @@ describe("Layer one - DOM -> InputSequence", function() { it("starts in roaming zone are ignored", function() { let playbackEngine = new InputSequenceSimulator(controller); let recorder = new SequenceRecorder(controller); - let layout = new FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + let layout = new FixtureLayoutConfiguration("screen2" as DeviceLayoutClass, "bounds1" as RoamingLayoutClass, "full" as ReceiverLayoutClass, "safe-loose" as SafeLayoutClass); controller.layoutConfiguration = layout; let fireEvent = () => { - playbackEngine.replayTouchSamples(/*relative coord:*/ [{sample: {targetX: 10, targetY: -5}, identifier: 1}], + playbackEngine.replayTouchSamples(/*relative coord:*/ [{sample: {targetX: 10, targetY: -5, t: 0}, identifier: 1}], /*state:*/ "start", /*recentTouches:*/ [], - /*targetElement:*/ controller.recognizer.config.maxRoamingBounds + /*targetElement:*/ controller.recognizer.config.maxRoamingBounds as HTMLElement ); } // This test is invalidated if the handler itself isn't called. So... let's verify that! // This requires white-box inspection of the actual handler control-flow, and we must do + // @ts-ignore // touchEngine is private let touchEngine = controller.recognizer.touchEngine; let trueHandler = touchEngine.onTouchStart; let fakeHandler = touchEngine.onTouchStart = sinon.fake(); @@ -61,11 +66,11 @@ describe("Layer one - DOM -> InputSequence", function() { it("ignores target-external events", function() { let playbackEngine = new InputSequenceSimulator(controller); let recorder = new SequenceRecorder(controller); - let layout = new FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + let layout = new FixtureLayoutConfiguration("screen2" as DeviceLayoutClass, "bounds1" as RoamingLayoutClass, "full" as ReceiverLayoutClass, "safe-loose" as SafeLayoutClass); controller.layoutConfiguration = layout; let fireEvent = () => { - playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: -5, targetY: 15}, + playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: -5, targetY: 15, t: 0}, /*state:*/ "start", /*targetElement:*/ document.body ); @@ -73,6 +78,7 @@ describe("Layer one - DOM -> InputSequence", function() { // This test is invalidated if the handler itself isn't called. So... let's verify that! // Not quite covered by the canary cases b/c of the distinct targetElement. + // @ts-ignore // touchEngine is private let mouseEngine = controller.recognizer.mouseEngine; let trueHandler = mouseEngine.onMouseStart; let fakeHandler = mouseEngine.onMouseStart = sinon.fake(); diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/recordedCoordSequences.def.ts b/web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.spec.ts similarity index 88% rename from web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/recordedCoordSequences.def.ts rename to web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.spec.ts index 95af23b61da..06136c66a79 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/browser/cases/recordedCoordSequences.def.ts +++ b/web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.spec.ts @@ -5,12 +5,13 @@ import type { GestureDebugSource, InputSample, SerializedGestureSource } from '@ import { HostFixtureLayoutController, - InputSequenceSimulator -} from '#tools'; + InputSequenceSimulator, + RecordedCoordSequenceSet +} from '#gesture-tools'; function isOnAndroid() { const agent=navigator.userAgent; - return agent.indexOf('Android' >= 0); + return agent.indexOf('Android') >= 0; } const loc = document.location; @@ -18,7 +19,7 @@ const loc = document.location; // filesystem for the drive. const domain = `${loc.protocol}/${loc.host}` -async function fetchRecording(jsonFilename) { +async function fetchRecording(jsonFilename: string): Promise { const jsonResponse = await fetch(new URL(`${domain}/resources/json/${jsonFilename}.json`)); return await jsonResponse.json(); } @@ -47,7 +48,7 @@ describe("Layer one - DOM -> InputSequence", function() { }); // We rely on this function to have the same context as `it` - the test-definition function. - let replayAndCompare = function(testObj) { + let replayAndCompare = function(testObj: RecordedCoordSequenceSet, mochaContext: Mocha.Context) { let playbackEngine = new InputSequenceSimulator(controller); // ********************************** @@ -68,18 +69,18 @@ describe("Layer one - DOM -> InputSequence", function() { // replayAsync sets up timeouts against the `clock` object. // This will run through the simulated timeout queue asynchronously. - this.clock.runAllAsync(); + mochaContext.clock.runAllAsync(); // resultPromise resolves on the final timeout in the queue. - return resultPromise.then((result) => { + return resultPromise.then((result: RecordedCoordSequenceSet) => { assert.equal(result.inputs.length, testObj.inputs.length); // Removes the timestamp element; we know that this component may not match perfectly. - let sampleCleaner = (sample) => { return {targetX: sample.targetX, targetY: sample.targetY} }; + let sampleCleaner = (sample: InputSample) => { return {targetX: sample.targetX, targetY: sample.targetY} }; // Returns just the observed, cleaned samples for a sequence object. The recorded coordinates // should match perfectly. - let seqCleaner = (input) => { + let seqCleaner = (input: SerializedGestureSource) => { return input.path.coords.map(sampleCleaner); }; @@ -124,7 +125,7 @@ describe("Layer one - DOM -> InputSequence", function() { // The 'terminationEvent' property should match. Any sequence that was "canceled" should still // cancel; that's a pretty critical detail! - let terminationEventMapper = (seq) => { + let terminationEventMapper = (seq: SerializedGestureSource) => { return seq.path.wasCancelled; } @@ -151,7 +152,7 @@ describe("Layer one - DOM -> InputSequence", function() { // 'describe' has a notably different `this` reference than `it`, `before`, etc, // hence the `.call` construction. - return replayAndCompare.call(this, testObj); + return replayAndCompare.call(this, testObj, this); }); } }); diff --git a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts index da610402eb0..95db91b0f8f 100644 --- a/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts +++ b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts @@ -30,7 +30,7 @@ describe('Keyboard loading in DOM', function() { it('`window`, disabled rule processing', async () => { const harness = new KeyboardHarness(window, MinimalKeymanGlobal); let keyboardLoader = new DOMKeyboardLoader(harness); - let keyboard: Keyboard = await keyboardLoader.loadKeyboardFromPath('/resources/keyboards/khmer_angkor.js'); + let keyboard: Keyboard = await keyboardLoader.loadKeyboardFromPath('/common/test/resources/keyboards/khmer_angkor.js'); assert.isOk(keyboard); assert.equal(keyboard.id, 'Keyboard_khmer_angkor'); @@ -48,7 +48,7 @@ describe('Keyboard loading in DOM', function() { it('`window`, enabled rule processing', async () => { const harness = new KeyboardInterface(window, MinimalKeymanGlobal); const keyboardLoader = new DOMKeyboardLoader(harness); - const keyboard: Keyboard = await keyboardLoader.loadKeyboardFromPath('/resources/keyboards/khmer_angkor.js'); + const keyboard: Keyboard = await keyboardLoader.loadKeyboardFromPath('/common/test/resources/keyboards/khmer_angkor.js'); harness.activeKeyboard = keyboard; assert.isOk(keyboard); @@ -80,14 +80,14 @@ describe('Keyboard loading in DOM', function() { let keyboardLoader = new DOMKeyboardLoader(harness); // Preload a keyboard and make it active. - const test_kbd: Keyboard = await keyboardLoader.loadKeyboardFromPath('/resources/keyboards/test_917.js'); + const test_kbd: Keyboard = await keyboardLoader.loadKeyboardFromPath('/common/test/resources/keyboards/test_917.js'); harness.activeKeyboard = test_kbd; assert.isNotOk(harness.loadedKeyboard); // With an active keyboard, load three keyboards but activate none of them. - const lao_keyboard_promise = keyboardLoader.loadKeyboardFromPath('/resources/keyboards/lao_2008_basic.js'); - const khmer_keyboard_promise = keyboardLoader.loadKeyboardFromPath('/resources/keyboards/khmer_angkor.js'); - const chiral_keyboard_promise = keyboardLoader.loadKeyboardFromPath('/resources/keyboards/test_chirality.js'); + const lao_keyboard_promise = keyboardLoader.loadKeyboardFromPath('/common/test/resources/keyboards/lao_2008_basic.js'); + const khmer_keyboard_promise = keyboardLoader.loadKeyboardFromPath('/common/test/resources/keyboards/khmer_angkor.js'); + const chiral_keyboard_promise = keyboardLoader.loadKeyboardFromPath('/common/test/resources/keyboards/test_chirality.js'); // Sure, why not `await` out of order? const chiral_keyboard = await chiral_keyboard_promise; diff --git a/web/src/test/auto/dom/web-test-runner.config.mjs b/web/src/test/auto/dom/web-test-runner.config.mjs index 62edfc7dbf9..3050837cd55 100644 --- a/web/src/test/auto/dom/web-test-runner.config.mjs +++ b/web/src/test/auto/dom/web-test-runner.config.mjs @@ -1,8 +1,8 @@ // @ts-check import { devices, playwrightLauncher } from '@web/test-runner-playwright'; import { defaultReporter, summaryReporter } from '@web/test-runner'; -import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; import { LauncherWrapper, sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; +import named from '@keymanapp/common-test-resources/test-runner-rename-browser.mjs' import { importMapsPlugin } from '@web/dev-server-import-maps'; import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; @@ -17,7 +17,17 @@ export default { browsers: [ new LauncherWrapper(playwrightLauncher({ product: 'chromium' })), new LauncherWrapper(playwrightLauncher({ product: 'firefox' })), - new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1 })) + new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1 })), + // named(new LauncherWrapper(playwrightLauncher({ + // product: 'webkit', concurrency: 1, createBrowserContext({ browser }) { + // return browser.newContext({ ...devices['iPhone X'] }); + // } + // })), 'iOS Phone (emulated)'), + // named(new LauncherWrapper(playwrightLauncher({ + // product: 'chromium', createBrowserContext({ browser }) { + // return browser.newContext({ ...devices['Pixel 4'] }) + // } + // })), 'Android Phone (emulated)'), ], concurrency: 10, nodeResolve: true, @@ -51,14 +61,25 @@ export default { files: ['build/test/dom/cases/element-wrappers/**/*.spec.mjs'] }, { - name: 'engine/osk', + name: 'engine/gesture-processor', // Relative, from the containing package.json - files: ['build/test/dom/cases/osk/**/*.spec.mjs'] + // Note: here we use the .spec.html file in the src directory! + files: ['src/test/auto/dom/cases/gesture-processor/**/*.spec.html'] + }, + { + name: 'engine/keyboard', + // Relative, from the containing package.json + files: ['build/test/dom/cases/keyboard/**/*.spec.mjs'] }, { name: 'engine/keyboard-storage', // Relative, from the containing package.json files: ['build/test/dom/cases/keyboard-storage/**/*.spec.mjs'] + }, + { + name: 'engine/osk', + // Relative, from the containing package.json + files: ['build/test/dom/cases/osk/**/*.spec.mjs'] } ], middleware: [ @@ -72,7 +93,7 @@ export default { } ], plugins: [ - esbuildPlugin({ts: true}), + esbuildPlugin({ts: true, target: 'auto'}), importMapsPlugin({ inject: { importMap: { @@ -88,8 +109,12 @@ export default { reporters: [ summaryReporter({}), /* local-dev mocha-style */ sessionStabilityReporter({}), - defaultReporter() + defaultReporter({}) ], + /* + Un-comment the next two lines for easy interactive debugging; it'll launch the + test page in your preferred browser. + */ // open: true, // manual: true, rootDir: KEYMAN_ROOT diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/asyncClosureDispatchQueue.spec.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/asyncClosureDispatchQueue.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/asyncClosureDispatchQueue.spec.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gesturePath.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.spec.ts similarity index 99% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gesturePath.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.spec.ts index 111e00ee93c..1159d969fad 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gesturePath.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.spec.ts @@ -6,14 +6,14 @@ import fs from 'fs'; import { GestureDebugPath } from '@keymanapp/gesture-recognizer'; import { timedPromise } from '@keymanapp/web-utils'; -import { TouchpathTurtle } from '#tools'; +import { TouchpathTurtle } from '#gesture-tools'; // Ensures that the resources are resolved relative to this script's source, not // to the cwd when the test runner was launched or to its built version. const scriptFolder = path.dirname(url.fileURLToPath(import.meta.url)) .replace("build/test/auto", "src/test/auto") // Mac/Linux .replace("build\\test\\auto", "src\\test\\auto"); // Windows -const SEGMENT_TEST_JSON_FOLDER = path.resolve(`${scriptFolder}/../../resources/json/segmentation`); +const SEGMENT_TEST_JSON_FOLDER = path.resolve(`${scriptFolder}/../../../../resources/json/segmentation`); describe("GesturePath", function() { // Note: if part of the suite below fails, it'll probably cascade into failures for some of the diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestureSource.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestureSource.spec.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestureSource.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestureSource.spec.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureMatcher.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.spec.ts similarity index 99% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureMatcher.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.spec.ts index aea79a15a96..ede257ca758 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureMatcher.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.spec.ts @@ -3,13 +3,13 @@ import sinon from 'sinon'; import * as PromiseStatusModule from 'promise-status-async'; const PromiseStatuses = PromiseStatusModule.PromiseStatuses; -import { assertingPromiseStatus as promiseStatus } from '../../../resources/assertingPromiseStatus.js'; +import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js'; import { InputSample, gestures, GestureDebugPath } from '@keymanapp/gesture-recognizer'; -import { TouchpathTurtle } from '#tools'; +import { TouchpathTurtle } from '#gesture-tools'; -import { simulateMultiSourceMatcherInput } from "../../../resources/simulateMultiSourceInput.js"; +import { simulateMultiSourceMatcherInput } from "../../../../../resources/simulateMultiSourceInput.js"; import { FlickEndModel, diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureModelDefs.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureModelDefs.spec.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureModelDefs.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureModelDefs.spec.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureSequence.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.spec.ts similarity index 99% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureSequence.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.spec.ts index 346e146809e..fe3837e42fe 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/gestureSequence.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.spec.ts @@ -2,7 +2,7 @@ import { assert } from 'chai' import sinon from 'sinon'; import * as PromiseStatusModule from 'promise-status-async'; -import { assertingPromiseStatus as promiseStatus } from '../../../resources/assertingPromiseStatus.js'; +import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js'; import { GestureModelDefs, buildGestureMatchInspector, gestures } from '@keymanapp/gesture-recognizer'; const { matchers } = gestures; @@ -16,10 +16,10 @@ type MatcherSelection = gestures.matchers.MatcherSelection; const getGestureModelSet = gestures.specs.getGestureModelSet; const modelSetForAction = gestures.matchers.modelSetForAction; -import { HeadlessInputEngine, TouchpathTurtle } from '#tools'; +import { HeadlessInputEngine, TouchpathTurtle } from '#gesture-tools'; import { ManagedPromise, timedPromise } from '@keymanapp/web-utils'; -import { assertGestureSequence, SequenceAssertion } from "../../../resources/sequenceAssertions.js"; +import { assertGestureSequence, SequenceAssertion } from "../../../../../resources/sequenceAssertions.js"; import { LongpressModel, diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedGestureSpecs.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/isolatedGestureSpecs.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedGestureSpecs.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/isolatedGestureSpecs.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedPathSpecs.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/isolatedPathSpecs.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/isolatedPathSpecs.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/isolatedPathSpecs.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/matcherSelector.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.spec.ts similarity index 99% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/matcherSelector.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.spec.ts index 8fdfd936a62..583e0c60312 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/matcherSelector.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.spec.ts @@ -3,14 +3,14 @@ import sinon from 'sinon'; import * as PromiseStatusModule from 'promise-status-async'; const PromiseStatuses = PromiseStatusModule.PromiseStatuses; -import { assertingPromiseStatus as promiseStatus } from '../../../resources/assertingPromiseStatus.js'; +import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js'; -import { simulateMultiSourceMatcherInput, simulateSelectorInput } from "../../../resources/simulateMultiSourceInput.js"; +import { simulateMultiSourceMatcherInput, simulateSelectorInput } from "../../../../../resources/simulateMultiSourceInput.js"; import { timedPromise } from '@keymanapp/web-utils'; import { gestures } from '@keymanapp/gesture-recognizer'; -import { TouchpathTurtle } from '#tools'; +import { TouchpathTurtle } from '#gesture-tools'; type MatcherSelection = gestures.matchers.MatcherSelection; type GestureModel = gestures.specs.GestureModel; diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/pathMatcher.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.spec.ts similarity index 99% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/pathMatcher.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.spec.ts index 4678efed715..ac2250f5f97 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/pathMatcher.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.spec.ts @@ -3,7 +3,7 @@ import sinon from 'sinon'; import * as PromiseStatusModule from 'promise-status-async'; const PromiseStatuses = PromiseStatusModule.PromiseStatuses; -import { assertingPromiseStatus as promiseStatus } from '../../../resources/assertingPromiseStatus.js'; +import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js'; import { InputSample, GestureSource, gestures, CumulativePathStats } from '@keymanapp/gesture-recognizer'; import { timedPromise } from '@keymanapp/web-utils'; diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.spec.ts similarity index 99% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.spec.ts index 784d7e30b66..44f0bce4f77 100644 --- a/web/src/engine/osk/gesture-processor/src/test/auto/headless/gestures/touchpointCoordinator.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.spec.ts @@ -2,7 +2,7 @@ import { assert } from 'chai' import sinon from 'sinon'; import * as PromiseStatusModule from 'promise-status-async'; -import { assertingPromiseStatus as promiseStatus } from '../../../resources/assertingPromiseStatus.js'; +import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js'; import { GestureModelDefs, GestureSource, gestures, TouchpointCoordinator } from '@keymanapp/gesture-recognizer'; const { matchers } = gestures; @@ -11,10 +11,10 @@ const { matchers } = gestures; const { GestureSequence } = matchers; type GestureSequence = gestures.matchers.GestureSequence; -import { HeadlessInputEngine, TouchpathTurtle } from '#tools'; +import { HeadlessInputEngine, TouchpathTurtle } from '#gesture-tools'; import { ManagedPromise, timedPromise } from '@keymanapp/web-utils'; -import { assertGestureSequence, SequenceAssertion } from "../../../resources/sequenceAssertions.js"; +import { assertGestureSequence, SequenceAssertion } from "../../../../../resources/sequenceAssertions.js"; import { LongpressModel, diff --git a/web/src/engine/osk/gesture-processor/src/test/auto/headless/pathStats.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/pathStats.spec.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/auto/headless/pathStats.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/pathStats.spec.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/@types/promiseStatus.d.ts b/web/src/test/auto/resources/@types/promiseStatus.d.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/@types/promiseStatus.d.ts rename to web/src/test/auto/resources/@types/promiseStatus.d.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/assertSegmentSimilarity.js b/web/src/test/auto/resources/assertSegmentSimilarity.js similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/assertSegmentSimilarity.js rename to web/src/test/auto/resources/assertSegmentSimilarity.js diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/assertingPromiseStatus.ts b/web/src/test/auto/resources/assertingPromiseStatus.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/assertingPromiseStatus.ts rename to web/src/test/auto/resources/assertingPromiseStatus.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/canaryRecording.json b/web/src/test/auto/resources/json/canaryRecording.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/canaryRecording.json rename to web/src/test/auto/resources/json/canaryRecording.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/basicMultitouch.json b/web/src/test/auto/resources/json/receiver/basicMultitouch.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/basicMultitouch.json rename to web/src/test/auto/resources/json/receiver/basicMultitouch.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/desktopRoamAndReturn.json b/web/src/test/auto/resources/json/receiver/desktopRoamAndReturn.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/desktopRoamAndReturn.json rename to web/src/test/auto/resources/json/receiver/desktopRoamAndReturn.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/embeddedBorderCancel.json b/web/src/test/auto/resources/json/receiver/embeddedBorderCancel.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/embeddedBorderCancel.json rename to web/src/test/auto/resources/json/receiver/embeddedBorderCancel.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/hardBorderCancel.json b/web/src/test/auto/resources/json/receiver/hardBorderCancel.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/hardBorderCancel.json rename to web/src/test/auto/resources/json/receiver/hardBorderCancel.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileProximityApproach.json b/web/src/test/auto/resources/json/receiver/mobileProximityApproach.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileProximityApproach.json rename to web/src/test/auto/resources/json/receiver/mobileProximityApproach.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileSafeZoneCancel.json b/web/src/test/auto/resources/json/receiver/mobileSafeZoneCancel.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/mobileSafeZoneCancel.json rename to web/src/test/auto/resources/json/receiver/mobileSafeZoneCancel.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupLongRoamingEnd.json b/web/src/test/auto/resources/json/receiver/popupLongRoamingEnd.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupLongRoamingEnd.json rename to web/src/test/auto/resources/json/receiver/popupLongRoamingEnd.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupSafePersistence.json b/web/src/test/auto/resources/json/receiver/popupSafePersistence.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupSafePersistence.json rename to web/src/test/auto/resources/json/receiver/popupSafePersistence.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupShimCancel.json b/web/src/test/auto/resources/json/receiver/popupShimCancel.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/receiver/popupShimCancel.json rename to web/src/test/auto/resources/json/receiver/popupShimCancel.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/flick_ne_se.json b/web/src/test/auto/resources/json/segmentation/flick_ne_se.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/flick_ne_se.json rename to web/src/test/auto/resources/json/segmentation/flick_ne_se.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/longpress_to_ne.json b/web/src/test/auto/resources/json/segmentation/longpress_to_ne.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/longpress_to_ne.json rename to web/src/test/auto/resources/json/segmentation/longpress_to_ne.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/nonstationary_hold.json b/web/src/test/auto/resources/json/segmentation/nonstationary_hold.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/nonstationary_hold.json rename to web/src/test/auto/resources/json/segmentation/nonstationary_hold.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/quick_small_square.json b/web/src/test/auto/resources/json/segmentation/quick_small_square.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/quick_small_square.json rename to web/src/test/auto/resources/json/segmentation/quick_small_square.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/simple_ne_move.json b/web/src/test/auto/resources/json/segmentation/simple_ne_move.json similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/json/segmentation/simple_ne_move.json rename to web/src/test/auto/resources/json/segmentation/simple_ne_move.json diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/sequenceAssertions.ts b/web/src/test/auto/resources/sequenceAssertions.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/sequenceAssertions.ts rename to web/src/test/auto/resources/sequenceAssertions.ts diff --git a/web/src/engine/osk/gesture-processor/src/test/resources/simulateMultiSourceInput.ts b/web/src/test/auto/resources/simulateMultiSourceInput.ts similarity index 100% rename from web/src/engine/osk/gesture-processor/src/test/resources/simulateMultiSourceInput.ts rename to web/src/test/auto/resources/simulateMultiSourceInput.ts diff --git a/web/src/test/auto/tsconfig.json b/web/src/test/auto/tsconfig.json index f61a4f23a86..f88f1475392 100644 --- a/web/src/test/auto/tsconfig.json +++ b/web/src/test/auto/tsconfig.json @@ -11,6 +11,7 @@ "include": [ "dom/**/*.ts", "headless/**/*.ts", - "integrated/**/*.ts" + "integrated/**/*.ts", + "resources/**/*.ts" ] } From 6276b1ecc35c3031940556c74761f471c3a972ed Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 26 Aug 2024 12:35:55 +0700 Subject: [PATCH 157/262] fix(web): fixes build issue due to unreferenced --ci flag inclusion in app/browser build --- web/src/app/browser/build.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index 1d757e264a2..e068e438888 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -44,11 +44,7 @@ do_clean() { } compile_and_copy() { - local COMPILE_FLAGS= - if builder_has_option --ci; then - COMPILE_FLAGS=--ci - fi - compile $SUBPROJECT_NAME $COMPILE_FLAGS + compile $SUBPROJECT_NAME BUILD_ROOT="${KEYMAN_ROOT}/web/build/app/browser" SRC_ROOT="${KEYMAN_ROOT}/web/src/app/browser/src" From 663dbede4c089c3a74c30461e62e46dedb2e9f32 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 26 Aug 2024 14:36:20 +0700 Subject: [PATCH 158/262] fix(web): fix build by calling correct script `test.sh` got removed since it did the same as `build.sh test`. --- web/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/ci.sh b/web/ci.sh index a466417275b..0dd42fa61e3 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -86,7 +86,7 @@ if builder_start_action test; then # No --reporter option exists yet for the headless modules. "$KEYMAN_ROOT/web/src/engine/keyboard/build.sh" test $OPTIONS - "$KEYMAN_ROOT/web/src/engine/osk/gesture-processor/test.sh" $OPTIONS + "$KEYMAN_ROOT/web/src/engine/osk/gesture-processor/build.sh" test $OPTIONS ./build.sh test $OPTIONS From e788bf50773b7e1476d3bedf00346be796ea015f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 26 Aug 2024 16:04:17 +0700 Subject: [PATCH 159/262] fix(web): enable tests to run on Ubuntu 24.04 Noble Previously running the tests failed because it couldn't find ICU 70 when running webkit. Ubuntu 24.04 Noble comes with ICU 74. Updating playwright to 1.46 fixes this. --- package-lock.json | 22 ++++++++++++---------- package.json | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 05f215f3160..29f478ef072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "eslint-plugin-promise": "^6.1.1", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "playwright": "^1.43.1", + "playwright": "^1.46.1", "sinon": "^17.0.1", "source-map-support": "^0.5.21", "tslib": "^2.5.2", @@ -12065,33 +12065,35 @@ } }, "node_modules/playwright": { - "version": "1.43.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", - "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", + "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.43.1" + "playwright-core": "1.46.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.43.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", - "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", + "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/portfinder": { diff --git a/package.json b/package.json index 23970ceff23..72508afc034 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "eslint-plugin-promise": "^6.1.1", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "playwright": "^1.43.1", + "playwright": "^1.46.1", "sinon": "^17.0.1", "source-map-support": "^0.5.21", "tslib": "^2.5.2", From 558636886c7a776fcd6eeb3791efae29d432c6d6 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 26 Aug 2024 19:39:47 +1000 Subject: [PATCH 160/262] feat(windows): remove left modifier only bit flag --- windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index 4d842401dd8..e33a610e61b 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -172,12 +172,6 @@ LRESULT _kmnLowLevelKeyboardProc( // #5190: Don't cache modifier state because sometimes we won't receive // modifier change events (e.g. on lock screen) FHotkeyShiftState = 0; - if (GetKeyState(VK_LCONTROL) < 0) FHotkeyShiftState |= HK_CTRL; - if (GetKeyState(VK_RCONTROL) < 0) FHotkeyShiftState |= HK_RCTRL_INVALID; - if (GetKeyState(VK_LMENU) < 0) FHotkeyShiftState |= HK_ALT; - if (GetKeyState(VK_RMENU) < 0) FHotkeyShiftState |= HK_RALT_INVALID; - if (GetKeyState(VK_LSHIFT) < 0) FHotkeyShiftState |= HK_SHIFT; - if (GetKeyState(VK_RSHIFT) < 0) FHotkeyShiftState |= HK_RSHIFT_INVALID; if (GetKeyState(VK_LCONTROL) < 0) { FHotkeyShiftState |= HK_CTRL; @@ -207,8 +201,6 @@ LRESULT _kmnLowLevelKeyboardProc( } - - //TODO: #8064. Can remove debug message once issue #8064 is resolved SendDebugMessageFormat("!UseCachedHotkeyModifierState [FHotkeyShiftState:%x]", FHotkeyShiftState); From 77d9c386ae91cc48e811d411e5ab6c21655906c0 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 26 Aug 2024 14:03:46 -0400 Subject: [PATCH 161/262] auto: increment master version to 18.0.99 --- HISTORY.md | 11 +++++++++++ VERSION.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 904e03468a2..8f5778c2232 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,16 @@ # Keyman Version History +## 18.0.98 alpha 2024-08-26 + +* change(ios): defer registration of fonts past initialization (#12190) +* refactor(ios): optimize font registration (#12210) +* test(mac): add unit tests to validate first calls to compliance check (#11724) +* fix(mac): limit short bundle version string to x.y.z format in info.plist (#12233) +* chore(developer): extend timeouts for lm compiler tests to 5 secs (#12273) +* fix(developer): find last matching key in LDML key bag when building KVK (#12278) +* chore(android): Cleanup stray debug statements in console (#12287) +* fix(developer): ensure call() detects invalid store in kmcmplib compiler (#12263) + ## 18.0.97 alpha 2024-08-24 * refactor(linux): cleanup API of kvk2ldml.py (#12276) diff --git a/VERSION.md b/VERSION.md index 48b8081cd1f..6ad090d9d60 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.98 \ No newline at end of file +18.0.99 \ No newline at end of file From 930221376c09d8004c81ba22f55a7b25d31b28d5 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Tue, 27 Aug 2024 04:19:51 +0700 Subject: [PATCH 162/262] fix(mac): show package info after keyboard install Move to updated APIs that no longer require window dismissal. Fixes: #9308 --- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 9189e39d58e..0482f784328 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -1119,14 +1119,16 @@ - (void)windowWillClose:(NSNotification *)notification { * TODO: this should really be refactored */ -- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - NSButton *button = (NSButton *)[alert.buttons objectAtIndex:0]; - if (button.tag == -1) { +- (void)downloadComplete:(NSModalResponse) returnCode { + os_log_debug([KMLogs uiLog], "downloadComplete, NSModalResponse returnCode: %ld", (long)returnCode); + if (returnCode == NSModalResponseCancel) { + os_log_debug([KMLogs uiLog], "downloadComplete, returnCode == NSModalResponseCancel"); [_connection cancel]; } - else if (button.tag == 1) { - [_downloadKBWindow close]; + else if (returnCode == NSModalResponseOK) { + os_log_debug([KMLogs uiLog], "downloadComplete, returnCode == NSModalResponseOK"); if (self.configWindow.window != nil) { + os_log_debug([KMLogs uiLog], "downloadComplete, self.configWindow.window != nil"); [self.configWindow.window makeKeyAndOrderFront:nil]; if (![[self.configWindow.window childWindows] containsObject:self.infoWindow.window]) { [self.configWindow.window addChildWindow:self.infoWindow.window ordered:NSWindowAbove]; @@ -1135,6 +1137,7 @@ - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInf [self.infoWindow.window makeKeyAndOrderFront:nil]; } else { + os_log_debug([KMLogs uiLog], "downloadComplete, self.configWindow.window == nil"); [self.infoWindow.window centerInParent]; [self.infoWindow.window makeKeyAndOrderFront:nil]; [self.infoWindow.window setLevel:NSFloatingWindowLevel]; @@ -1152,6 +1155,7 @@ - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInf } - (NSAlert *)downloadInfoView { + os_log_debug([KMLogs uiLog], "downloadInfoView"); if (_downloadInfoView == nil) { _downloadInfoView = [[NSAlert alloc] init]; [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-downloading", nil)]; @@ -1165,6 +1169,7 @@ - (NSAlert *)downloadInfoView { } - (NSProgressIndicator *)progressIndicator { + os_log_debug([KMLogs uiLog], "progressIndicator"); if (_progressIndicator == nil) { _progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 300, 20)]; [_progressIndicator setIndeterminate:NO]; @@ -1186,36 +1191,39 @@ - (void)downloadKeyboardFromKeyboardId:(NSString *)keyboardId { - (void)downloadKeyboardFromURL:(NSURL *)url { NSURL* downloadUrl = url; - + os_log_debug([KMLogs uiLog], "downloadKeyboardFromURL, url.path: %{public}@", url.path); + if (downloadUrl && _downloadFilename) { if (_infoWindow.window != nil) [_infoWindow close]; [self.downloadInfoView setInformativeText:self.downloadFilename]; + if (self.configWindow.window != nil) { + os_log_debug([KMLogs uiLog], "downloadKeyboardFromURL, self.configWindow.window != nil"); [self.configWindow.window makeKeyAndOrderFront:nil]; if (![[self.configWindow.window childWindows] containsObject:self.downloadKBWindow.window]) { [self.configWindow.window addChildWindow:self.downloadKBWindow.window ordered:NSWindowAbove]; } [self.downloadKBWindow.window centerInParent]; [self.downloadKBWindow.window makeKeyAndOrderFront:nil]; - [self.downloadInfoView beginSheetModalForWindow:self.downloadKBWindow.window - modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) - contextInfo:nil]; + [self.downloadInfoView beginSheetModalForWindow:self.downloadKBWindow.window completionHandler:^(NSModalResponse returnCode) { + [self downloadComplete:returnCode]; + }]; } else { + os_log_debug([KMLogs uiLog], "downloadKeyboardFromURL, self.configWindow.window == nil"); [self.downloadKBWindow.window centerInParent]; [self.downloadKBWindow.window makeKeyAndOrderFront:nil]; [self.downloadKBWindow.window setLevel:NSFloatingWindowLevel]; - [self.downloadInfoView beginSheetModalForWindow:self.downloadKBWindow.window - modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) - contextInfo:nil]; + [self.downloadInfoView beginSheetModalForWindow:self.downloadKBWindow.window completionHandler:^(NSModalResponse returnCode) { + [self downloadComplete:returnCode]; + }]; } if (_connection == nil) { - [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-downloading", nil)]; + os_log_debug([KMLogs uiLog], "downloadKeyboardFromURL, _connection == nil, set button title cancel downloading, tag = -1"); + [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-downloading", nil)]; NSButton *button = (NSButton *)[_downloadInfoView.buttons objectAtIndex:0]; [button setTitle:NSLocalizedString(@"button-cancel-downloading", nil)]; [button setTag:-1]; @@ -1257,6 +1265,7 @@ - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheRespo } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { + os_log_debug([KMLogs uiLog], "connectionDidFinishLoading"); NSString *filePath = [self.keyboardsPath stringByAppendingPathComponent:self.downloadFilename]; [self.receivedData writeToFile:filePath atomically:YES]; [self unzipFile:filePath]; @@ -1264,6 +1273,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-download-complete", nil)]; NSButton *button = (NSButton *)[_downloadInfoView.buttons objectAtIndex:0]; + os_log_debug([KMLogs uiLog], "connectionDidFinishLoading, set button title download complete, tag = 1"); [button setTitle:NSLocalizedString(@"button-download-complete", nil)]; [button setTag:1]; [[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardDownloadCompletedNotification From 500c800bb495ab44db66e3b4558cd40224230f1e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:01:50 +1000 Subject: [PATCH 163/262] feat(windows): remove developer debug logging --- common/windows/cpp/include/registry.h | 2 +- windows/src/desktop/kmshell/xml/strings.xml | 4 ++-- windows/src/engine/keyman32/hotkeys.cpp | 2 -- .../keyman32/k32_lowlevelkeyboardhook.cpp | 24 ++++--------------- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/common/windows/cpp/include/registry.h b/common/windows/cpp/include/registry.h index c6d1bf85ec1..842176e9edc 100644 --- a/common/windows/cpp/include/registry.h +++ b/common/windows/cpp/include/registry.h @@ -110,7 +110,7 @@ #define REGSZ_KeyboardHotkeysAreToggle "hotkeys are toggles" #define REGSZ_DeadkeyConversionMode "deadkey conversion mode" // CU // I4552 #define REGSZ_ZapVirtualKeyCode "zap virtual key code" // LM, defaults to 0x0E (_VK_PREFIX_DEFAULT) -/* Non-chiral use of hotkeys instead of left-only hotkeys */ +/* Default is to only use left modifier in hotkeys trigger */ #define REGSZ_UseRightModifierHotKey "use right modifier for hotkey" diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index ea09ca5b744..9409d6d8c12 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -379,8 +379,8 @@ - - Right Modifier keys work with Hotkeys + + Right-side modifier keys trigger hotkeys diff --git a/windows/src/engine/keyman32/hotkeys.cpp b/windows/src/engine/keyman32/hotkeys.cpp index e7fdbe3d79d..1652397d77b 100644 --- a/windows/src/engine/keyman32/hotkeys.cpp +++ b/windows/src/engine/keyman32/hotkeys.cpp @@ -139,8 +139,6 @@ Hotkey *Hotkeys::GetHotkey(DWORD hotkey) { for (int i = 0; i < m_nHotkeys; i++) { if (m_hotkeys[i].HotkeyValue == hotkey) { - SendDebugMessageFormat( - "LanguageHotkey[%d] = {HotkeyValue: %x, hkl: %x} passed in: %x", i, m_hotkeys[i].HotkeyValue, m_hotkeys[i].hkl, hotkey); return &m_hotkeys[i]; } } diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index e33a610e61b..8e16d536f2a 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -130,7 +130,7 @@ BOOL IsTouchPanelVisible() { } /* - Cache UseRightModifierHotKey debug flag for this session + Cache UseRightModifierHotKey for this session */ BOOL UseRightModifierHotKey() { static BOOL flag_UseRightModifierHotKey = FALSE; @@ -148,8 +148,6 @@ BOOL UseRightModifierHotKey() { return flag_UseRightModifierHotKey; } - - LRESULT _kmnLowLevelKeyboardProc( _In_ int nCode, _In_ WPARAM wParam, @@ -169,20 +167,14 @@ LRESULT _kmnLowLevelKeyboardProc( SendDebugMessageFormat("wparam: %x lparam: %x [vk:%s scan:%x flags:%x extra:%x]", wParam, lParam, Debug_VirtualKey((WORD) hs->vkCode), hs->scanCode, hs->flags, hs->dwExtraInfo); // I4674 - // #5190: Don't cache modifier state because sometimes we won't receive - // modifier change events (e.g. on lock screen) FHotkeyShiftState = 0; - if (GetKeyState(VK_LCONTROL) < 0) { + if (GetKeyState(VK_LCONTROL) < 0) { FHotkeyShiftState |= HK_CTRL; - // TODO remove - SendDebugMessageFormat("ProcessHotkey VK_LCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); } if (GetKeyState(VK_RCONTROL) < 0) { FHotkeyShiftState |= UseRightModifierHotKey() ? HK_CTRL : HK_RCTRL_INVALID; - // TODO remove - SendDebugMessageFormat("ProcessHotkey VK_RCONTROL [vkCode:%x isUp:%d FHotkeyShiftState:%x useRight:%d", hs->vkCode, isUp, FHotkeyShiftState, UseRightModifierHotKey()); } if (GetKeyState(VK_LMENU) < 0) { @@ -200,10 +192,8 @@ LRESULT _kmnLowLevelKeyboardProc( FHotkeyShiftState |= UseRightModifierHotKey() ? HK_SHIFT : HK_RSHIFT_INVALID; } - - //TODO: #8064. Can remove debug message once issue #8064 is resolved - SendDebugMessageFormat("!UseCachedHotkeyModifierState [FHotkeyShiftState:%x]", FHotkeyShiftState); - + //TODO: #8064. Can remove debug message once issue #8064 is resolved + SendDebugMessageFormat("!UseCachedHotkeyModifierState [FHotkeyShiftState:%x]", FHotkeyShiftState); // #7337 Post the modifier state ensuring the serialized queue is in sync // Note that the modifier key may be posted again with WM_KEYMAN_KEY_EVENT, @@ -287,30 +277,24 @@ BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState) { Hotkeys *hotkeys = Hotkeys::Instance(); // I4641 if (!hotkeys) { - SendDebugMessageFormat("Failed to get Instance"); return FALSE; } Hotkey *hotkey = hotkeys->GetHotkey(ShiftState | vkCode); // I4641 if (!hotkey) { - SendDebugMessageFormat("GetHotkey Null"); return FALSE; } if (isUp) { - SendDebugMessageFormat("Is Up"); return TRUE; } if (hotkey->HotkeyType == hktInterface) { - SendDebugMessageFormat("PostMasterController"); Globals::PostMasterController(wm_keyman_control, MAKELONG(KMC_INTERFACEHOTKEY, hotkey->Target), 0); } else { - SendDebugMessageFormat("ReportKeyboardChanged"); ReportKeyboardChanged(PC_HOTKEYCHANGE, hotkey->hkl == 0 ? TF_PROFILETYPE_INPUTPROCESSOR : TF_PROFILETYPE_KEYBOARDLAYOUT, 0, hotkey->hkl, GUID_NULL, hotkey->profileGUID); } - SendDebugMessageFormat("PostDummyKeyEvent"); /* Generate a dummy keystroke to block menu activations, etc but let the shift key through */ PostDummyKeyEvent(); // I3301 - this is imperfect because we don't deal with HC_NOREMOVE. But good enough? // I3534 // I4844 From 30ec15c5fa7de4d085e720273523534131b3ba03 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 27 Aug 2024 09:05:05 +0700 Subject: [PATCH 164/262] fix(android): Auto-mirror Text Size icons for RTL support --- .../drawable-hdpi/ic_light_action_textsize.png | Bin 135 -> 214 bytes .../ic_light_action_textsize.png | Bin 0 -> 217 bytes .../ic_light_action_textsize.png | Bin 0 -> 168 bytes .../ic_light_action_textsize.png | Bin 0 -> 180 bytes .../ic_light_action_textsize.png | Bin 0 -> 326 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png index 3809ef23536c1503e7240029e194a1550f232ff3..9c8e3354e6b793baad6a56d8579ad95602a512ee 100644 GIT binary patch delta 197 zcmV;$06PDN0oDPK8Gi-<007|tn3wdXGLMCUfx(+IKp05*dAc};R4^t>97+(eF!-a+W!00=EFvKl@PK*6G4_l{ zTua#gF|KfAzo5!?Pr;{u@{`CGN0!Bh60ay6dgOd!L142RuUCVZ5T{E=e^Vd>gPxf1 VAA$9PE|k1|%Oc%$NbBSc;uILpXq-h9ji|$e-Zp;usRq z`gZDB&K3g!H~A#_sIH|qP9~{qsxCd^RmNZKX=BmDC-k9u!r=qrpH4hsZxJ}ZiQ)c% zjTNkI)&D+voJg{{!g|B+leR(VSH2~aYj!?(qI6mAmdsv<@4CAWpV_}OPAD{cajTv9 zvsA5x@1oQeW*F_uet-7+c4whY6DG8_=%}PT{QFk;ZFY3iXQz(uADJa~>WN0>OTGcR Ojlt8^&t;ucLK6Tu301KG literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png new file mode 100644 index 0000000000000000000000000000000000000000..a31690d1271909d78d605835e0ccc8e184b92ef6 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSc;uILpXq-h9ji|$dC7QaSVxQ zeS5{4i$Rgc^`geR`qQ_%6wdn}5&0exd{R|7iS5DKE4A0#^Yu@k6THq5RQB)lj%0W1 zMJ=-h9K8x_UVrOT-P|Co`$#J7E9- literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png new file mode 100644 index 0000000000000000000000000000000000000000..ca9829d1788b88326c0a16341121cd247bfe4b1a GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmSQK*5Dp-y;YjHK^0Pf%978f1 z-(C&mYETex4eb85|LLs;r;87qZJuB5e_~{^jqSj>zH>Ka_x#?x`H`|G(G-|K#NG5T0O`%-tLJY|``^2v<$P*I`zJ2_wUSb8@PG WSRB^-GdlsTW$<+Mb6Mw<&;$T$3P2+O literal 0 HcmV?d00001 diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png new file mode 100644 index 0000000000000000000000000000000000000000..d15adad0acdcff6bf06e65d23c87f92d696727fd GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmSQK*5Dp-y;YjHK^8b3eIEGZr zd3(!|ugO7x!BOSk|I@!1H*vb%$z8Ln=;no)r&G4^0@Zc4M9vnBp^r@q-N zf55WoD2JGGL{EYt3d6X#zN{>K=8dnhXIro;P>wiL{Eb;JWdCZLJ9B4kkT{sc(k-at yhQ`?WS?^ Date: Tue, 27 Aug 2024 09:37:43 +0700 Subject: [PATCH 165/262] refactor(web): fix tests --- web/src/engine/osk/gesture-processor/build.sh | 2 +- ...spatchQueue.spec.ts => asyncClosureDispatchQueue.tests.ts} | 0 .../{gesturePath.spec.ts => gesturePath.tests.ts} | 4 ++-- .../{gestureSource.spec.ts => gestureSource.tests.ts} | 0 .../{gestureMatcher.spec.ts => gestureMatcher.tests.ts} | 0 .../{gestureModelDefs.spec.ts => gestureModelDefs.tests.ts} | 0 .../{gestureSequence.spec.ts => gestureSequence.tests.ts} | 0 .../{matcherSelector.spec.ts => matcherSelector.tests.ts} | 0 .../gestures/{pathMatcher.spec.ts => pathMatcher.tests.ts} | 0 ...ointCoordinator.spec.ts => touchpointCoordinator.tests.ts} | 0 .../{pathStats.spec.ts => pathStats.tests.ts} | 0 11 files changed, 3 insertions(+), 3 deletions(-) rename web/src/test/auto/headless/engine/osk/gesture-processor/{asyncClosureDispatchQueue.spec.ts => asyncClosureDispatchQueue.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/{gesturePath.spec.ts => gesturePath.tests.ts} (98%) rename web/src/test/auto/headless/engine/osk/gesture-processor/{gestureSource.spec.ts => gestureSource.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/gestures/{gestureMatcher.spec.ts => gestureMatcher.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/gestures/{gestureModelDefs.spec.ts => gestureModelDefs.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/gestures/{gestureSequence.spec.ts => gestureSequence.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/gestures/{matcherSelector.spec.ts => matcherSelector.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/gestures/{pathMatcher.spec.ts => pathMatcher.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/gestures/{touchpointCoordinator.spec.ts => touchpointCoordinator.tests.ts} (100%) rename web/src/test/auto/headless/engine/osk/gesture-processor/{pathStats.spec.ts => pathStats.tests.ts} (100%) diff --git a/web/src/engine/osk/gesture-processor/build.sh b/web/src/engine/osk/gesture-processor/build.sh index f3bafab5ed5..b2ea635d8a7 100755 --- a/web/src/engine/osk/gesture-processor/build.sh +++ b/web/src/engine/osk/gesture-processor/build.sh @@ -61,5 +61,5 @@ builder_run_action configure do_configure builder_run_action clean rm -rf build/ intermediate/ builder_run_action build:module do_build_module builder_run_action build:tools src/tools/build.sh build -builder_run_action test:module test-headless "${SUBPROJECT_NAME}" +builder_run_action test:module test-headless-typescript "${SUBPROJECT_NAME}" builder_run_action test:tools do_test_tools diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/asyncClosureDispatchQueue.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/asyncClosureDispatchQueue.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/asyncClosureDispatchQueue.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/asyncClosureDispatchQueue.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.tests.ts similarity index 98% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.tests.ts index 1159d969fad..0a90b0c6289 100644 --- a/web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.spec.ts +++ b/web/src/test/auto/headless/engine/osk/gesture-processor/gesturePath.tests.ts @@ -11,8 +11,8 @@ import { TouchpathTurtle } from '#gesture-tools'; // Ensures that the resources are resolved relative to this script's source, not // to the cwd when the test runner was launched or to its built version. const scriptFolder = path.dirname(url.fileURLToPath(import.meta.url)) - .replace("build/test/auto", "src/test/auto") // Mac/Linux - .replace("build\\test\\auto", "src\\test\\auto"); // Windows + .replace("build/test", "src/test/auto") // Mac/Linux + .replace("build\\test", "src\\test\\auto"); // Windows const SEGMENT_TEST_JSON_FOLDER = path.resolve(`${scriptFolder}/../../../../resources/json/segmentation`); describe("GesturePath", function() { diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestureSource.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestureSource.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestureSource.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestureSource.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureMatcher.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureModelDefs.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureModelDefs.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureModelDefs.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureModelDefs.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/gestureSequence.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/matcherSelector.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/pathMatcher.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/gestures/touchpointCoordinator.tests.ts diff --git a/web/src/test/auto/headless/engine/osk/gesture-processor/pathStats.spec.ts b/web/src/test/auto/headless/engine/osk/gesture-processor/pathStats.tests.ts similarity index 100% rename from web/src/test/auto/headless/engine/osk/gesture-processor/pathStats.spec.ts rename to web/src/test/auto/headless/engine/osk/gesture-processor/pathStats.tests.ts From 733bc29bf1009ec20122ebd6f9b81e18fac8eb4a Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 27 Aug 2024 09:52:48 +0700 Subject: [PATCH 166/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- android/KMAPro/build.gradle | 2 -- android/KMAPro/kMAPro/build.gradle | 1 - 2 files changed, 3 deletions(-) diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index ab7158c8d88..5614fcae6a6 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -2,7 +2,6 @@ buildscript { repositories { google() - //jcenter() deprecated August 2024 mavenCentral() flatDir { dirs "kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/" @@ -24,7 +23,6 @@ allprojects { repositories { maven { url uri("${projectDir}/libs") } google() - //jcenter() deprecated August 2024 mavenCentral() maven { url "https://jitpack.io" } } diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index e44f72ad576..05a684f34db 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -145,7 +145,6 @@ repositories { dirs 'libs' } google() - //jcenter() } dependencies { From ea6e29581bd930d42d39cef5c774263fc4d56652 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 27 Aug 2024 13:06:37 +0700 Subject: [PATCH 167/262] change(mac): some minor changes after review comments --- mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h | 10 +++++----- mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m | 14 ++++++-------- .../Keyman4MacIM/KMSettingsRepository.m | 6 +----- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 0a1cc8cca37..87d240701d9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -1,9 +1,6 @@ /* * Keyman is copyright (C) SIL International. MIT License. * - * KMDataRepository.h - * Keyman - * * Created by Shawn Schantz on 2024-07-30. * */ @@ -13,9 +10,12 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject -@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/keyman.inputmethod.Keyman' +// keymanDataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' +@property (readonly) NSURL *keymanDataDirectory; + +// keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' @property (readonly) NSURL *keymanKeyboardsDirectory; -// keymanKeyboardsDirectory = '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' + + (KMDataRepository *)shared; - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 20d6e892a46..9ecedab2314 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -1,9 +1,6 @@ /* * Keyman is copyright (C) SIL International. MIT License. * - * KMResourcesRepository.m - * Keyman - * * Created by Shawn Schantz on 2024-07-30. * * Singleton object which serves as an abstraction for the reading and writing of Keyman data. @@ -39,11 +36,11 @@ @implementation KMDataRepository @synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; -/* - The name of the subdirectory within '~/Library/Application Support'. - We follow the convention of using the bundle identifier rather than our subsystem id. - (Also, using the subsystem id, "com.keyman.app", is a poor choice because the API - createDirectoryAtPath sees the .app extension and creates an application file. +/** + * The name of the subdirectory within '~/Library/Application Support'. + * We follow the convention of using the bundle identifier rather than our subsystem id. + * (Also, using the subsystem id, "com.keyman.app", is a poor choice because the API + * createDirectoryAtPath sees the .app extension and creates an application file.) */ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; @@ -158,6 +155,7 @@ - (NSURL *)obsoleteKeymanKeyboardsDirectory { } return _obsoleteKeymanKeyboardsDirectory; } + /** * Only called from migrateData. * Causes user to be prompted for permission to access ~/Documents, but they should already have it. diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index a605ea6e6ec..630e2c324e5 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -1,9 +1,6 @@ /* * Keyman is copyright (C) SIL International. MIT License. * - * KMSettingsRepository.h - * Keyman - * * Created by Shawn Schantz on 2024-07-29. * * Singleton object for reading and writing Keyman application settings. @@ -61,7 +58,7 @@ - (BOOL)settingsExist { * For versions before version 1, the keyboards were stored under the ~/Documents directory. */ - (BOOL)dataModelWithKeyboardsInLibrary { - // NSUserDefaults returns zero if the key does not exist + // [NSUserDefaults integerForKey] returns zero if the key does not exist NSInteger dataModelVersion = [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion]; return dataModelVersion >= kVersionStoreDataInLibraryDirectory; @@ -142,7 +139,6 @@ - (void)convertSelectedKeyboardPathForMigration { * Convert the path of the keyboard designating the Documents folder to its new location * in the Application Support folder */ - - (NSString *)convertOldKeyboardPath:(NSString *)oldPath { NSString *newPathString = @""; if(oldPath != nil) { From 8c4c4855785637581b78ddced75e7c29a1652146 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 27 Aug 2024 14:02:05 +0700 Subject: [PATCH 168/262] fix(web): fixes wordbreaker test import path --- common/models/wordbreakers/test/test-search-property.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/models/wordbreakers/test/test-search-property.js b/common/models/wordbreakers/test/test-search-property.js index 85996c7286e..9f33e4cc0c8 100644 --- a/common/models/wordbreakers/test/test-search-property.js +++ b/common/models/wordbreakers/test/test-search-property.js @@ -3,8 +3,8 @@ */ import { assert } from 'chai'; -import { searchForProperty } from '../build/obj/default/searchForProperty.js'; -import { propertyMap } from '../build/obj/default/data.inc.js'; +import { searchForProperty } from '../build/main/obj/default/searchForProperty.js'; +import { propertyMap } from '../build/main/obj/default/data.inc.js'; describe('searchForProperty', () => { it('correctly finds character classes for standard ASCII characters', () => { From 9cf996a1841ee2a3c608f57b7db6f1fda89a94bf Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 27 Aug 2024 14:02:05 +0700 Subject: [PATCH 169/262] fix(web): fixes wordbreaker test import path --- common/models/wordbreakers/test/test-search-property.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/models/wordbreakers/test/test-search-property.js b/common/models/wordbreakers/test/test-search-property.js index 85996c7286e..9f33e4cc0c8 100644 --- a/common/models/wordbreakers/test/test-search-property.js +++ b/common/models/wordbreakers/test/test-search-property.js @@ -3,8 +3,8 @@ */ import { assert } from 'chai'; -import { searchForProperty } from '../build/obj/default/searchForProperty.js'; -import { propertyMap } from '../build/obj/default/data.inc.js'; +import { searchForProperty } from '../build/main/obj/default/searchForProperty.js'; +import { propertyMap } from '../build/main/obj/default/data.inc.js'; describe('searchForProperty', () => { it('correctly finds character classes for standard ASCII characters', () => { From 0daf91021c90f5b490261e6db83b9defeff8d9e8 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 27 Aug 2024 14:03:04 -0400 Subject: [PATCH 170/262] auto: increment master version to 18.0.100 --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 8f5778c2232..0972d983247 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 18.0.99 alpha 2024-08-27 + +* feat(web): import the generator for the pred-text wordbreaker's Unicode-property data-table (#10690) +* feat(web): optimize the wordbreaker data table for filesize and ease of first-load parsing (#10692) +* (#12297) +* (#12115) + ## 18.0.98 alpha 2024-08-26 * change(ios): defer registration of fonts past initialization (#12190) diff --git a/VERSION.md b/VERSION.md index 6ad090d9d60..59ec57bac21 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.99 \ No newline at end of file +18.0.100 \ No newline at end of file From 0b1f85bd77282c345fe1890125d699ccb4cddc96 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 28 Aug 2024 06:56:21 +0700 Subject: [PATCH 171/262] chore(android,ios): Update FirstVoices keyboards to 12.15 * Keyboard version updates corresponding to fv_all.kmp version 12.15 --- oem/firstvoices/keyboards.csv | 71 ++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/oem/firstvoices/keyboards.csv b/oem/firstvoices/keyboards.csv index cbede23edc4..22812ec14de 100644 --- a/oem/firstvoices/keyboards.csv +++ b/oem/firstvoices/keyboards.csv @@ -7,57 +7,58 @@ fv,fv_uummarmiutun,Uummarmiutun,Arctic,fv_uummarmiutun_kmw-9.0.js,9.1.1,ikt-Latn fv,fv_eastern_canadian_inuktitut,ᐃᓄᒃᑎᑐᑦ (Eastern Canadian Inuktitut),Arctic,fv_eastern_canadian_inuktitut_kmw-9.0.js,9.2.1,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics) fv,fv_migmaq,Mi'gmawi'simg / Mi'kmawi'simk,Atlantic,fv_migmaq_kmw-9.0.js,9.1.2,mic-Latn,Mi'kmaq (Latin) fv,fv_skicinuwatuwewakon,Skicinuwatuwewakon,Atlantic,fv_skicinuwatuwewakon_kmw-9.0.js,9.1.1,pqm-Latn,Malecite-Passamaquoddy (Latin) -fv,fv_uwikala,'Uwik̓ala,BC Coast,fv_uwikala_kmw-9.0.js,9.3,hei,Heiltsuk +fv,fv_uwikala,'Uwik̓ala,BC Coast,fv_uwikala_kmw-9.0.js,9.4,hei,Heiltsuk fv,fv_dexwlesucid,Dəxʷləšucid,BC Coast,fv_dexwlesucid_kmw-9.0.js,9.2.1,lut-Latn,Lushootseed (Latin) fv,fv_diitiidatx,Diidiitidq,BC Coast,fv_diitiidatx_kmw-9.0.js,9.2.1,nuk-Latn,Nuu-chah-nulth (Latin) -fv,fv_gitsenimx,Gitsenimx̱,BC Coast,fv_gitsenimx_kmw-9.0.js,10.1.1,git,Gitxsan (Latin) +fv,fv_gitsenimx,Gitsenimx̱,BC Coast,fv_gitsenimx_kmw-9.0.js,10.1.2,git,Gitxsan (Latin) fv,fv_hailzaqvla,Haiɫzaqvla,BC Coast,fv_hailzaqvla_kmw-9.0.js,9.5.2,hei,Heiltsuk (Latin) -fv,fv_haisla,Haisla,BC Coast,fv_haisla.js,2.1,has-Latn,Haisla (Latin) +fv,fv_haisla,Haisla,BC Coast,fv_haisla.js,2.1.2,has-Latn,Haisla (Latin) fv,fv_halqemeylem,Halq'eméylem,BC Coast,fv_halqemeylem_kmw-9.0.js,9.2,hur-Latn,Halkomelem (Latin) fv,fv_henqeminem,Hǝn̓q̓ǝmin̓ǝm,BC Coast,fv_henqeminem_kmw-9.0.js,10.1,hur-Latn,Halkomelem (Latin) fv,fv_klahoose,Homalco-Klahoose-Sliammon,BC Coast,fv_klahoose_kmw-9.0.js,10.1,coo,Comox fv,fv_hulquminum,Hul’q’umi’num’,BC Coast,fv_hulquminum_kmw-9.0.js,9.1,hur-Latn,Halkomelem (Latin) fv,fv_hulquminum_combine,Hul̓q̓umin̓um̓,BC Coast,fv_hulquminum_combine_kmw-9.0.js,2.0.1,hur-Latn,Halkomelem (Latin) -fv,fv_kwakwala_liqwala,Kʷak̓ʷala,BC Coast,fv_kwakwala_liqwala_kmw-9.0.js,9.2.5,kwk-Latn,Kwakiutl (Latin) -fv,fv_kwakwala,Kwak̕wala,BC Coast,fv_kwakwala_kmw-9.0.js,9.1.2,kwk-Latn,Kwakiutl (Latin) -fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,9.2.1,clm-Latn,Clallam (Latin) -fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.1.2,ncg-Latn,Nisga'a (Latin) -fv,fv_nuucaanul,Nuučaan̓uł,BC Coast,fv_nuucaanul_kmw-9.0.js,9.1.4,nuk-Latn,Nuu-chah-nulth (Latin) -fv,fv_nuxalk,Nuxalk,BC Coast,fv_nuxalk_kmw-9.0.js,10.0,blc-Latn,Bella Coola (Latin) +fv,fv_kwakwala_liqwala,Kʷak̓ʷala,BC Coast,fv_kwakwala_liqwala_kmw-9.0.js,9.3,kwk-Latn,Kwakiutl (Latin) +fv,fv_kwakwala,Kwak̕wala,BC Coast,fv_kwakwala_kmw-9.0.js,9.2,kwk-Latn,Kwakiutl (Latin) +fv,fv_lekwungen,Lək̓ʷəŋən,BC Coast,fv_lekwungen-9.0.js,1.0,str,Lekwungen +fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,10.0,clm-Latn,Clallam (Latin) +fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.2,ncg-Latn,Nisga'a (Latin) +fv,fv_nuucaanul,Nuučaan̓uł,BC Coast,fv_nuucaanul_kmw-9.0.js,9.2,nuk-Latn,Nuu-chah-nulth (Latin) +fv,fv_nuxalk,Nuxalk,BC Coast,fv_nuxalk_kmw-9.0.js,10.0.1,blc-Latn,Bella Coola (Latin) fv,fv_sencoten,SENĆOŦEN,BC Coast,fv_sencoten_kmw-9.0.js,9.2.2,str,Straits Salish -fv,fv_sguuxs,Sgüüx̱s,BC Coast,fv_sguuxs.js,1.0.1,tsi,Tsimshian +fv,fv_sguuxs,Sgüüx̱s,BC Coast,fv_sguuxs.js,1.1,tsi,Tsimshian fv,fv_shashishalhem,Shashishalhem,BC Coast,fv_shashishalhem_kmw-9.0.js,9.2,sec-Latn,Sechelt (Latin) -fv,fv_skwxwumesh_snichim,Sḵwx̱wúmesh sníchim,BC Coast,fv_skwxwumesh_snichim_kmw-9.0.js,9.3,squ-Latn,Squamish (Latin) +fv,fv_skwxwumesh_snichim,Sḵwx̱wúmesh sníchim,BC Coast,fv_skwxwumesh_snichim_kmw-9.0.js,10.0,squ-Latn,Squamish (Latin) fv,fv_smalgyax,Sm'algya̱x,BC Coast,fv_smalgyax_kmw-9.0.js,9.2,tsi-Latn,Tsimshian (Latin) -fv,fv_xaislakala,X̄a'ʼislak̓ala,BC Coast,fv_xaislakala_kmw-9.0.js,9.1.1,has-Latn,Haisla (Latin) -fv,fv_hlgaagilda_xaayda_kil,X̱aayda-X̱aad Kil,BC Coast,fv_hlgaagilda_xaayda_kil_kmw-9.0.js,9.3,hax,Southern Haida -fv,fv_dakelh,Dakelh,BC Interior,fv_dakelh_kmw-9.0.js,9.2,caf-Latn,Southern Carrier (Latin) +fv,fv_xaislakala,X̄a'ʼislak̓ala,BC Coast,fv_xaislakala_kmw-9.0.js,10.0,has-Latn,Haisla (Latin) +fv,fv_hlgaagilda_xaayda_kil,X̱aayda-X̱aad Kil,BC Coast,fv_hlgaagilda_xaayda_kil_kmw-9.0.js,9.4,hax,Southern Haida +fv,fv_dakelh,Dakelh,BC Interior,fv_dakelh_kmw-9.0.js,9.2.1,caf-Latn,Southern Carrier (Latin) fv,fv_ktunaxa,Ktunaxa,BC Interior,fv_ktunaxa_kmw-9.0.js,10.0,kut-Latn,Kutenai (Latin) -fv,fv_kwadacha_tsekene,Kwadacha Tsek’ene,BC Interior,fv_kwadacha_tsekene_kmw-9.0.js,1.0,sek-Latn,Sekani +fv,fv_kwadacha_tsekene,Kwadacha Tsek’ene,BC Interior,fv_kwadacha_tsekene_kmw-9.0.js,1.1,sek-Latn,Sekani fv,fv_natwits,Nedut’en-Witsuwit'en,BC Interior,fv_natwits_kmw-9.0.js,9.1.3,caf-Latn,Southern Carrier (Latin) -fv,fv_nlekepmxcin,Nłeʔkepmxcin,BC Interior,fv_nlekepmxcin_kmw-9.0.js,9.4,thp-Latn,Thompson (Latin) -fv,fv_nlha7kapmxtsin,Nlha7kapmxtsin,BC Interior,fv_nlha7kapmxtsin_kmw-9.0.js,10.0,thp-Latn,Thompson (Latin) -fv,fv_nlakapamuxcheen,Nlakapamuxcheen,BC Interior,fv_nlakapamuxcheen_kmw-9.0.js,1.0.2,thp,Thompson -fv,fv_nsilxcen,Nsilxcən,BC Interior,fv_nsilxcen_kmw-9.0.js,9.3,oka,Okanagan +fv,fv_nlekepmxcin,Nłeʔkepmxcin,BC Interior,fv_nlekepmxcin_kmw-9.0.js,9.5.1,thp-Latn,Thompson (Latin) +fv,fv_nlha7kapmxtsin,Nlha7kapmxtsin,BC Interior,fv_nlha7kapmxtsin_kmw-9.0.js,10.1.1,thp-Latn,Thompson (Latin) +fv,fv_nlakapamuxcheen,Nlakapamuxcheen,BC Interior,fv_nlakapamuxcheen_kmw-9.0.js,1.1,thp,Thompson +fv,fv_nsilxcen,Nsilxcən,BC Interior,fv_nsilxcen_kmw-9.0.js,10.0,oka,Okanagan fv,fv_secwepemctsin,Secwepemctsín,BC Interior,fv_secwepemctsin_kmw-9.0.js,9.2,shs-Latn,Shuswap (Latin) fv,fv_stlatlimxec,Sƛ̓aƛ̓imxəc,BC Interior,fv_stlatlimxec_kmw-9.0.js,9.3,lil-Latn,Lillooet (Latin) -fv,fv_statimcets,St̓át̓imcets,BC Interior,fv_statimcets_kmw-9.0.js,9.1.4,lil-Latn,Lillooet (Latin) -fv,fv_taltan,Tāłtān,BC Interior,fv_taltan_kmw-9.0.js,9.1.5,tht-Latn,Tahltan (Latin) -fv,fv_tsekehne,Tsek'ehne,BC Interior,fv_tsekehne_kmw-9.0.js,9.1.2,sek-Latn,Sekani (Latin) -fv,fv_tsilhqotin,Tŝilhqot'in,BC Interior,fv_tsilhqotin_kmw-9.0.js,9.1.3,clc-Latn,Chilcotin (Latin) -fv,fv_southern_carrier,ᑐᑊᘁᗕᑋᗸ (Southern Carrier),BC Interior,fv_southern_carrier_kmw-9.0.js,10.0.1,caf-Cans,Southern Carrier (Unified Canadian Aboriginal Syllabics) +fv,fv_statimcets,St̓át̓imcets,BC Interior,fv_statimcets_kmw-9.0.js,9.2,lil-Latn,Lillooet (Latin) +fv,fv_taltan,Tāłtān,BC Interior,fv_taltan_kmw-9.0.js,9.2,tht-Latn,Tahltan (Latin) +fv,fv_tsekehne,Tsek'ehne,BC Interior,fv_tsekehne_kmw-9.0.js,9.2,sek-Latn,Sekani (Latin) +fv,fv_tsilhqotin,Tŝilhqot'in,BC Interior,fv_tsilhqotin_kmw-9.0.js,9.2,clc-Latn,Chilcotin (Latin) +fv,fv_southern_carrier,ᑐᑊᘁᗕᑋᗸ (Southern Carrier),BC Interior,fv_southern_carrier_kmw-9.0.js,10.1,caf-Cans,Southern Carrier (Unified Canadian Aboriginal Syllabics) fv,fv_anicinapemi8in,Anicinapemi8in/Anishinàbemiwin,Eastern Subarctic,fv_anicinapemi8in_kmw-9.0.js,9.1.1,alq-Latn,Algonquin (Latin) fv,fv_atikamekw,Atikamekw,Eastern Subarctic,fv_atikamekw_kmw-9.0.js,9.1.1,atj-Latn,Atikamekw (Latin) fv,fv_ilnu_innu_aimun,Ilnu-Innu Aimun,Eastern Subarctic,fv_ilnu_innu_aimun_kmw-9.0.js,9.1.1,moe-Latn,Montagnais (Latin) fv,fv_swampy_cree,ᐃᓂᓂᒧᐎᐣ (Swampy Cree),Eastern Subarctic,fv_swampy_cree_kmw-9.0.js,9.2.1,csw-Cans,Swampy Cree (Unified Canadian Aboriginal Syllabics) fv,fv_moose_cree,ᐃᓕᓖᒧᐎᓐ (Moose Cree),Eastern Subarctic,fv_moose_cree_kmw-9.0.js,9.2.1,crm-Cans,Moose Cree (Unified Canadian Aboriginal Syllabics) fv,fv_northern_east_cree,ᐄᔨᔫ-ᐄᓅ ᐊᔨᒨᓐ (Northern East Cree),Eastern Subarctic,fv_northern_east_cree_kmw-9.0.js,9.2.1,crl-Cans,Northern East Cree (Unified Canadian Aboriginal Syllabics) -fv,fv_severn_ojibwa,ᐊᓂᔑᓂᓂᒧᐎᐣ (Severn Ojibwa),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,10.0.1,ojs-Cans,Severn Ojibwa (Unified Canadian Aboriginal Syllabics) -fv,fv_severn_ojibwa_rdot,ᐊᓂᔑᓂᓂᒧᐏᐣ (Severn Ojibwa right w-dot),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,1.0.1,ojs,Oji-Cree -fv,fv_ojibwa,ᐊᓂᔑᓇᐯᒧᐎᓐ (a-finals),Eastern Subarctic,fv_ojibwa_kmw-9.0.js,10.0.1,ojb-Cans,Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics) -fv,fv_ojibwa_rdot,ᐁᓂᔑᓇᐯᒧᐏᓐ (a-finals right w-dot),Eastern Subarctic,fv_ojibwa_rdot_kmw-9.0.js,1.0.1,oj,Ojibwa -fv,fv_ojibwa_ifinal,ᐊᓂᔑᓇᐯᒧᐎᣙ (i-finals),Eastern Subarctic,fv_ojibwa_ifinal_kmw-9.0.js,1.0,oj,Ojibwa -fv,fv_ojibwa_ifinal_rdot,ᐊᓂᔑᓇᐯᒧᐏᣙ (i-finals right w-dot),Eastern Subarctic,fv_ojibwa_ifinal_rdot_kmw-9.0.js,1.0,oj,Ojibwa +fv,fv_severn_ojibwa,ᐊᓂᔑᓂᓂᒧᐎᐣ (Severn Ojibwa),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,10.1,ojs-Cans,Severn Ojibwa (Unified Canadian Aboriginal Syllabics) +fv,fv_severn_ojibwa_rdot,ᐊᓂᔑᓂᓂᒧᐏᐣ (Severn Ojibwa right w-dot),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,1.1,ojs,Oji-Cree +fv,fv_ojibwa,ᐊᓂᔑᓇᐯᒧᐎᓐ (a-finals),Eastern Subarctic,fv_ojibwa_kmw-9.0.js,10.1,ojb-Cans,Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics) +fv,fv_ojibwa_rdot,ᐁᓂᔑᓇᐯᒧᐏᓐ (a-finals right w-dot),Eastern Subarctic,fv_ojibwa_rdot_kmw-9.0.js,1.0.2,oj,Ojibwa +fv,fv_ojibwa_ifinal,ᐊᓂᔑᓇᐯᒧᐎᣙ (i-finals),Eastern Subarctic,fv_ojibwa_ifinal_kmw-9.0.js,1.0.1,oj,Ojibwa +fv,fv_ojibwa_ifinal_rdot,ᐊᓂᔑᓇᐯᒧᐏᣙ (i-finals right w-dot),Eastern Subarctic,fv_ojibwa_ifinal_rdot_kmw-9.0.js,1.0.1,oj,Ojibwa fv,fv_naskapi,ᓇᔅᑲᐱ (Naskapi),Eastern Subarctic,fv_naskapi_kmw-9.0.js,9.3.1,nsk-Cans,Naskapi (Unified Canadian Aboriginal Syllabics) sil,sil_euro_latin,English,European,european2-1.6.js,3.0.2,en,English basic,basic_kbdcan,Français,European,canadian_french-1.0.js,1.1.1,fr-CA,French (Canada) @@ -82,23 +83,23 @@ fv,fv_isga_iabi,Isga Iʔabi,Prairies,fv_isga_iabi_kmw-9.0.js,9.1.1,sto-Latn,Ston fv,fv_lakota,Lak̇ot̄a,Prairies,fv_lakota-9.0.js,9.1.1,lkt-Latn,Lakota (Latin) fv,fv_nakoda,Nakoda,Prairies,fv_nakoda_kmw-9.0.js,9.1.1,asb-Latn,Assiniboine (Latin) fv,fv_tsuutina,Tsúùt'ínà,Prairies,fv_tsuutina_kmw-9.0.js,9.1.1,srs-Latn,Sarsi (Latin) -fv,fv_plains_cree,ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree),Prairies,fv_plains_cree_kmw-9.0.js,11.0.2,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics) +fv,fv_plains_cree,ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree),Prairies,fv_plains_cree_kmw-9.0.js,11.1,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics) fv,fv_dine_bizaad,Diné Bizaad,South West,fv_dine_bizaad_kmw-9.0.js,9.1.1,nv-Latn,Navajo (Latin) fv,fv_dane_zaa_zaage,Dane-Z̲aa Z̲áágéʔ,Western Subarctic,fv_dane_zaa_zaage_kmw-9.0.js,9.4,bea,Beaver fv,fv_dene_dzage,Dene Dzage,Western Subarctic,fv_dene_dzage_kmw-9.0.js,11.0.1,kkz-Latn,Kaska (Latin) -fv,fv_dene_zhatie,Dene Zhatié,Western Subarctic,fv_dene_zhatie_kmw-9.0.js,10.2,den,Dene Zhatıé +fv,fv_dene_zhatie,Dene Zhatié,Western Subarctic,fv_dene_zhatie_kmw-9.0.js,10.2.1,den,Dene Zhatıé fv,fv_denesuline_epsilon,Dënesųłıné,Western Subarctic,fv_denesuline_epsilon_kmw-9.0.js,10.0.1,chp,Chipewyan fv,fv_denesuline,Dɛnɛsųłiné,Western Subarctic,fv_denesuline_kmw-9.0.js,10.0.1,chp,Chipewyan (Latin) fv,fv_gwichin,Gwich'in,Western Subarctic,fv_gwichin_kmw-9.0.js,9.2.1,gwi-Latn,Gwichʼin (Latin) fv,fv_han,Hän,Western Subarctic,fv_han_kmw-9.0.js,9.2,haa-Latn,Han (Latin) -fv,fv_kashogotine_yati,K'áshogot'ı̨nę́ Yatı̨́,Western Subarctic,fv_kashogotine_yati_kmw-9.0.js,10.0,scs,North Slavey -fv,fv_tlingit,Łingít,Western Subarctic,fv_tlingit_kmw-9.0.js,10.0.1,tli,Tlingit (Latin) +fv,fv_kashogotine_yati,K'áshogot'ı̨nę́ Yatı̨́,Western Subarctic,fv_kashogotine_yati_kmw-9.0.js,10.1,scs,North Slavey +fv,fv_tlingit,Łingít,Western Subarctic,fv_tlingit_kmw-9.0.js,10.1,tli,Tlingit (Latin) fv,fv_neeaandeg,Nee'aanděg',Western Subarctic,fv_neeaandeg_kmw-9.0.js,9.1.1,tcb-Latn,Tanacross (Latin) fv,fv_neeaaneegn,Nee'aaneegn',Western Subarctic,fv_neeaaneegn_kmw-9.0.js,9.1,tau-Latn,Upper Tanana (Latin) fv,fv_northern_tutchone,Northern Tutchone,Western Subarctic,fv_northern_tutchone_kmw-9.0.js,9.2,ttm-Latn,Northern Tutchone (Latin) fv,fv_sahugotine_yati,Sahtúgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_sahugotine_yati_kmw-9.0.js,9.1.1,scs-Latn,North Slavey (Latin) fv,fv_shihgotine_yati,Shıhgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_shihgotine_yati_kmw-9.0.js,9.1.1,scs-Latn,North Slavey (Latin) -fv,fv_southern_tutchone,Southern Tutchone,Western Subarctic,fv_southern_tutchone_kmw-9.0.js,9.2.2,tce-Latn,Southern Tutchone (Latin) +fv,fv_southern_tutchone,Southern Tutchone,Western Subarctic,fv_southern_tutchone_kmw-9.0.js,9.3,tce-Latn,Southern Tutchone (Latin) fv,fv_tagizi_dene,Tāgizi Dene,Western Subarctic,fv_tagizi_dene_kmw-9.0.js,9.3,tgx-Latn,Tagish (Latin) fv,fv_tlicho_yatii,Tłı̨chǫ Yatıı̀,Western Subarctic,fv_tlicho_yatii_kmw-9.0.js,9.1.1,dgr-Latn,Dogrib (Latin) fv,fv_dene_mb,ᑌᓀ ᔭᕠᐁ (Dene MB),Western Subarctic,fv_dene_mb_kmw-9.0.js,9.2.1,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics) From 745bbd5cd57283f2d6445a862501613d673e60d3 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 28 Aug 2024 09:57:30 +0700 Subject: [PATCH 172/262] fix(android): Clarify directions on Text Size menu --- .../kMAPro/src/main/res/drawable/textsize_decrease.xml | 5 +++++ .../kMAPro/src/main/res/drawable/textsize_increase.xml | 5 +++++ .../kMAPro/src/main/res/layout/text_size_controller.xml | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml create mode 100644 android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml new file mode 100644 index 00000000000..3e8ea253f78 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_decrease.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml new file mode 100644 index 00000000000..b51b51139b5 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/textsize_increase.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml index c250b1bf072..cfd36374deb 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml @@ -15,7 +15,7 @@ android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:contentDescription="@string/ic_text_size_down" - android:src="@drawable/ic_action_decrement" /> + android:src="@drawable/textsize_decrease" /> + android:src="@drawable/textsize_increase" /> From 24daed27f7bf4fe964f8178c7b69a77f08834e93 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 28 Aug 2024 12:54:42 +0700 Subject: [PATCH 173/262] change(mac): minor comment cleanup Fixes: #2542 --- .../Keyman4MacIM/KMSettingsRepository.m | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index c4001b5acde..c7d655593aa 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -18,15 +18,14 @@ NSString *const kUseVerboseLogging = @"KMUseVerboseLogging"; /** - The following constant "KMSavedStoresKey" is left here for documentation - though we have abandoned stores written to UserDefaults with this key because - they used a less-reliable numeric key prior to integration with Keyman Core. - It is replaced by the renamed "KMPersistedOptionsKey" which directly - represents what it is saving. + * The following constant "KMSavedStoresKey" is left here for documentation + * though we have abandoned stores written to UserDefaults with this key because + * they used a less-reliable numeric key prior to integration with Keyman Core. + * It is replaced by the renamed "KMPersistedOptionsKey" which directly + * represents what it is saving. */ NSString *const kKMDeprecatedPersistedOptionsKey = @"KMSavedStoresKey"; -//NSString *const kObsoletePathComponent = @"/Documents/"; NSString *const kObsoletePathComponent = @"/Documents/Keyman-Keyboards"; NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; @@ -152,7 +151,7 @@ - (NSMutableArray *)activeKeyboards { return activeKeyboards; } -/* +/** * returns dictionary of persisted options for the single selected keyboard */ - (NSDictionary *)readOptionsForSelectedKeyboard { @@ -170,7 +169,7 @@ - (NSDictionary *)readOptionsForSelectedKeyboard { return selectedOptionsMap; } -/* +/** * returns dictionary of all persisted options for all keyboards * (options are stored in UserDefaults as a map of maps) */ From 0aec7237c4a7ccb2ecc01ebbc65762c4bb9cee1a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 28 Aug 2024 14:29:19 +0700 Subject: [PATCH 174/262] docs(core): fix a typo in the KMX+ doc --- docs/file-formats/kmx-plus-file-format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md index 75e1df42950..3b7003f09a8 100644 --- a/docs/file-formats/kmx-plus-file-format.md +++ b/docs/file-formats/kmx-plus-file-format.md @@ -456,7 +456,7 @@ For each key: |---|------|---------|------------------------------------------| |16+| 32 | vkey | int: vkey ID | |20+| 32 | mod | int: modifier key flags | -|24+| 32 | key | int: index into `key` sibling subtable | +|24+| 32 | key | int: index into `keys` sibling subtable | - `vkey`: If this is 0-255, it is the resolved standard/predefined vkey (K_A, etc.). It is resolved because the `vkeyMap` from LDML has already been From 2a6e9507eeaacf5fc8dc7da249fc1e5d33ccb69c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 28 Aug 2024 14:30:16 +0700 Subject: [PATCH 175/262] docs(core): improve formatting of KMX+ doc --- docs/file-formats/kmx-plus-file-format.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md index 75e1df42950..ddca82a6e1e 100644 --- a/docs/file-formats/kmx-plus-file-format.md +++ b/docs/file-formats/kmx-plus-file-format.md @@ -368,8 +368,8 @@ For each element: Either `to` or `id` must be set, not both. Entries with an `to` field are sorted in a binary codepoint sort on the `to` field, -followed by entries with an `id` field set sorted in a binary codepoint sort on the `id` field. - +followed by entries with an `id` field set sorted in a binary codepoint sort +on the `id` field. ### C7043.2.15 `key2`—Extended keybag @@ -402,7 +402,8 @@ For each key: |28+| 32 | multiTap | list: index into `list` section with multiTap key id list or 0 | |32+| 32 | flicks | int: index into `key2.flicks` subtable | -- `id`: The original string id from XML. This may be 0 to save space (i.e. omit the string id). +- `id`: The original string id from XML. This may be 0 to save space (i.e. + omit the string id). - `flags`: Flags is a 32-bit bitfield defined as below: | Bit position | Meaning | Description | @@ -412,7 +413,8 @@ For each key: - `to`: If `extend` is 0, `to` is a UTF-32LE codepoint. If `extend` is 1, `to` is a 32 bit index into the `strs` table. The string may be zero-length. -- `longPress`, `longPressDefault`, and `multiTap` refer to key ids or lists of key ids in this same `key2.keys` subtable. +- `longPress`, `longPressDefault`, and `multiTap` refer to key ids or lists + of key ids in this same `key2.keys` subtable. #### `key2.flicks` flick list subtable @@ -424,11 +426,14 @@ For each flicks in the flick list: |12+| 32 | flick | int: index into `flick` subtable for first flick element | |16+| 32 | id | str: flick id | -- `id`: The original string id from XML. This may be 0 to save space (i.e. omit the string id). +- `id`: The original string id from XML. This may be 0 to save space (i.e. omit + the string id). Elements are ordered by the string id. -If this section is present, it must have a 'flicks' in the list at position zero with count=0, index=0 and id=0 meaning 'no flicks'. +If this section is present, it must have a 'flicks' in the list at position zero +with count=0, index=0 and id=0 meaning 'no flicks'. + #### `key2.flick` flick element subtable For each flick element: @@ -438,7 +443,8 @@ For each flick element: | 0+| 32 | directions | list: index into `list` section with direction list | | 8+| 32 | keyId | str: id of key | -If this section is present, it must have a 'flick element' at position zero with directions=0, flags=0, and to=0 meaning 'no flick'. +If this section is present, it must have a 'flick element' at position zero with +directions=0, flags=0, and to=0 meaning 'no flick'. There is not a 'null' flick element at the end of each list. From cf3eef418a31e8d1d1aedb534656dcb1a234b467 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 28 Aug 2024 14:32:27 +0700 Subject: [PATCH 176/262] chore(web): remove obsolete comment --- web/src/engine/js-processor/src/mock.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/engine/js-processor/src/mock.ts b/web/src/engine/js-processor/src/mock.ts index 151d8313caa..e44b76e759c 100644 --- a/web/src/engine/js-processor/src/mock.ts +++ b/web/src/engine/js-processor/src/mock.ts @@ -1,7 +1,5 @@ import OutputTarget from './outputTarget.js'; -// Due to some interesting requirements on compile ordering in TS, -// this needs to be in the same file as OutputTarget now. export class Mock extends OutputTarget { text: string; From 5b570517b174ce1ffd294629b0249011ba055260 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 28 Aug 2024 16:14:17 +0700 Subject: [PATCH 177/262] fix(linux): display error and abort if fonttools are missing --- linux/keyman-config/keyman_config/__init__.py | 9 +++++++++ linux/keyman-config/km-config | 17 ++++++++++++++++- linux/keyman-config/km-kvk2ldml | 8 +++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/linux/keyman-config/keyman_config/__init__.py b/linux/keyman-config/keyman_config/__init__.py index 2bb3cdbaa25..26fbeddf7dd 100644 --- a/linux/keyman-config/keyman_config/__init__.py +++ b/linux/keyman-config/keyman_config/__init__.py @@ -1,5 +1,6 @@ import atexit import gettext +import importlib import logging import os import pathlib @@ -53,6 +54,14 @@ def initialize_sentry(): SentryErrorHandling().initialize_sentry() +def are_requirements_missing(): + try: + ttLib = importlib.import_module('fontTools.ttLib') + except ImportError: + return True + return False + + class FileCleanup(): """ Allow to register files that will be deleted when the process exits diff --git a/linux/keyman-config/km-config b/linux/keyman-config/km-config index f40ebeebb4b..641fb964706 100755 --- a/linux/keyman-config/km-config +++ b/linux/keyman-config/km-config @@ -9,7 +9,10 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from keyman_config import __versionwithtag__, __pkgversion__, add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running +from keyman_config import ( + _, __versionwithtag__, __pkgversion__, add_standard_arguments, + are_requirements_missing, initialize_logging, initialize_sentry, + verify_dbus_running) from keyman_config.handle_install import download_and_install_package from keyman_config.ibus_util import verify_ibus_daemon from keyman_config.view_installed import ViewInstalledWindow @@ -42,6 +45,18 @@ if __name__ == '__main__': initialize_sentry() verify_dbus_running() + if are_requirements_missing(): + if args.install or args.url: + logging.error('Missing requirements. Please install python3-fonttools.') + else: + dialog = Gtk.MessageDialog( + None, 0, Gtk.MessageType.ERROR, + Gtk.ButtonsType.OK, _("Missing requirements. Please install python3-fonttools.")) + dialog.run() + dialog.destroy() + sys.exit(1) + + logging.info('Keyman version %s %s', __versionwithtag__, __pkgversion__) verify_ibus_daemon(False) diff --git a/linux/keyman-config/km-kvk2ldml b/linux/keyman-config/km-kvk2ldml index 34c4e76fdf5..2ddddc5becc 100755 --- a/linux/keyman-config/km-kvk2ldml +++ b/linux/keyman-config/km-kvk2ldml @@ -5,7 +5,9 @@ import logging import os import sys -from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running +from keyman_config import ( + are_requirements_missing, add_standard_arguments, initialize_logging, + initialize_sentry, verify_dbus_running) from keyman_config.kvk2ldml import parse_kvk_file, print_kvk, convert_ldml, output_ldml @@ -25,6 +27,10 @@ def main(): initialize_sentry() verify_dbus_running() + if are_requirements_missing(): + logging.error('km-kvk2ldml: Missing requirements. Please install python3-fonttools.') + sys.exit(1) + name, ext = os.path.splitext(args.kvkfile) # Check if input file extension is kvk if ext != ".kvk": From ce5c590db2282a7692ed43d42a54b68ddf248adf Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 28 Aug 2024 21:49:58 +1000 Subject: [PATCH 178/262] feat(windows): update setup form SIL logo --- windows/src/desktop/setup/UfrmRunDesktop.dfm | 975 ++++++++++-------- .../src/desktop/setup/keyman_setup_sil.xcf | Bin 0 -> 36105 bytes 2 files changed, 569 insertions(+), 406 deletions(-) create mode 100644 windows/src/desktop/setup/keyman_setup_sil.xcf diff --git a/windows/src/desktop/setup/UfrmRunDesktop.dfm b/windows/src/desktop/setup/UfrmRunDesktop.dfm index c9872c3b511..c298bf1243f 100644 --- a/windows/src/desktop/setup/UfrmRunDesktop.dfm +++ b/windows/src/desktop/setup/UfrmRunDesktop.dfm @@ -30,412 +30,575 @@ object frmRunDesktop: TfrmRunDesktop AutoSize = True Picture.Data = { 0954506E67496D61676589504E470D0A1A0A0000000D49484452000002BB0000 - 0067080600000000008AFF000000017352474200AECE1CE90000000467414D41 - 0000B18F0BFC6105000000097048597300000D8A00000D8A01FD2DA348000032 - 5A4944415478DAED9D097C54D5D9FF9F3B6B32931D02041209229B828A056BF1 - 558322B5B56EADEBEB025671EFDF0DB10256D00275A9A27D5BADD58A5A972E2A - 2E6D5544032AB24A70615F12CC0209D993C93699F99F27F74EBC33B9F7CEB977 - EECC2433CFD7CFF94466EE3DDBDCE577CE79CEF308DEBBC00F0441104452B2FA - CD29F1AE0291C4AC7CFAAFF1AE029104082476098220921712BB443C21B14BC4 - 0212BB044110490C895D229E90D8256201895D8220882486C42E114F48EC12B1 - 80C42E411044124362978827247689584062972008228921B14BC41312BB442C - 10FC7ED2BA0441100441104462426297200882200882485848EC120441100441 - 10090B895D822008822008226121B14B100441100441242C2476098220088220 - 888485C42E41100441100491B090D825088220088220121612BB044110044110 - 44C2426297200882200882485848EC12044110044110090B895D822008822008 - 226121B14B100441100441242C2476098220088220888485C42E411004411004 - 91B090D825088220088220121612BB04411004411044C2426297200882200882 - 485848EC12044110044110090B895D822008822008226121B14B100441100431 - 8011AE7936A1C49CFFA51B0433F323B14B1004411004318021B1AB0D895D8220 - 08822088010C895D6D48EC1204411004410C6048EC6A43629720088220086200 - 4362571B12BB044110044110031812BBDA90D825088220088218C090D8D586C4 - 2E4110044110C40086C4AE36247609822008822006302476B521B14B10044110 - 04318021B1AB4DD289DDB9AB77CE028B7F76CF3FFCFE524180958F4E3FF6ED78 - D78B2008225A088290CDFE4C65691A4B53583A86A571B24376B154C6D25E96BE - 64690B7EC6DE0F6DF1AE3B4110E121B1AB4D5289DDB91F6F7F813579B6C2570D - 2C15835F58D96D6D7FFB89A2131BE25D573DB01719BEB4FECED209610EDDC6D2 - 65EC37DF65429967B03F2FB23492F3141C50DCCACAAE88675F1144B2C0EE5127 - FB339DA55B59FA99812C9A595ACDD2B32C1593F02588FE0B895D6D9246EC6A08 - DDBE084CF8FA80095F8109DFF1A5F1AE7BD8EAC658EC92D02588FE0BBB3FF125 - 3199A5C5604CE42A81B3BE7F606905BB8F6BE3DD4682208221B1AB4D52885D5D - 42B72FA5AC87565A2DFE171F293AB624DE6D5122966297953591FD798EA51F72 - 9EF2214BB7B132F7C4BB9F0822D19166736F6069094BE95128029F1D0FB2F406 - BBA73BE2DD5E9DFD82038014D9C75E96B6B17634C7BB7E0411292476B54968B1 - 7B67714996D5EF7C0BFC506452960DEC2758D9DFEC7C632576593913D81F3670 - E016BA1B58BA9695B723DE7D4410890EBB3FDDECCF7C29A981C20E07EDEB590A - CCD05A599AC452214BA77016F7164B73D9BDBD3FDEEDE6EC9B41ECCF2B2CFD58 - F6B169665D04116F48EC6A93B062775EF1F6137DDDC20B20C0896AC7D8AC42D3 - A014CB6E6F77B7B3C6E39B64B158F414D1E00761F9EFCF1CBF38DE6D8D85D865 - 658C607FFEC8D2059CA790D0258818C1EE4F1BFB73374BBF5339E43596FEC4D2 - 46764F766AE4930AA2F0BD9CA59F83B6A9123E47EE60F9BD1FEFF673F40F895D - 22A121B1AB4D428ADDB9AB77DCCE44EEA2E16ECBFEA64E5F8ADBEA1B7AB8DD82 - 0F3B18ECB26FC9B0FBB35D0EFBA0B45467A6FCBC5D15D50DF5DD8E2C3D65592C - FEC9F1366F88B6D865F90F657FFE0CFA842EDAE86E8967BF1044B2C0EED15341 - 1473A1E214EFF97B585ACDEE479FCE3C71A6F8621045F42485437076F746966F - 4DBCDBCFD11612BB44424362579B8412BBF77CB2FD02BF4F5884B3B9D9A9F612 - ABB76D58B7603B227477E5D575DB7BC4EEA4A1AE52B7D3511838A7AAAEB1C3E9 - B07565A4A6B86D56AB5072A0A2D623A40EE29DE5EDB608A3E2BD892D9A6297E5 - 9D03E26CD11CCE5330EF1B58196BE3D92704912CB07B3403C4CD63D7847C65CA - EA8A247AD10E78214B39D2C798E7E52CEFAFE2DD7ECE3690D825121A12BBDA0C - 78B18BE60ADD3E6116EB950B41B439EB25DD61DB75DCB0B471D50DCDB0F38807 - 1C0E67606637674866DAA8F223F5D0D025EC28C8700ECE74A7E6E2395DDE6EF8 - F6BB43B5ED56F7A0403E68EE906EB7EC6FF0740CF75B6C43BEEF3D78F1B1E913 - 66C7BB0FA22576A597DC232CDDC25915DCB13D8BE5BF26DE7D4210C902BB4FCF - 02719655BE21CDD47B51E6E161194B3F62E926965EF30F901708895D22D121B1 - ABCD8013BBB8E9CCE2733E20E0A6330D7B5C64689A75B3C7D391E5B4DB5B335D - F6D4EE6E5F4A63AB2763507A5A4D5D53D3D195AD3E6B4E7ADA9E3183538F4A75 - D871B72EECADAC69CF4A733794D6B678BB7CFE144F47A77FFAF811199BF7957B - DC2E57CD91968E7C67AADBC50E6DECB60827CA67758B474DCEEAF2DA67E1FFDB - 6D5D2F161DD81A137FBDD110BB9C9B5DE4E0CBF5972C7D32505E800431D09144 - E802961E0AF9EA2996E699ED31415AE9B988A5570792DF5D12BB44A24362579B - 012776EFF9686791DFE2FF44EDFB3487757757479BA34370148E1DEC5A93E372 - 9C117A4CA3A7ADA3BCBEA522D3ED3AE2F37A3306A7A78EB6DBAC56BBD51A64BB - D0D8DAD6D5D4D6E139DCD279B80B2CC3058B2DADB7E398EE7EF4CC09CBE5C77F - 943F15EB5524FDB3C466F74E8F85E0355BEC726C76090585EE5D2CBD45429720 - 6207BB57713617378E5E1DF2D54C762BAE8A77FDFA0B2476894447AFD8C561F2 - 4985B970C6F83C38664806DE235076A419B61DAC852F4B8FC0E1A6BE6359AB45 - 00FC2F800FFCE0F305176B67324A0891A95D5E3C521F492F7691B91FEF409139 - 9BA5A00D662874270C71175A2D164793A7DDE372DA7B34AC1965767577FBBE39 - 585D357C50567963ABA763FE69138344F4EAFC29CBFD20DC1E725AC98CF24D93 - A3DD1F668A5D49E8E212E552E0F3D3D90C262E694AE58F66E97FA4846D92F7E1 - 5610ED82D749E96BADDDE5898CD457E80E0E4D787083128683C599B73A107F6B - 742FF51F08B3033F244F7CC0E4B33413C4FE1F0FDFBBA3C2410DFA4BDE04A2FF - E40D036976CF0CA4150FEC6BDCAC795248DF7C0B629FA37782ADAC6FBC31A80F - 9A5FBDCAD20CD9C75FB374292B7F67BCFBABBF4062974874F488DD1F8D190A4F - 5D350D4E1A99DB4798223EF62ABFFF8DCDB0ECDDADBD9FA5D8ADF0F5D24BA030 - F77B59F0EABABD30EBD9E0B9C7B50BCE87696386067D36F4B697A1B6A55D577B - 48EC4AA03983CDE7B88309CC3B4012BDE307A79664B99CBDA60D0D2D1EA86E6A - 3D929F9399ED4A7158CD28B7AEC5537AB0AEA9E8B7674E2E0B7CF651C1D42236 - 6C519C6D16C0BFE2ACF2CDD746B32FCC12BB92D0B982A56720C64257DA647325 - 4B3783F2CE6F35F077789EA51759150EAAE43D0A44D74B3CFE816F62F9FCD960 - 1BD06DD3132CDDC87138CE9CA3107D1DC2FF6E1F60DF042257B1727005E24C10 - 07245339CA42F183B6966FAA2D6B4B79E2000E67E879A36E61DF3FC9D2B32CDF - 56837DE6607FD0AD1DDEB728DCF1B747C18ED783DCE72B3E7571D77F254B9FB1 - 849E3EB6EB1DE8B0F2F01AFB1BC7A1BF61793F243B4F699356447D6E062AF73E - 89B81048EC12890EAFD8BDE0A44278FD96B3C069D7964437BEF029FCA5F8FBBD - AD2876BF5D76298C9289DD57D6ED81ABFF1C2C7D3E5B78411FB19B7BEB4B2476 - 2325207A339CB673321C82B3BDCB6BF7777B0795D7B7B81CAE8C8C805785B139 - 293B5ADBDB87170CCECA345A161BED7898783DF59231237A5D8D952E5E9EB56D - D5E77BDCA5A583D5CE8BB6E03543EC4A42F732109744795EE62874EF0451641A - 9EC192221BFD82A5DFB0342E826EC0D9CC47B1FEA1119158197857A370B98F23 - 9F9741749BA63BAA928EDFA10AC4D9D846CEE37BC5AE6433896DE1DD3428E79F - 2CDD191AB699E5390CC4885BBF34902782B3BCB7F3CE24B2F2B241141DE8C715 - 3757F15C6F4AE06F8E8398BFB254C2E35ACB88D865E7E00C2EDAC09E6AA08E78 - 3DA1EDEC21836D0CD79E012B76A5670EBE158B583A0D8267CA119C25C76B0A07 - 371FB1F49D5EF769B2B248EC2A20DD8BD358FA2988034D1C74A29A09041FC141 - 1B3E7FD6B37EAA8E777D097578C4EEB0CC54D8F1F0659099EA089BDF49BF7903 - 4ACABE8F0C4E62378A7816E4BD037E388F35FB73BF5F78CB39F607DB6CD7BEF7 - 91DAF18F7CF6D573992ED7150D9EF63AAF5F68AEEBF01504EC6CD11599A5CB93 - 376AE8A04136ABC5A6B72EAC979AC0EF3B432E74918F27CE5CED1F9493EFDDB7 - 6FACD638299A82D724B18BB37A2F82B613F900F8204421F04484421745167A7B - B8DA681E0ABCC7D25DA1E1895576AC2B81E75D6CC4A5920E21D523A8591A0E3A - C42E88A14E71E6F89208FA0723FFA16FD4C3529D7167FDD31C750887A69B2B69 - E61867D67125003738991DCAF625961E60E5976A1DA447ECB2F45B1067BB71A5 - 23928158509F9B096B0F5E4328E28A42BE3AA3BFBAFF33B88A807C0AE24AC27B - 5AB3E53A575878095D5DC181CF6721C7E08AD09D6699F6A89411B4E2C0D1E6FF - 61C77FAE707C2188D738FA51E65DC543F31C1CF4AD333AE820A2078FD8FDDDA5 - 27C3BC73FBEEEB6FEDE8823D879AC0EBF3C1886C3764B91C9075D30AE8EAFEFE - 6726B11B253CF3F37049BA00FFDF923964A7FFE893C1969D3BBEBDA6A6BAABA5 - 7169CE9C7F3CA976EE931B77FCCEE570DC989DE6CADA515E7DA8C96F73DB05A1 - 657CAE3BA5B9ADA36568567A819EBAB01EDAD6ED152EBC7CFCB052F9E7ABF3A7 - BCE00761366F3E4CF03EC904EF1D66F755A462979D7F3AFBF32CF0BFD07FCDD2 - EF2314BA58169A1F18992D0B479FA016D20C06967711C7F9D7B3739FD7D91E9C - A146E1FEFF78F3D7F1BBE18B16CD1E70F69537B08716FFC7D23C964E06FE010E - 0FAAA24E3203C072679B5496123D11BDB0BFD4CC6A748ADDB526F60F462F9B67 - D4DC430DD61E5CA97A0E44D12207AF9727FADB8651565FB401C715183D223714 - 1CD0DECF9AA618CC87C46E104162573A0EEF91B9607C45856B6049C496706217 - 378E6D79F0E730313FF8675FB7E7305CF4E4074C8C76F41E37B970106CD85B1D - B4A98CC46E1468BB2FEF36BFD0E3241DACC7CF28B60C2E280A3DA6ABB9A9ADA3 - F6C8CBD9D7BFAEFA407BF4F3AFAE670395BB2A5A7D13C6E5BA0FEEA8ACCF9D7A - F4D0C39EF6CE82DCCC34EBB60315AD8DDD36F7292307B73478DABA8664A66587 - 64D1C8BA7CF9C5C70C5F149AF7FAABE6BDEEA9AF3BC55352323245E0FF4D9838 - BEF6ECF28D2BCCECAF48C42E3B17ED245F003E7B56045D913D1E891DA281328D - D067A691958B66178F739CBB82A5DBF4081396370A22DC28342DCCA1BDBF81CE - DFED3B884C20C8C1591A147CB8746996D00D802FD2A79444166BEF75200AB368 - A2E95F5687D8C5592C7C1E98758D629F63B095D7CD6CAC86890E5E3357F49770 - DDD26C2E9A2BA1D035E39A4313965FB1F47AE82C2389DD207AC5AE14F23DD295 - A100789F6164BE376896B77F104EECA6A5D861D72397415EA62BE8F3E9CBDE85 - 353BABC2E64F62D76C168F70793A7C3D22C37ECACFD7812B53533C74B7B77B3B - 6AABD7648E1C7331143DA0E8E6EBCEE29D85369F6F36CEC21E3B38A5B3ACBA6E - 48E1D0412D95758DCE4EB0D53577C1D8F1B9A9DFE4B85327E2F13EBFBFC222C0 - 73365BE7F20B0B0BFBE4B9F186074ADA1B1A0A3A76ED6EB2646576E68C19E76D - F7768EAE5DB5CAC9237CCD16BC46C5AE01D1891BA29646323B6520F47024E00C - C4AF587D9BA4B28F03D16E754298F350205CC2CEFB5647BBCE077166331CBDFE - 4F75FC6E03091C64A0C83AA0D047BCFD6F461D144D2A7488DD68F00E4B73CCB6 - 7D646D42DBE73714BEC2EBF1D6501BED58C3E1E10585EBBF411C60544A9FA188 - 3F16C48D9867A99C870308B45D7F552EB848EC06D12376D9F763405C5999A950 - 14F63F7AB6D9C852A7D4F7F84CC2F782D6C004FB1F273F9E8985E711421B2EB1 - FB3013BB59C16277C6C3FF868FB7877F4490D83519CFFCE11FB2669E0D29EEC3 - F66997E2D2671ACF797E5FB7BFBDA6669BB7BDFDBE9CEB5E7D5FEDB85BDEDD74 - 43BA3BF5948943D37F9AEA74F4FC221B761F6CB0B932BF1D3728A52CDDE5FCE7 - 2F46E7AD543A1737A3D555377F9199331897E2A072EFDE4ECFA76B1DEEDCDC5D - D63163BD5D020C3EF2D9A7433384F0A186CD14BC46C4AE34CAC7CD68BCA233E2 - 6558E9A58726100F19CDC300BD338D464C0D38DB85D6FEE893F84E8EC32F60F9 - BE239D97886217B98AB5F195D00F6364CA10405178C459ECA238B888D569B599 - 99B236E58138B09BA1F035BA89C37BE08B78983484F1F082836E5CC17B393020 - 55C9033797E1E6497C76842EBDF7993197EE479C453E4A761C8AC14B2178A085 - D359683F5FC7D114DC4CFA37564E8B54C68011BB2CED85BE130CD86FB88701CD - D7362889556936BE10442F247340D9ECC1948DCA44E48413BBE823F7D385E7C3 - 29A38385E86A26742F58FE01783AB47F3E12BB26D271DF8899DD820F47D0609D - 34BDD8925B5864249FF62335FBBDEDADCF64CF7EF551B86F64362C2BAB8FB46E - 9B6E5C7C434666E6C336A7334BFEF986E79F832192C787A13FFEF1BA0307CBA6 - D976EC0007C46E8657AFD8650967ABF5CCAE9A626F28D906E34B298FF314DC69 - BF42AA37DE89289671B6011FBCBC4BFAA1B3D96AB360A160B95CA60C1A9B8442 - C1CD95D7B03CABA4F31255ECE24BF7D74A6EC16264CA80280ACB388B5D04DD91 - A1BD69B79999B2765D0EA27051DB6C8462184D08B6C772D95963E32B0AADB9AC - 2EFB75E4A5E615437373A474AEA9DE180690D83D87255C75927B6F41EF2968F6 - B2957700246D68C399F92B14BEC6E7190E703F36A3DD84317836A8CD3E6D2CFC - F5FAA23E9F6F3A5003B39F2D861D95EA5289C4AE592C166C9E8E615D817FDA4F - BBA204EC292746926567637DB5B7A9FEADCC6B5FBFC9681E6BAFBA77566676D6 - 436959597D36B5757576C2972FBFD42B76475C76D98E92E24F46E71EAE0EEFD7 - 438209DEC94CF096F01EAF844EB18B4211BD42DCCC99FD5F40142E3CB31F5A75 - 44BFA9388B730DC7E1F8F2C10D36FF557A314B330E3F0171669AC7FEAF77B34E - 98593039DCA60C2CCFB3417C8184E37E9696045E3006C52E8A38140F3868D80D - 18C4469C7D3997250C6A6274D309123AB81802E2CB125FA07ABC11E092F4D5AC - 997D9E9C614C19D0D55493547EC0E70D3E5931A8089EA7D7D6B38FE8362876F1 - DA7F526A57A9F4592118EBF37F81B86AD0A8B30E9A48B3E6B86A11CE251D7A34 - C0DF197D3C1B76E5C55927F4B6F29CD44F727036F56E56768D813CD596E37B36 - 5DAA09CD2416BB78CDCAFB1F9FC10B0CBA56C49531F4A0B154E16BB40BBE8A36 - ADC50F1EB18B9BCF3EB9EFBC3E6214C199DDE51F7E0D0FADFC123ABC7DC7E224 - 764DC2333F0F6FC2DB02FF8E64663708D6BEF623D5ADA9B9C316DB042672CE5A - C2E5FE67E39C0716399CCE39E9D9D9C3B58E3BB0ADA4213523C3DF527AA02E6F - F20F5277BDF9AFE1D91C660C321A98E09D1E89E0D5E9DF15FD7DEA89EA8662F7 - 4E136675795D7F696E300AC913EDF950B8849B29EE9D5195965517009F294558 - 53066983D003200A592D7A7CEBB2FC36CACED52B7671060B37E56C56D90086E6 - 3528CA6672E61700FB1C5DA1A90D2EF4BA88D37271173065C065669C75459323 - 5CCD29D75A0695063868C3899B6278064C483188769695B27CF48A5D45DFC4B2 - FC8E0671B6F15CCEFCD043C8E52CBFBD3AEAC085E48319CD69E6709E82BFCF9B - 523F61BDEACC327590EE3334175A1EF255C46ED8241188C2553EF8D134114962 - B12B079F0D0B4C3045530B258FF6BB8F98BD6A41F0C11B54A220270D56DD7B2E - 8C1DA61C720067792F7EEA43F8AE2EF83221B16B02EDF7E715F9BA8323905986 - 1DBDDE7AEC19A718CD53CEA18FDEED1876D6CFEC4C5456B29BF571FB8C254F28 - 1D57BAF899C2C6BAFA67535DEE33AC36BBE6EC6C6D4545D3A15D3BBB5CDDDE7A - D7D061AD35BB761664E70DAFA8DFBD7B925B87770689069BDD3BAAE8C0D606BD - 272231580E4737558F1B7D88E9B49545FBB0E7785EBA3A6D80E5B6B2E8720BED - B2C389E41510C69481E585C14470A6EA9C3079F599D1D3F9BB710D02589EC783 - 38EBCBBB098C2B0A9E64E38DED9CCE91A7A6AF626976EEB0969DA6463DF0379F - 05E28B9DC73F6890BF599D62179F495787DBE0A573A3675443F94AA207C5C83C - CEFE9183A20FC522CEFAAE539A99D7510FA5A885A62C776B08E9DECD9F0AE724 - BBD8C541DB8D91FCA6B2B2DC5259A1832ADD1B7B09F3D0132E78707A0ABC7CE3 - 74387B623E5814F4CAAEAA0698F6E0DB50EFF9FE5622B16B02729FBABD1573BA - 0ED94EBD6C9819F91F2AFED03BE4B8B19F7D3F532C3CCB046FD04301379F7575 - D9BFB30816AE0D715FAD5DD3993F6AD4A1CA55AB8E32206E95B87346F9A6E546 - 4E8C81D8E59E6D55A91FAF5BAE209B56CEBC794301F72E69EBD82815F6E12DBD - 90FE0BE185459F59629DBF9BEA8B3C244FDE99E60038DB7E1DCF4B50A7BDADA2 - 337B3390064FE873F86E8EC3F1BA7D4976AE1EB1FB0B76EE9B9C75E2756B17D5 - BE91EA820F245CBD590CC6DDD5E12008852FDA0117EB117052F9D74BE7CAF93D - 88338B11874F56197CA9DEAF492E76B15F2E37122847A36E6A83EA7EE9DF3919 - D0237611DCB076E5B431F087AB4F85F4147B9FEF577CBA1BAE7BAEB8D7D72E89 - DD08699F3F7CA10FFC8A3373669932547DF2210C9D3836282FBF0D26388A96F6 - CEAEECBDFF992201FC9FE8CDBBEE5095AFDDD3D25EF9F917D6415EAFD3702505 - 583CE3BB4D8B0C9D1A9B8D4E866DB274B8E5BA89E5FF679D79F386024621FDBF - 013B411DC24DD59441874984E24B58E7EF36939DBF8AB34F702320EFC084BBCF - 2533897F801856341CD11674BCB3F341E24087D82D8610138830F5C1973FCEDE - 8F8977DFC8EA14885A87A62F574490150E76D1CC6C45C0F556987295025DF431 - E389B06D6AF79EE2F59CE462D774F3028D4135F7E0993017BD6237009A33BC7D - C78F615C5ED0DE7B6869EF82D1735F839A6651A492D88D80B68523A6F97D3ECD - 87BE75E8D15F76A60C72415A768AD5959E6D4FCFC8E4CD3F008ADD21E347ADB1 - E68D3923F0995FF0CF779CB56C59E0DF7B16FCE9448B45D86AB42D8DB5B5B07F - F5AAA6F4564F86D54806024C6762B7D8D0A9B1DBD56FC82B03AB1F0AD1A5610E - 8B244C2FCF06317CD99E1788AAA6634658F505A6232A9BE2ACAC19619E55FAE3 - 1810675D7EC07138B7F092DA8BB3693CF6A9D116BBB920AE1684DB6888D72C6E - 866A97CEE315BBBA848B8A988A4BDFA8D40F371B5E08A2FBADB30C6683D7DF83 - 200612D00AD78BD7DDBB103C10E909916D64639446394AAB2A41BFB7ECD86415 - BB51332D501970063D6789D86154EC226398E0FD7AC925E0B005EF37BAE24FAB - E1EF1BF6F5FC3F89DD08E8F5A9AB03EBB853FF6B1931F6277ACEA9DBF625A45A - DABF4D9D34EDB8DE0FFDFE75F6B39705B9B0D97DDF1F0E5B6DB62146DBF3CD9A - 4F1A1C35D55DF6E6D65C9DA7AE9951BEA9C868B9317661859B945ED1E1B286D7 - C1BBEA0E7E8E3278671CE576BBBCCBFD5A9BAD945EEA4A282E87EB0C177C25CF - AC9A942FAFF0D22BA2F538EB0F2BE824FB5BBC27A7B0846EA5C68238332ADF78 - 8403D01AA9AEB889139DDF978328707884779000D1132E584954C4AA6FA28934 - 68990AA2AB3CB435D7B36115414179BF9A8716953EBE8B1DFF44D89CF5B543C9 - E55FD00A8EECD86415BBA6D629A44CB550D541A643446C8844ECA249C3C6453F - 87C92307057DBEF08D4DB0F41D710E90C4AE41DAE6E7CD63253F6CE45CEBC917 - 6EB5A465733FA05BCB0F42FBC19DF583A6CD0C0A076CCF2E74C00F6EE87577B6 - 77C19F5E162CC25546DBB4E59FAFB7E4B478D26C191915DEDC5C0FECDB177649 - 5300FF36ABBDBBC8E8E6B49E3C622B767149F34A1DB381BCC20B67089E063182 - 8F5EF0A18B4B9A53C31C17F4C2D5E121422D48028F9D6631A82C8727ABD865F9 - A0B37FDC6486A6244642C7E23588261D682F786A986349EC86AF3BDE3F183D12 - 852F0E1E789EAD8A5E1524F38207A42487DB0C4747BD3114147A18B85EF6B1E2 - 06C02416BBBA4DC374D44FEDB7D675EF10E61089D8B531B1BB6DC925306178B0 - 29C3DDAFAD8727DE17175B49EC1AC0B370F814F0F937193DDF7ECACFD7822BF3 - 743DE794BFF152C7E0A93FDADDD9D631C2027E8FBFABA3C1D2EDDD9875F7C7D7 - 058ED977FFD3B8CCF796913AEDFE720B7496944060B35A46D1F45DEDDECEACE6 - 3D7BBD368FC76E6D6E569A316EF4835014433FBB4AE04B0B378EA0494738E120 - 3F87CB7D90CE25F568136ABFC96B86D0E725A631AB110A96B758C95E2ED9C4AE - B43110BD6D2C84C8FC01EB81C4AE0E240193CF12065FC167A3D66A491FB32695 - 7E306CA2C4515725B1A574ED25ABD88DB63991D2FD14B5D964421DE72F9FF787 - 5587020485BBB25A2C302C33156E3C7302CCFD49DFD7D0E94BDE81CF761FEAF9 - FF48C46EC11DAF405D6B07F0EA4D3CAAFDF9EB1240EC2EC87B87B5E63CA3E71B - 0938D1527600BA0E95D5A53ABCD5FE6EBF0DEA2B8EB158FC8BD396552D921FC7 - 04AFEE0E69AAAB85C6AACA8ACAF51B4604024C205E87A33975E2C4CA8C11C3D3 - 2BB77C09CECA4AB9CF5E53842E1281D8453B5774ADB54723D2911A5C3BABFB59 - 94B03EF67CAC7EF8727826CC797D5E8A9C1B92D03EF1271A339C492376D9B9F8 - F4C34D4E97F094652224760D22B509BD39A06F63B55593A0550F9D76DDD1023D - 0FFC3DA42DC92876A3EAE64E2A576933ACE9F6D944781A5A3BF9B48B10FCBF28 - 621DB6BE3B8DCA6A5BE0D85FFF03DA3A45F7E79188DDE6B62EF0813E6995E572 - 0C6CB1DB367FF81D7E88CC762B92E86ADD6D6D50FDC13F61704637FED2EFBA96 - 549D2FFF7EE7BCE5ABED4EE7997AF23C54560A759B36D4399B5B73E4E6DD68A2 - C004ED0996C1B9FBBD9D6D2E4B53CB30DB8993F779F7EFF5FBC64E583873E51F - FFAEA71C350C0ACAA0109B619C852BC1EB9FB53F895DA5195AAD885E72425FEA - 3C2259736772B2885D4900A1D0BD92A71C9321B11B2152B00AEC0BA5E86C6803 - 3F87B5A75A3A56CF46BD68D1C7EC2849C5AEE1F64558475DCF2BC21CD85BD854 - 3177CB8B9FC1331F6FEFFD772462D7088200035BEC7AE6E77D03E2A614C3D87F - 78D13A706785F3D9DA075F571734EDDDE9B1946D71A53A7ADADD068277B46B49 - 4DAF5FD7FD0B9FBEC32F806E31EE696E064F636353D5964D5D6975F5BD56DED9 - A79FB1A17CEBE6B1EEE6D66C183CB8CCDFD4946DE9E8987556C5E69566F5A9C1 - 485C7D62C94BA2041FD8BCB36FF830BD224C4CFAFE2E7679035EF49EABC34FAF - E6A69C6410BB1A01006205895D13D008471C14B98CC42E895D20B11B17CC12BB - DD3E3F2CFFE06B98F7FAFAA00C49ECEA84895D9C01D0EBAD2008EB0FCE5D6BC9 - 1CA268B38B6EC6864D1CF337217754EF46B3B6C395DECE9A4375C2E13DA9A982 - 27643392FF3CD7D243EF05FE8551D4BABDFE0346EB86B6BBFE6D25E0907EA7C2 - 59B33D5B3FFEC89AD6D0D024389C1DD6DABAFBCF2EDFB8C2CC3ED5292851E8A2 - EFD86F54F2D2130D0AD1B4DFD51150221628BE8C38FD00F7BE3838FB9B272045 - 32885D5E176F0170961DCD69D01F6B6D207CB15436B60BBD36A09D342E93F3D8 - FD92D83509D6C64210FB2ED4B67F3E6BCF32E918255B7614C468AE152B1BCE3F - B1FA04DDCF2476A3834A1DF1F7BF9D95EB8956B9445F2215BB1DDE6EF8B6BC1E - 16BDB519FEBDED20844A4312BB3AF1CC1FCE6E04FF758633B0D85BADD32EA9B1 - 389C85F28FBDAD2DD0DDD1D164B1D9D2EC1999BDD604351B3E6D7337ED4BB55B - 95DBC93E7DC4BDB4EA5EF9673BE73DB9DFEE748C523ABEB6AA0A8EECDC593168 - E4C8D6ECA38E1A8533BAE9D9D9F6B21DDFB6A7BADC4D75E5E516FBEEDD83B102 - 1687A369E4955765E0795B56BED99477C2092F9EF6B7C77842E6EA4287680A1B - AE539A894307F4B844CF1B6E1443F6FE9EE5EB55C8AF3FD8F005507B51A0EB30 - 749513CE676BCF8C11A7605A01E1430D2783D8E50DDE8175B803DBCA192A1AC3 - 34A3FFE270C11248EC9A84462007F9AA87523FC4DDF76A928A5D8436A8250977 - BFB63E48EE86D3BE81A7ACB7DB07E5F5ADB0B5EC0854D4B582D7A77C9ECD6A81 - 7B7E7A3CE4B8BF8F9DF565692DBCB67E6FD071B7CE380E0A077305A2D564EE4F - 4F18D862B7737EDEB15E515818DA9D6F3F6146310C2A280AFDBCED5015F83ADA - EADD238FEE712FD6D9500F0DEB3EE8CC4E697384C9F22BD7D2AA20B1B1F3DEE5 - 7FB63B9C37281D8C33B7F66DDB823E4B2938EA2BA1706476EDDAB5052E59E8E0 - F471E3360E9E76EAC93D79AEFFE2837357BD744E34FAD4ECE0043AC3B1223873 - 7375E86C8A94570A889BD96E099387A27FCC5820BDC4EF94EAA9053EC417487D - 134ED4840D359BE86297A52DC0F7DBAB5E3F26D485C4AE89A8AC8204F9C866C7 - A0EFEA07438EE9F5711DA77A27ABD855749B68162A0183C8F5581C88C4F5587F - 24215C8F219EFB865DCF8A9F0542CF4B910BEBD0C22D96E3A6AB8AE483EFFCB3 - E5A8F32FE9195234EEDA01B07F03B89CE1DB27586CF9A9BFFDAE22F06FADD0C1 - DB3E2DEECED8BBBFCFD6C594DCDC5DED0505C3DD3983EC7EAFB7E5F0E68DB6CC - CE2E61E879E7D7B7B4B6B4FDF0B9878E8D565F46231297B47B1E1FDE17705643 - D10E58CA8B67335790ED5FACE10C3F8BFD87A1377F03DACBF2D81768CBAC690E - 9304627727673D74871825B11B1F786C3455FA1805F012DE603451A8772CC4AE - A9CBF72AFDA857ECE267BF66E718F15F1EAE7EB8F2F74710030DC989AAC02694 - 21B1AB4D5C23A8216DF70DFF85DFE29FC5E38A2C9C1786FA6FBE82ECF1133683 - CD3EE5D0DA8F21ABB30CAC9670B962270817A72EAB7C43FED9DEF9FFE711ACD6 - D4D063BFFCCFBF5BB20F1FEE33478F260BCE3163F7B70B9069B73B5B3CF547D2 - 2D757529BEC6E64FCEA9D8F4BFD1ECC328869DD5EB8E0C67EC7FC5F26F0AC947 - 29ACA7128A617563818E4D67FF61E9A7618E41FBC5FBC3C5A24F02B17B84B37D - 8B41F445CCFD30D278D1864262D74454445EE8CCAE526441DD031A93EB1D0BB1 - 6BDAEA94DE800D1AD71FD7C0DB601D953CD9C4DD64255921B1AB4DDCC56E8096 - F97967B096A11761D59797FDE40B3E83B41CD599E0E6FD7BC19D7FD4115F6767 - 4ED5076F5A86656B6A8D5E5817BCE45E56354BFED9F6BB1E7BCF62B79FDBD6D2 - 0C1939DF87D02B595BEC4BDDB7CFE250B09D7616147CDDED74DAB2274ECC49CB - CE195A535EBEEF9417961C13EDBE8BA2D8C5466274A2677554A78FFDAE8EE00D - BA97B343EA8BA21C9DE0BFAF643FCC713E3AD27F43EF790A6DE09AA126B1DB8B - EE654F1D7D97F06257721BE86679369A95A746594A660CA17DACB6492D9E2B37 - 668B5DA5EBCFB4E0191ACF4CBD6217415BF8A7CC9C55D730FD8AEBA0269921B1 - AB4DBF11BB019AE60D1963B5D96E13C08FE23353FE9D75D2F4624B6E61916AE7 - 78BD20D86C3D33BC50B615D2527DBCC5D6BB965605EDEC5E957FF26C4766DA43 - 2D6D1D59479D7AEA662664A759AD36475767276CFBCF7B2D83EA1B342DB0D1C7 - EEE8395715153E7087E130C0BC444BEC4A79ABB91C520343B9626CF435B23CF4 - 8866ACE3CDECFC2F74D411F3FF1188EEADC6B3F45B969ED43B436CC0738012EF - 8328D88F709447625744D752AB4E7766092D76651B4AE7B2F42B96D645CB5440 - 63835A9F9979958D892B20CCA6CD681105B18B1E85D0CB4CE8A6565396F03542 - 991B11BBA67B65D0F0CCA1E96E91881E2476B5E9776237C0E17B86B9D36CC26D - ECF17A3348CBE8E1C46E7BF561E8A82E6F80EFBECE425B5DCBD153FED301CE51 - 9DDBBF98E04ED116BE16104E4F595AF969E0DFC5A3266779BB6CBDA3538BC3D1 - 9872FCA483D963C68DAAADACB4A7656494EF7967E5E86CA1AF9D040A5DABBDBB - A8E8C0D6A80BDD9EF2A22876A5FCF5BA23C3A533147C7B64791482F2C35109AC - 1F6E70798323421B2E95DE0EA21897BF183038C6523D517C585E688B8DCB86F7 - EBE99F107053DF139C1E05125DECF2DAEC46D3BC26D1C5AEBC2F70A08936E5FF - 30B2B2C151D608103DAB4C0FF9AACFE633958123DE8B37B0635F37B14EB899D6 - 1ACE4E360A6257EDF75634E5D299B75BCA7B8EC2D746C42ED227B47304F5C395 - 041CE0CC0FF92AEAAECE087548EC6AD36FC5AE9CD6F9C3AF6602F23626763D6A - 62B7B5E23B68DFFA3164B87A82D235D94FBFB256B03946D56EDD0CA935DF80DD - 16C60D07C063EEA555F7C83F5B9D3FA50423A0F5E934267C3B9DF64E4B736B6E - E84EB5580BDD9E32A32C76A5326682181E97D71D59D0C3559A1542A1F1B28E62 - 31DC250A135CFA2C957D8EEEDCD0761B97F8B4FCAD625977EBB1A1D3615FAC84 - 2E7BB52410BB5FEA38FE2F20CEEED669948FC206039EE0CC3DAF1D79C28A5D8D - 41E8632C3D62A667138D088BEB40B4532D0B395E6DF65DD3CFB7CE3AE1F57017 - 8803E85B43EB1072AC9A0BC433D8796B0D96AFB4F1362241CFE1FAD1A8D84550 - 9C3E1EC99E8830F5C3FC1F09B75781880E2476B51910623740FBAB973D68C939 - EA56C1660F12371DB547A069DBBA962CA8ED312DB08E995262299874A2B7B515 - 6A56BD2186060ED71102EC4A5D52355EFED9470553173115FC40D893BFA7D1DB - 6D3BF19CAA2F4A63D92F3112BB7AC30923E8BEEDB9C02CA701930833C0CD339A - 2FC29076F2DA172BB102742CD326BAD895FCECF278E208B009447184EDADC3EB - 46BAEEF259C2194C5C1A3F8D33AF0009297659792EA9AFE6A81C827D89E2E3E3 - 40608E08CAC2E52BDC648B03586E81232DF563FF87DE4B110B5E8510C6D89FD7 - A95DCB1ABF0FF74A8C429E6AA1C60DB54F1292386BFE57501FCCE915BB380097 - 6F14C419D9478DCCF04AD7C1E52086FE0E9D6040EF45B89A57A1375FC21C48EC - 6A33A0C46E0FC58B535A8E1CBA57B05A6F76A4670C6DDCBD03FC7B37803B456A - 476AD676FB8F2E3A16430357AFF9B0335BA8765838BBACBBDB323EFDE18ADE87 - E5AAFC934F14C0BF95B3668D7E108ACE2EDF5812EB2E8985D895CAD1EB8E4CC9 - 7E57AF4984193C09E2AC613B673B79032184822FB8E7790F4E12B1AB26086245 - 428A5DA94C1493B84148CB23055E3B8FB2B4C6E0A64D1CA0E2EC29AE7A850A5D - 6C03DAA8966A9C3F05C415A150F186D7DC4296DED423C625C185AB06E8DB35D4 - 246A9D549F032AE72AF9840D1BF25CA32E6ACBF9817C6F054E1B6A69961A0714 - 382B1F109238500F9D89D62B762F656956483E1831142771B6F28A7CE95AC381 - 41A8B918D2E7394FC41E12BBDA0C3CB12BE3D06F4FBDB7BDA2ECD221D9DD7823 - 16F47C6873001CF3A30DAD7BB78F4DEDAAC9E615BA3D9DC11EE8A94BAB1E937F - F651FED45208BF641A37A1DB53EF18895DA9ACE3D91F5CA2E3152F7DC209B33C - 2682282663217835C319ABB4D18840D3EDE22749C4AE1976D09190B06257562E - 0A10EC632DD31BB949D0D75A9B01A519461CD8A237055C9D99A4701897C091F2 - C299DDC741F939FA96D46F5F688971960D06073A596AAB52D43CAC0FEEEF785F - 4DC0699828E12C380AB9CF4385B7AC2FD08CEBDD502F03ECEB31209A47283DCB - D0A401AF3F749157A6542FA95DA783B8C1507EBFE22C3ADA41BF1F728AEE086A - 2C619B9E86E0E70CD6ED5FD2393B54A25F62DBF3A5FEBE45E5F7C37C6E62E9B5 - 78F950264406AAD8C5ABECD21F8E86821C37941D69817F6DDCDF13FB8DC4AE12 - 8B8F73B4B6D5DE2C58045CD23BCE6836AC273E752FAD3A5DFED9AAFC9357489E - 2134CE1326C74BE8223116BB46C209A3FFDA79F2C84292A0C41701CF8635A3E8 - B6D995EA86B32C686EA127B4B36E1FC1C92076A5F3F4AE08F08075CF82F003D1 - 8416BB52D9784FA26F5B346BE0BD9F70C50A7F7714C10113041C98A0B09DCCD2 - 388D7375091C0EC18BA0582D6669A3542FAC13CE2AE373026787D13B819A6DFE - 2EA93E6BB4EA239928E1F57089469FA0179883D2BF8F02D1CB0BF687E23DA8E3 - 79B84BCA7BA7F4EF74A95D5315DAD533406709DD569A122E987D8FEF35F486A3 - F4BBA29DFC3AA9EF7110A4E73AC059ED67A2B12192D0C74015BBF94CE4CE9C98 - 0F3327E5C3EA6F2BE0D52FF6426B8797C46E38DA17E45DE3F3FB6F604D3324A2 - BAFC8E9CCC6565BDA3F7D523A65CE81784B7D48E6742F75A267457C4B3CDB114 - BB5279F8604531781BE7298A2F4769696C11986FC38BE5E1D2EDE346771FB3BA - 9DCDFE7CA8E39499ACAC553ACB480AB12B9D6BA6F90A0E6270397A39471B135E - ECCAEA80E2106762D13B09EF063EBDE0B5827E5B3FD0190444EE1E70AA89F5C1 - 25F97B585576F21C2CCDEEBE62A07F54EF41C99C012744F0F737B2B1550E3E73 - D0EE7F8F49E182E583CE93401C949B31C160C80C85881E0355ECA6A5D8E1CE1F - 4F82D223CD307A48062C7D772B747A7D247679F12CC83B177C7033087D6C9E34 - B108302B6549D54B817F87BA2093D31F842E126BB12B95A9E686480DB489BB3C - D4E1BA6483772688E2C58C97E07B525EEB235956935C9AE1753083E3708C9C74 - 0D2BAE4A6719492376A5F3474AE71BD9FC87E020066D247189D7C5D9C6A411BB - B2BAE0EF3F1B44BFBB668A5EBC1F1ED0B2D1E5A89B96EDA71E703612BD723CAB - 67402B89EECB40342DC8E13D0FC2DC8352BE782DA2981FA7235F39785D2F0AAC - 44992D76A5E3CC986088F83A20CC67A08A5DC4EDB4C3B1C3B3607B6503B47674 - F57C466257279E85C3A7F87DBE3B04E879C9F1F4C8DBAE25872E947FF451C1D4 - 954CD9062DC1F617A1DB53E538885DA95CBDE18455ED6725D30114CEF8B0C625 - 4B3D2F42144138FB8ECB741BCC5852D38810A404DAA32ED12BAE934DEC4A79E0 - EFFC3396EE05FEC14DC0BE10C5CD01C94B036F1B934EEC86D40967D2AF01D184 - 448FB80B80A21207B5D88F3BCDB2CB64754313019C0DC5CDA07A04399A5DE07D - FE1AEFFDA05036DEDBB8448F03A79F719C82D71F3EE7EE6365B684C91BC5245E - 03F8ECE0ED6FBCBF1F06D10CC327CBCB74B11BD27E9C99E51D78069EB168AEB1 - 816673FB1FC2357F492831E77F690E895D23B42D1C51E0F3F9E7B206DF080238 - 350EDDEB5A5A3546FE818229C39D33CA37F1446F220C20D9D6A1109A06A25D1B - DAAEC9674BD0B60E6DFC706309DA9A6D8D46A854CEF0C1389B7B212B7F63BCFB - 6D20212DFD8E05716083BF33FEBE93A5AF5160A1105F0FA21DE7FA489CF413BD - FD8D9B8DB08F7183282E6963844A1C68054459A0DF03F7166EBAD4DCCC6642BD - 708316DA874E93D250599D764975D926FD3FAEA07C6796D09256958E0551F09D - 02A27D704078E3B587A6112844D17D5BB5CEBC03038D22109F65F2BC03CF2FDC - DC879BD04A4D700FC72D7665E70436DF611D4F937E07F45F8E130D286E711FCA - D7521FACD7DB0704D19F481AB1DBCBE2E31C9E8EDAB97EBF703BBBD587847E8D - 0129DC4BAB32433FEF11BC16011F04C533BEDB541CEF66109123B945C2DFBA8F - 5B261D36A638EB75AB9E486D044110666244EC124432917C62578667FEB0EBD8 - 53E26EA6707B5D4CB1A16E71EAD22A5E3B54628012E27101678DD0D518465242 - 83219CFDC297068FED9D2EDFBA0441106643629720B4496AB11B0037B3097E98 - EB17A0C6B5A4EAD278D787883E1ACEEEF56068631A4110849990D825086D48EC - 12498789810ED005D353E44C9D20887842629720B421B14B241D2685B0A558F0 - 0441F40B48EC128436247689A442A74B313570331A0ADDB7E3DD1E82200812BB - 04A10D895D22A9602F8551ECCF6B105924AF5FB3F47B0A914910447F80C42E41 - 68436297482AD84B019DEBFF158C39D84716B1F448204801411044BC21B14B10 - DA90D825920E83E154D1B9FA22965652F4208220FA13247609421B12BB44D222 - 45953A9E250CCB89D19B0A418CA48404A249A1C8C5E8791B6836972088FE0889 - 5D82D086C42E41100441100491B090D825088220088220121612BB0441100441 - 1044C2426297200882200882485848EC12044110044110090B895D8220088220 - 08226121B14B100441100441242C24760982200882208884E5FF037933C780A7 - F045C20000000049454E44AE426082} + 0067080600000000008AFF000000C67A5458745261772070726F66696C652074 + 7970652065786966000078DA6D505B0E032108FCE7143D02022A1EC77D25BD41 + 8F5F5036A99B4EE2803C4604CECFFB8297839280E4AAA595820669D2A89BA338 + D1072794C103DB8614D1250EA546135988CDF2BC6A9936DDF168B86DEAE6E51F + 21DD23B1AD8926A1AF0FA178887D229FED08A116424C339142A0CF6F61695A97 + AF9DB842E701A73D6309B181E75DAA6DEFC8F60E139D9C188D9975D6B01F01EE + E6A831B17AA195B95F8D33D798C416F26F4F37E00BBA4A59FA71D652FC000001 + 84694343504943432070726F66696C650000789C7D913D48C3401CC55F53B545 + 2A0E7610E990A13AD9C58A38962A16C142692BB4EA6072E917343124292E8E82 + 6BC1C18FC5AA838BB3AE0EAE8220F801E2ECE0A4E82225FE2F29B488F1E0B81F + EFEE3DEEDE0142ABCE54B32F01A89A65645349B1505C1103AF08228201C41097 + 98A9A7730B79788EAF7BF8F87A17E359DEE7FE1C434AC964804F244E30DDB088 + D78967362D9DF33E7198552585F89C78D2A00B123F725D76F98D73C561816786 + 8D7C768E384C2C567A58EE61563554E269E2A8A26A942F145C56386F7156EB0D + D6B9277F61A8A42DE7B84E3382141691460622643450431D16F55583468A892C + ED273DFC638E3F432E995C353072CC63032A24C70FFE07BFBB35CBF129372994 + 04FA5F6CFB631C08EC02EDA66D7F1FDB76FB04F03F03575AD7BFD102663F496F + 76B5E81130BC0D5C5C7735790FB8DC01469F74C9901CC94F53289781F733FAA6 + 2230720B0CAEBABD75F671FA00E4A9ABA51BE0E01098A850F69AC7BB83BDBDFD + 7BA6D3DF0F168772E851144DAD00000D7669545874584D4C3A636F6D2E61646F + 62652E786D7000000000003C3F787061636B657420626567696E3D22EFBBBF22 + 2069643D2257354D304D7043656869487A7265537A4E54637A6B633964223F3E + 0A3C783A786D706D65746120786D6C6E733A783D2261646F62653A6E733A6D65 + 74612F2220783A786D70746B3D22584D5020436F726520342E342E302D457869 + 7632223E0A203C7264663A52444620786D6C6E733A7264663D22687474703A2F + 2F7777772E77332E6F72672F313939392F30322F32322D7264662D73796E7461 + 782D6E7323223E0A20203C7264663A4465736372697074696F6E207264663A61 + 626F75743D22220A20202020786D6C6E733A786D704D4D3D22687474703A2F2F + 6E732E61646F62652E636F6D2F7861702F312E302F6D6D2F220A20202020786D + 6C6E733A73744576743D22687474703A2F2F6E732E61646F62652E636F6D2F78 + 61702F312E302F73547970652F5265736F757263654576656E7423220A202020 + 20786D6C6E733A64633D22687474703A2F2F7075726C2E6F72672F64632F656C + 656D656E74732F312E312F220A20202020786D6C6E733A47494D503D22687474 + 703A2F2F7777772E67696D702E6F72672F786D702F220A20202020786D6C6E73 + 3A746966663D22687474703A2F2F6E732E61646F62652E636F6D2F746966662F + 312E302F220A20202020786D6C6E733A786D703D22687474703A2F2F6E732E61 + 646F62652E636F6D2F7861702F312E302F220A202020786D704D4D3A446F6375 + 6D656E7449443D2267696D703A646F6369643A67696D703A6161396535366135 + 2D333431342D343666352D613332652D636634366131363238666130220A2020 + 20786D704D4D3A496E7374616E636549443D22786D702E6969643A3338336433 + 3663392D303533352D343866372D613937392D39376362663636653861386522 + 0A202020786D704D4D3A4F726967696E616C446F63756D656E7449443D22786D + 702E6469643A33393739313537392D393962652D346132322D393138362D3261 + 66363332643536316464220A20202064633A466F726D61743D22696D6167652F + 706E67220A20202047494D503A4150493D22322E30220A20202047494D503A50 + 6C6174666F726D3D2257696E646F7773220A20202047494D503A54696D655374 + 616D703D2231373234383434343935383930383934220A20202047494D503A56 + 657273696F6E3D22322E31302E3338220A202020746966663A4F7269656E7461 + 74696F6E3D2231220A202020786D703A43726561746F72546F6F6C3D2247494D + 5020322E3130220A202020786D703A4D65746164617461446174653D22323032 + 343A30383A32385432313A32373A35372B31303A3030220A202020786D703A4D + 6F64696679446174653D22323032343A30383A32385432313A32373A35372B31 + 303A3030223E0A2020203C786D704D4D3A486973746F72793E0A202020203C72 + 64663A5365713E0A20202020203C7264663A6C690A2020202020207374457674 + 3A616374696F6E3D227361766564220A20202020202073744576743A6368616E + 6765643D222F220A20202020202073744576743A696E7374616E636549443D22 + 786D702E6969643A65363665353134362D313361392D346661642D393062322D + 393933653737386139353165220A20202020202073744576743A736F66747761 + 72654167656E743D2247696D7020322E3130202857696E646F777329220A2020 + 2020202073744576743A7768656E3D22323032342D30382D32385432313A3238 + 3A3135222F3E0A202020203C2F7264663A5365713E0A2020203C2F786D704D4D + 3A486973746F72793E0A20203C2F7264663A4465736372697074696F6E3E0A20 + 3C2F7264663A5244463E0A3C2F783A786D706D6574613E0A2020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020202020202020202020202020202020202020202020202020200A202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 200A202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020200A20202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 20202020202020202020200A2020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020202020202020202020202020200A202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020200A20202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 20202020202020202020202020202020202020202020202020200A2020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020202020202020202020202020202020202020202020202020202020200A + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020200A202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020200A20202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 20202020202020202020202020200A2020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020202020202020202020202020202020200A202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020200A20202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 20202020202020202020202020202020202020202020202020202020200A2020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 20200A2020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020202020200A202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020200A20202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 20202020202020202020202020202020200A2020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 202020202020202020202020202020202020202020200A202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020202020202020 + 2020202020202020202020202020202020202020202020202020200A20202020 + 20202020202020202020202020202020202020202020200A3C3F787061636B65 + 7420656E643D2277223F3EDC58905300000006624B474400FF00FF00FFA0BDA7 + 93000000097048597300000D8A00000D8A01FD2DA3480000000774494D4507E8 + 081C0B1C0F66E13F84000036CA4944415478DAED9D099C1C6599FF9FEA73667A + EEC9E44E980472410209240A412111C2211EE80A2E8B0AC8A1ACEC7F410E09A0 + 06C50485D5B8BBCAEAA2465C76659143D6658580098781404212AE1CE49884CC + 643293B9677AA6A78FFAD76FAA7BA6BA52D5FD56754DF74CF7F3FD7C5EC854D7 + F9D6F1FEDEE77DDEE79122DF24991886619882E4C52717E7FA149802E6E9877E + 9DEB53600A0089C52EC3304CE1C26297C9252C76996CC062976118A68061B1CB + E41216BB4C3660B1CB300C53C0B0D86572098B5D261BB0D8651886296058EC32 + B984C52E930D58EC320CC314302C76995CC26297C90652AE4F80611886611886 + 61460A16BB0CC3300CC3304CDEC26297611886611886C95B58EC320CC3300CC3 + 30790B8B5D866118866118266F61B1CB300CC3300CC3E42D2C76198661188661 + 98BC85C52EC3300CC3300C93B7B0D865188661188661F21616BB0CC3300CC330 + 4CDEC26297611886611886C95B58EC320CC3300CC330790B8B5D866118866118 + 266F61B1CB300CC3300CC3E42D2C7619866118866198BC85C52EC3300CC3300C + 93B7B0D865188661188661F21616BB0CC3300CC3304CDEC26297611886611886 + C95B58EC320CC3300CC330790B8B5D866118866118266F61B1CB300CC3300CC3 + E42D2C7619866118866198BC85C52EC3300CC3300C93B7B0D865188661188661 + F21616BB0CC3300CC3304CDEC26297611886611886C95B58EC320CC3300CC318 + 50FEE8812AE57F1FC9F5791851ED77D1BC4ADF713AAE2314AB7FED9229BB127F + CFBAE6897931994EC8F5F95AC12D496FEEF9CDE75B9DDA1F8B5D866118866118 + 0314B17BB6F2BF57737D1E462CA8F25185CF95B4AC2F2AD33B6D036B8F5D5177 + 4B62D9CCABFEB05696E91F737DBE5670B9A415FBD6FDCD0B4EED8FC52EC3300C + C3308C01A355EC4E29F1D08C324FD2B2984CF476DB00F544626BBBAE9CC16257 + 43C189DD5B5FD87915B9E4AB07FF90E57A49A2A71FFCC4297FCCF579310CC38C + 20188A5DA294A54A59AC9493943247F3FB6EA51C54CA5EA5BCA594ADF1657DB9 + 3E7186C925A351EC16B9253ABDC64F2E9D823BD01DA1866004FF64B1ABA3A0C4 + EEAD2FBEF71BE592AF36F8A943291B49969E8EBAFBFFB876F9A28E5C9FAB45D0 + 683DA694D3D2ACB743295F24B511CB947395F25BA588FA01A143F10DA534E4A8 + 8E18A6D0F02B6539A9EFDDA76C6CDFAD941795F24BC2F791852F53808C46B17B + 4AA58FAAFCC9EE0B5DE118DC174856FF64B1ABA360C46E0AA17B3C92F2618F91 + 227C2545F8CEABCFF5B90B906DB1CB429761462FF8AE2F52CABD644FE41A01AB + EFBF28659D521C9B34C230A39DD126763129ED6445EC6A81FBC2B6B610F545E4 + C42216BB3A0A42EC5A12BAC753AF3C3E4FBB5DF26F1F587ECAF65C5F8B09D914 + BBF395F2B0523E2AB8FEF34AB949291FE4B88E18A6108035F706A5FC40296523 + B07F7C3BBEA794279412CAF5C55AAC1774008A34CB30DE8B6F6277AE4F8E19BD + 8C26B13BD88BADF153892759BA7DD81BA1833D11ED2216BB3AF25AECDEBC615B + A55BF63F45322D7368971D44F2D3A3D0CF375B62779E52948E83B0D0DDAC946B + 94B233D715C4300540402977C58B191076E8B4BF4EC3165AB7521628A54E2967 + 0A1EEB29A5DCA694FDB9BE68416A94F2A8522ED42C73D2AD8BC9534693D89D50 + ECA659E5DEA465A1A84C5B5B4383D65D0D2C7675E4ADD8BD7DC37B0B6351E937 + CA152E345BC7E396BA6A8A5C7B22D1A8BF25185BE072B9AC1CA2432669ED8FCF + 9B776FAEAF95B22376A728E5674AF9ACE0FA2C7419267B605AF6AD4AB9DFE4F7 + FF52CACF95F286520652ECA79854E1FBB74AF93CA57655C277E466A5FC39D717 + 2F008B5DC616A345EC42AC9D3ECE4FC5EE64D9B6A7334CCDFD51FDEA2C7675E4 + A5D8BDF585F7FF51B9B2559303AEFD5D03B1A2803B36E168BF0B1F3B1A57E2DD + 5AEE95AB4A7CDE9AD2627F8576BBDD0DCD1DED515FA59563B95CF2A251E0DE30 + D2627782527E41D6842E7C74B7E6B85E18A65040830C31A717A778E76F2775A2 + 59CCE23E6129FE02A9227A81C1EFB0EE7E4D292DB9BE780158EC32B6182D6277 + 5C919BE656245B75831199B6B586483E7E7516BB3AF24AECDEF697F73E2BC7A4 + 55B0E656157BB7BB237D13A392E798140D4F6A8B7A07C5EE820925F501BFAF2E + B1CD91B6CE90DFE709971717053C6EB7B4FD40436B502AAE11B5F2465DD28C51 + 30896D24C56E35A9D6A2EB05D7C7BEE133F8728EEB84610A857252278F7D45B7 + DCA9D115885EBCD3F790FA3DA0F83E61FD7D3BD7172F088B5DC616E9C42E0CAD + 357E3795FB5C835657FC1D56D467E7408C8E042314952D1C2C05A756FBA8DC9B + AC4B767786A9E578AB2E60B1AB63CC8B5DB82B4463D255CA855C4AAACFD91065 + 3ECFEE532696CE69EEE8A65DC782E4F3F91396DDEAF115A5330E1F6BA78EB0B4 + 735AB97F5C45A0B816DB8423517AEFC3A6D67E77A026B11FB83B94795DFB3B82 + A1C9B2CB335E537BBFFDA74F9C7C75AEEB80464EECA291FB9152FE5E707DCCD8 + BE4A292FE5BA4218A680388F542BAB76429AD3EF6222C2C31AA59CA594AF93EA + 1AE150533EE2B0D8656C61267611E3765AC043934B3CE4365152F0A77DAF6360 + D0029B09989086B8BAFA7D6F3916327B0159ECEA1873621793CE5C31FF77254C + 3A4BE18F0B2694BAB70483A14ABFD7DB5B51E22D8E4663459DBDC1F29AB2D296 + B6AEAE998DBD31777559E907B3C6154F2FF679079FA4BD8D2DFD95A5818EFAD6 + 9E48382617054303F2F2B953CAB7EC3B1C0C9494B41CEB094DF517074A94553B + A32E69A1D6AABBA16E616538E24503435E4FF8B7CBEBB7672B5EEF48885D91C9 + 2E5AD0B87E15D54063A7016498B10EBEE1772BE5FBBAE5FFAC943BC8F98809B0 + EC7E4E29FF49632BEE2E8B5DC6164662B7D4EBA23915DEE3FC678D802885AB41 + 267A1799D290314D4B7D4F840EF746CC3661B1AB63CC89DDDBD6EF5C26BBE40D + 66BF97FADC7BC2A13E5F48F2D5CD1E57F2527589EF5CFD3A9DC1BED0E1F69E86 + 8A40C9B15824523EAEACF844AFC7EDF6BADD4963049DBD7DE1AEBE50F068CFC0 + D130B9264B2E4FA9A6E26E79F0BC93D76AD75F3F6531CE6B59FCCFED1E6F6479 + 9604AFD36237DD64173D10BADF24D5BAC4429761B207ACB99838FA65DDF20B94 + B23ED727378A60B1CBD8422F76E13B3BBBDC7B5CF6B2544094D6F744C437D0B1 + 649C9FFC1A618D46F6CD96100DC44C9B5B16BB3AC69CD805B7BEF83E44E6D54A + 499A6006A13B6F7CA0CEED72F9BA82FDC112BF7750C33A71CC70341A7BF750F3 + 91C93595873B7B83A1BBCF599024A25F9872C65A9924FDC3B47D45C3964559A8 + 1227C52E842E86285793589CCE6E72764813C73F51291F8B175C93B60EB7C5CF + 7F53BCBC43A96797E733A82B8483830B0F3EC848070BCB5B1BA9F71AE1A59EA5 + F433F0B5E09B309554B184FA9F4BC3E1A8D0A941BCE437498D9F0C9FD0B164DD + 73028C78A0AE3159F3745DDDBC476A9D233A019E53FBAD9B3870BF8295F57CCD + 32BC13972B6557AE2B6B14C16297B18556EC8E5784EEAC0AAFA970420368F45B + 5811A56FB4846C3590655E179D569D9C44A23D141B748F48018B5D1D6352EC02 + B8337862BE9B158189D03783A277EEB8E2ED9525FE21D7868E9E203577F51E9B + 5A5D515552E4733B71DCB69E60FDA1B6AE653F38EFF4838965EBA72E5EA63CC5 + 86D66689E475E7376CBD6684ABC329B18BE7E10AA5FC1B655FE86292CD954AB9 + 918C677E9B81FBF02B5233BA1D32596746FC1C45E203E37A7E61F31A10B6E927 + A4CE504F072CE710A2BFA7F4F7EDB978DD24E2A26204E213A4764896081C0BE2 + 07BE964F92F9B036F6890E1C2CF4A259B750F73F25359D6CAFCD3AC3571C61ED + F0DE42B8E3DE43B0E379D0C67C8578C4ACFF46521B1E44FA789FAC7774508FFF + 21B0DE7728D935C06892562A44EADC098CDE7D1671C7C36297B14542EC56FA5C + 744A95CF5034750CC406933A748763831658A4F3D5277ED8D13630F8BB554E28 + F50CFA066BF9A02B4C47FBA2A93663B1AB63CC8ADD0409D15BEEF75C54EE93FC + FDE188578E466A0EB7F794F84ACACB135115665717EDECEDEF9F3C6D5C6585DD + 63C56439A888D7B32F9F3D7528D4D881553FA9DCB1FEAF1F04EAEBC7996D9705 + C1EB84D895E2BF614854A43187D0C5CB04919989050BBED27F43AAB89893C17E + 60CD7C207EFEFA8C48E8E840B8AC14D8CFEF480D9B6627AB92E87D3842AA35B6 + 53707DADD8AD8E5F8BE8A4412D8F937ACFF4699B27929A71EBAB36F60960E5C5 + 8754D4925845AAE8401CD7F348EC793302F71C9D985F939A2841A425B1237661 + C1850FECD936CE11CF137C679B6C5E633AC6B2D8C53707610D9729E5E3946C29 + 07B092E39942E7068DDE87643D7C5A0216BBC6E05D5CAA944F92DAD144A71386 + 8E44F21174DAF0FDC1BD68CEF5C9E60288DD22B7F4EAC26A1F790C7C170EF546 + E890CE45A1B6C83DE8D3AB65AF22509B520B544370DC525D1486CD2DA1416B71 + 0A58ECEA18D562B7F7AE89CF904C9F2692FF2ACBD253FED967ECF07EF57F4D2F + FE87AFEC78B8A2A4E48A8E607F5B4496BADB42B169093F5B842273858393664C + A8A9F1B85D1EF1B350511EAB2E9263E76A852E78F194152FCA35D55323FBF6CD + 4E653A1E61C1EB84D885550FC2F5044A0F3E841002B06266227421B210EDE1CB + 19EC43CF9F48B54EEAD3131BCD583702DB21B6A89D904AA2422A21A8279335B1 + 8B54A7A8F3CB32A81F64FE83E5F968FC6FCCAC7F48E01CD2912ECC15BED6B0AC + C3728E094E4EA7B27D4429DF554A7D9AF5AC88DDFB487D2F30D29149474C5FE7 + 4E826708226E996E39CE7BB486FFB3338A005E21752401EF782A6BB995111651 + F4A32B4633F431220481E1946B8FD131F4230EE9AE19AE487F3558BF2EBE2F7C + EB4447F1E09E834E1FDCC7EC763AC61C55FF79E0EC53AA7CAFEAC37E0158733F + 34982466E47A60248AD301E3F047C7172509B5DE488CB6B5A61DD062B1AB63D4 + 8ADDDE951331243D6DF0A22BC6EF92677E843C55B573FB5B5A9AC33D9DAB6B6E + 78FCA766DBAEDDFCFEFD253EDFD7AA4A4B2A771E6E6EEA923D01AF24F5CCAD0D + 1475F7857A2654964DB3722E8AD0DD118D48975E316F52BD76F90B53CEF88D4C + D2D5A2FB5104EF4F15C17BF3085457A662F71C5287A2451BF43B95F24F9499D0 + C5B1E07E60C75A960EA3A41655F1E37D4E60FBEBE2EB5A01166A08F7FF6761FF + A2F70D0D2DDC1E607D154DEC918A7F25D5DAF81112EFE088904AD405E2C7BDDA + A1631991C8E885FA32337B5811BB2F3B583FC85E863AB7EBEE610646AA1E2655 + B468C1F3F2131A7D1346E1038E11182B22570FC4EEB749B53C1AC1627718BDD8 + C57A784790EAD9EE888A68C7322FB870FD918FB9A5C18E5612C85A86EC654640 + 189FAA13BB7626A955C55D27B43406A3B4BF3B9C6E53DB62D7254974DACC2A3A + F9844A0A1479A8B52B44BB3EEC1C2CD158F63E2705217683774EBC49960683A4 + 93FBD4F337BAC64D5BA65F27DCDDD5176A3DF6BBEAEB1F33FDA0FDE8D51DD7C5 + 62F4CD86DED8BC39B581433B1BDB6B97CC9C7034D83F30ADB6A2D4BDE340436F + 67D41338F384713D1DC1BEF0F88AD22ADD2E3A95B662ED65B3A6ACD2EFFBB52B + 6FFF7DB0BDEDCCE0F6ED271449E2D5A888E36B2E6878739DC3559689D8859FE4 + 6F48CC9F152014D98F29333F44ABC7B48391A5F196F8B9A7639D526E226BC204 + 8208138596A6594F7B0FACDC370CE1662210B4C04A03C1F749724EE82640430A + EB8FD157F15A5285D948922EBEACA8D885150BDF03A79E51D4397C7E7FEFF0F5 + 9AB9E8E09981FFFD6849D70DB318DC9520749D78E6E0C2F20FA4D6A7DECAC862 + 7718ADD89D42998F0C25C07B86CC7C4F509E5B792F7EE1C8C7244A16BB7D5199 + B6B7864C1346C0BF77BE4EA4C2027CD0A2D885AF2E7C76B5A44824A1C596D83D + F7D409F49D2B17D28C89A5C7FDD6D93B405FB86F23ED6B1CF6F07BECAE73E9C4 + C9C303034B6F7E960622C38FC3EB3FBD84B4D3A53EFD9D17E9C316B16635EFC5 + AEBC6A724930141BAC0DEF999FDF44251529C543B4BF3F126A6D7EA9E284595F + 9096AF320CF375F3869D759E58EC6A58614F1E573470B0B96D7CDD849A9EC6B6 + 4EFF0079DA944ED2ECB9B5C5EF56078AE763FD982C37B8247AD8E31958FBB919 + 338EDBE7E6EBBFB3BDBFA3635A68F79E2E5765C540F5AC3991FEC8C089ADEBD7 + FB4584EF08085EBB62D7AAE85C1D2F9958A7ACA61ECE045820D02076C5FF3E85 + 54BFD57969B683404083F09E85637D8654CB663AB4F14F45EFDB58029D0C88AC + 0306BF89D6BF13E760E652212A7647826748CD44E8B4EF237C9F9F30588EE711 + 231C0DD676E738E922BC40B8FE2FA91D8CC6F832B49027933A11F33C93EDD0EA + C2771D9D4CADE062B13B4C42ECCE227564E502836D51FF704D48446D41DDE39B + 84762155C704F50FE307DC7CB2117924271889DD77DA070633A4996194DAD7CC + E52115D807F6A5E52D45640B24A9B02C763F73D634FAF1D7960C5A768D50B6A7 + 85373E43DD7DC356E5FFFBC10A9A33B57CF87CAF7D2A49ECEE7CF852F27B87CF + 7FF91DCFD1C1A33D42D79EF762B777E5A4E7956A5D414581A3DEA59763E8B354 + 643B391695FB5B5A7644FAFB57D65CF75F7F365BEFC667DEB8A12C507CE6FC09 + 659F2CF6FB20BC68F39E431D9E928AF7E6D4141D2C2BF13FFE8593263F6DB42D + 26A3B53577BF56513D0E4371D4B877EF40F095977D81DADADDEE59B3236189C6 + 1D7BF59509E552FA54C30E0B5E3B6217BD7C4CE612159D4E0CC3A2D1830BC4F7 + 33D88755B496463BAE0622A00B8F98C4B708AC8BFA7E26FEEF7C14BBE04BA4FA + 91EAC9862B430233E1914BB10B7100379A171DDEEF24523B76E71BFC86307178 + 075EA3DCB834A48AF082EF1046F0E0C3DE95621F985C86C993F876E887DE8D2C + E6781F61459EAE59063188706CDA8ED691F8B1DB04AE039349F1DC245AEAB124 + 76F7D2F10606D41BE630C07D0D9D4323158686AC2E5EBFE8A419B93D38355179 + D4A217BBA9DC171220ABDACCB2648BECBEEE301D095A9BA0B6A8C64F014F727C + DD4D47FB455E644B62B7BACC4F2F3D78D1A0DB8219879A7B69D9EDC9D28AC5AE + 4DFAEF9C7C41548AA1074DEE05CB37BA6AEB96D9DACFB196FD91FEDE7FABBEE6 + BF1E90EF9C5E25DD7FA83DD3737BE3865537945754FCD0E3F7576A976FFED5C3 + 343E1EF161C285176E3A70E8E052CFCE9DE413A85A0705AF55B10B6BB515EBAA + 53FE86F00D46A33449707DCCB45F173F6F7C48F126C2DA800FAFE890BEDE9A6D + 6605D383E38ABA32984D12D28317F72BA436B2205FC52E1A5D0813A35914D970 + 650066C232976217201C19FC4DAD4FCB4ECDDF922A5CCC261B410CC38500E1DA + B239EC6C36F115420B7EA3FB2DECCB2C2A46BAC991C0E9680C6345EC5E44EAA8 + 93367A0BA2A7C0ED05E1FC443B4075A45AE6AF30F80DDF337470FFE2D0758F2A + B462172EAB5B5B438359D15231B3CCAB08DE648BEC2E45201F4BEF7E90C459E3 + 8B925211C37D62EB31210F424B62F7EA1527D177BE94DC0CBD73A09DFEF0CA41 + EAEA0BD38993CAA8C4EFA6FBFE3379DE368B5D1BC8ABC8130C4D1CEA2E793F7E + C576F2162DCC649F039DEDCD91AEF6A72ABFFAD8D7EDEEE3A52BEFB8AAA2AAF2 + FBA59595C74D6A0B0F0CD05BBF7B6448EC4EF9E217776EDFB8E1C4DAA3CD3ED1 + FD2B8277912278B78BAE6F8215B10BA18886E146C17DFF3BA9C245C4FA910ABC + 11B0E27C45605D343E9860F37F64DC30A3C22F26D5322DE2FFA79DAC93CA0AA6 + C58A2BC30A521B907440E4FC80861B183B6217220EE2019D863DF1FAA953CA25 + A486FFB23BE904E83B17E3496D2CBF46D6A21160481A11368C3A99A95C1910DE + A82B7EFCC47031C4DBA2F876567D3D8D44B71DB18B67FFA7F1EBAA8F2FAB237B + 75FE0752470D3A2D9E433A6035C7A845BA907468B4719F11E33993505E2220DA + CAC3F17AD2026B2ADEC9161BFB341B8E4F4CBA34139A852A76F1CC6AEB1FDF60 + A497B6135A11236388A0B1DAE037B84A40F0D63B74EDA306ADD8159C1C361867 + B7CA9F3CC26B35CE2EC29C9D59EB4F5A06D709B850086049EC3E78FD62FAFCC7 + 863FAFCD1DFDB4ECB63F537F38B53867B16B83DE9513F112DE94F83B13CB6E12 + CA1DEE3FD6DC5B5C3BF15E8F448F48E7AF160AFFB3F9BAEFACF2F9FDD7975555 + 4D4EB5DE811DDB3B8ACBCBE59EFA036D93169D51BCFBC93F4CAE127063D0D0A1 + 08DEE5190A5E2BF15D11EFD34A5637885DBC34995A7545437FA59B60A405FE7C + 102EE92CC55A8B2A9E797CEC455C29445C19F0266366F2B7D3AC9788ADFB8666 + 9955B10B0B167C90B790B14506EE3510651708EE2F01EA1CBE9D669D0BAB21E2 + 528988842B0386996175C5B81846730E53EA6150BC54F0E1C4A418910E13D848 + AAB86DD42CB32A76CD6213279849AAB5F112C1FD214208ACB07B2D9C832810DD + 70A7B95E707DDC9F27E3F584F382A877CAD501EF19DC85D6EA963B11860D2210 + C255DBF949E72252A862570BBE0DF8F665EA8A66964A1EFEBBF84E383D6A9153 + 1262172FC69663E9ADBA60F1383F15B993E5D51BA9D3FB1E0792529C5E932C76 + 31316D77677AB14D16C5EECF6E3A932E5E3265E8EFB7F7B7D3A5F7A637D4B3D8 + B548DF3D1397C5A2C919C85C1367BEEE3EF9DC33EDEE534BD30BFF139A78DEA7 + BC8AA86C9424E9C7BE15AB7F62B4DE81550FD575B6B5FFB2B82470AEDBE34D69 + 9D6D6D68E86ADABD2B5C128DB4974C98D8DBB27BD7B4AA49931BDAF7EC5910B0 + 109D214E87C71B99B1BC7E7B87D50DE38CF470382C26886260F72366C55716FE + 61B006897C15ACF8006B7D6511720B7ED9E944F23A4AEFCA806422B0545D9466 + 5F46163D2BF74DB413702AA9565FD14960A259F0A6C4AF73B9C03ED3C52A8675 + 0E62A74B605F7A3CF17AC03B2C121F541F6FD68AD8C53709023FDD042F2B133D + 473A952F440FC4C81D82F5A305A20F6211565F4C58CAC4FDCB286BA153C3DD66 + 425A3BF9534FA18BDDC7E3BF65ECD2476A8715C7D277AAEC4CEC1DF524C4AEA8 + D044DE89B374B171A1713735F75B3AAE51F832F8FCEE13B02C9345B17BFB65F3 + E9C64F0D0FDE45A231BA74D5067AFF506A49C262D722DA98BA4327E62F69F29C + FDC5894EECBF69E3F391F1A7CC7E75D8522CFD5211BC491F054C3E0B87BD1FBA + 2497D084B8B75F7E6960EA8C194D8DEBD74FB7216E8DB86545C396B536B71D69 + B16BC5DA6A8468582EBD4FAB08A2A980B543DAA213A5443EDE689060114D272C + 8CACC456EE5BAA865C8BA8A53901ACEDF0A3156904ADF8DB9A05B37702749EE0 + 0E72ABC0BA786E1FD1FC6D45ECC2FAFCA4E0BAA261ED46BA6E003E4818BDB997 + EC87AB432708C2177EC01BC99A80C3F1AF8B6FAB05B1B9615974227DB251E72B + D5FB5AC86217F582D1043B8972CC30EB548FD6F8CEB64988DDB7DB06A84BC00D + C1C8228BE80988A26005A318BB1662F55A12BBB315C1FAEC7DE7274562E80A86 + E9BB8F6CA33FBEF6A1E94158EC5AA06FE5A47B62241B5AE69C726538B2E1799A + 307F76D2BE640FCDF32F5F33645DF9E09E879649246FB0BAEFB6A623B1FE604F + 7FE35F5F73D744227EABDB0F21D1BD2B0E6F596573EB6C4C74CAC4274B342C17 + 2C8CBFB0B86FD154C07869FE8E86FD0445855B2A5706519708B346D8CA7D836B + C27AC13AC14440D18E89953A879BC47F939A56341D232DE844ADF37A71202A76 + 37D2F12E10A940E30FEBFDAC515037091259EBE0FA724506FB4167176E66EB68 + D8973A1546892E8CDC7832C1ECDD337B9E0B59EC8E847B8159A7DA4AE7794C00 + B1DB1F955F119C18661876AC2D14A3F73B847C6D87A8F1BB685EA52E0B9B2274 + 0F89852FB31C7AECEEBF3B95AEBDF0F8CFD7ABEF1EA53B7FFD1635B6068FFB8D + C5AE20C1BB272F9563B1941F7DF784996F0D14D59450695591BBA4ACCA5B565E + 61F53810BBE3E7CE78C93D69D6B98965B224DFE53FFFFE3589BFF7DCF5B3854A + E56EB37B2D9DADADB4FFC5F55D65BDC172B79D1D48B45C11BB1B6D1E3E5BB3FA + ED466580105D9D669D4CD2F48A4C104363FB691ACEAA266A114ED58089666533 + B3CA3A91E6D9889348B5BA9C21B0AE15E185EB85354DC43F75A4055D2DA9A305 + E9261AE29985B5293186282A76AD0A17233195ABBA3102930D2136E142719ECD + 7DE0F9FB1EA9D14C52B5FC78EEFE87923B228914D9762646996134AAA2BFDF09 + 0A55EC8EA46B81518753FF9D1DF340EC7ED81379455064D2F48087A6EB124134 + 042374A0DB5A64B61ABF5B11BBB663F55A16BB6E97440F5CBF982E5D3AFDB8DF + DABA4374D503AFD27B0793DD1A58EC0A321453D702EE3967FF9F6BCAEC8BAD6C + D3B6E32D2A76F5BF57BC60E929430B657993EF82FB9342D8ECBEF39F8FBA3D9E + F176AFE7DD973674F85A9AC3DEEEDE5A8B9BBEB4A261CB32DB1599DD1056F061 + 44A3213A4C251AE03DD50CFE74885A1CB57EBBA2C3FDA91A44A346DD08B3E170 + 2BE982B501EDD3212ABCAC36F65682F58B083AB408782717931A566A36A99651 + EDC42374405BE2E78A499CF025C56436081C11E1AD172056D2055B8907ED74DD + 8C24E8B42C2135541E7CCDAD4C58051094786FCC22B418D5F137E3F5E3244621 + FFF42338090A55EC3A7D4E5ACC5255EB5D87C63410BBDB5A43AF08247218644E + 85976A758920F67685A9A9CF9A613DDB621740107EE9BC13E98ECBE71F177317 + 111A2EBE7B3DB5F70C5BA859EC0A105C39F10EE5D1F9A19D6DDD1FB9749BABB4 + 4AF803DD7BF810F51FDAD55EB3F482A474C0DEAA3A9FB4F86B43DEDE1FDCF5B3 + DF492EE94B76AF69EBE3BFEFA9EE09967ACACB1B22B5B541DAB72FED90A644F2 + 0EB737BA2C83C969209B6217439A68CC441B6B51E1050BC143641C9B351DF8E8 + 624873499AF5F40DAE688408B32409227E9A1BC97C38BC50C52E4C07681031DC + 6927752C9E41B874C05FF0EC34EBB2D84D0FDE1F648F84F045E741E4DB6A1655 + 016DCA77E3458B15371C514A488D30709D6699D904C04215BB765CC34431BBD7 + 56DF9D51CDDCA70E7DAC31187D4574FD85D53E2AF52647647AB77D803A06AC45 + F9ABF6BBE8E44ADB29876D89DD04D36A03F4AFDFF8282D98912499E897CFEEA1 + FB1F7B67E86F16BB69E8BD7BD2628AC96FDADDDE7BE6E75FA6928A73AC6C73F8 + 894742E3969CB567A02F34C54572500E873A5CD1C81B55B76DB836B1CEDE7B7E + 8E61BEA7EC9CD39EB7B6D2C0F6ED9498AC56BE6CF9EEFEC84065F7077B239E60 + D0EBEEEE36B21877CA242DCB629C5D23D06861E2085C3AD20907ED36A2E183AC + 0CA98F34FA8FB0A81B8251236666D5D083E361A29051B7BED0C42E260622DAC6 + 3D94593C602BB0D8B5063E6053494DBE826F63AAD11223B726A37AC8C44529DD + B91A892DA3FA2D54B13BD2CF9AD1FB3492D6E4AC53FEE801A3FB608A3E110440 + C8B27E8190655A2A7C2E5AA09BA066C11D2223B10B4A8BBD8A983D9FA6D4940C + 2D3BDCD24BE7DC369C458DC56E1A7AEF9AF80CC9837E3DB6B09370A2E7E0010A + 371D6C2BF6459AE5A8ECA1F686935C2EF9DEB2FB9B5669D75304AFE559A45D6D + ADD479A4B1A1F1F5CD5312092640C4E7EB2E9E3FBFB17CCAE4B2C6AD6F91BFB1 + 511BB3D729A10BEC8A5DF8B922B4161A23B34C476688CEAC1E4D59C28CFCF9D0 + 38FC5B9AED8C1A45910949F04F84CB8D596353486217A9B931C9E932C1633905 + 8B5DFBE09A10CD01B18DCD464DF4A31E56FCBA470A441E784CB7AC10C5EE4887 + B90346936147C23F3B675811BB5E97441FD52582B090E23789528F8B16D6248B + 5DB842C02542808CC52EB8EB8A53E9BA8B869B38651F34FBAB4F52341E2F98C5 + 6E0A822B27DD2C939C91EF5626D9D5A27D7DD4FCDCE334AE3C8AABFF9FC0EAA6 + CF687FDF79FB4F5EF4FAFD9FB0B2CFA683F5D4F6E6E6367F776FB576F0022E0A + 8AA03DCD35AE767F64A0AFC4D5D533D1B370D1BEC8FEBD726CF6BC7B2EFCE3CF + 1FB3729C14D81194FA149BA982851B211A9F7534895DA3C62855462F2DFA465D + 4424A79B995C28621702E85FE2D7906D58EC664E75BC2E8CB2B3C1071EF1569B + E37F5B99A8375218B91D15A2D8CDE4FA323947ABDFAB518D15B11BF048B44817 + 760C892490504214651783111DE0B3ABCFC206E370AF22269149ADB93F4A7DE6 + 7EC48E88DDEF5FB588AEFCC4CCA1BFC3CAB1E75EF7D4A0E8052C7653D0BB72E2 + BBA40A0CDB783FFAB94D14A84C17B3F53862E13075EDDD15741DDC5A52EC1BBC + 5B7D24454E0CAC3E3614D775DFDD3FBF5996AC4FA408767753B0B3B3EBC8D637 + C3A56DED3589E555E79CBBF9F0B62DB303DDBD55346EDC41B9ABABCA150A5D75 + 7EE3D6A71DAC563B99B88C72C94394E0832D6A7DC3C7F40A4A9D937EB48B5DD1 + 8417DA6D45E3F4A69B94530862D72C0140B660B1EB0C66E988F599CB58ECB2D8 + 2D58B16BE47A0071BAAD35FD54145885A706DC34B1D8739C1B84194874B1BF3B + 42E1E333B32589DD0B573EBF562663B16B74A8D2122F7D7CFE04FAFB4FCF21AF + 7B5870BF7DA09D2E5D359C13C6AAD8BDF6C77F3D2E84993CF49FE46592442B9E + 5B7DC19816BBB000588D569084FB8C4B5E76558C37F4D94598B189F367FD8754 + 3B6368A259DFD1C6C8404B539B74F483E26229A89B8C247F3AB0E6E89F127F21 + 8B5A34221FB07B6EF0DD95776C275FBC6AEBAEBA3AB8ED2F2FB84B3B3ABA249F + 3FE46E6DFBF6050D6FAE73B85AAD084A085D4CE878D7E4772BD9A0403AFF5DD1 + 8412D9C0AC31128903AC6D3844EA5B24E44F21885DD1106F096065873BCD1BF1 + EB4D7C398BE3D785A80DF0FFC430B988DF2F8B5DE7A823B5EEF4BEFD88E39A08 + E368E4CB0E410C77AD6CF970C25D49FF3EB3D81D198CCE11F71FC22A687D77A3 + 0F2B62D7685219125120218519C8B836B5C4435302E222570B2CC7EFB787A927 + 9234012E49ECCAB20C638365CBAE9E6FFF761B3DFA97FD437F5B15BB16395F92 + A417ED6EAC2707627792F222C8D7DADE81CBDBEB5E7A598BCBE7AFD32E8EF4F6 + 503414EA72793CA5DEF28AA1AE48CBE657FA025DFB8ABD6E6373BFB2F447A56B + 9ABEA55DB6F3F6B5FBBD7EDF0CA3F55B8F1CA163BB7635D49C70426FD5F4E933 + 60D12DABAAF21EDCF95E7F7149A0ABEDF0619777CF9E71380197CFD775C2955F + 1A7C12B63EFD64D7A4D34EFBED398FFE9348CA5CAB888A2691749D782660ADC5 + 10BD68BA516426830FAF91E7FC68F0E14B60D65020741842E5A48BD99AB01889 + 08A675943ED57021885DD1E41D38879BE3D72AE2DE8634CD885F9C2E59028B5D + E7304BE4A0AD63A37A180DB1570B51EC029EA0962156C4AE51B8B0546217E278 + 6699978AECA85C0DB0ECEE508EA19904E7B8D8DDF47EF360ACDDA8C68ACC6237 + 05A195134F8EA8C2C2D6EC7CEF69E76FA49A69CBF4CBFB9A8E502CD4D71E3861 + E660AC8C818E76EAD8F4DC4055519F2FCD2EDF0EAC694A121B3BEFF8C92FBC3E + FF0D462BC372EBDDB1236959D1B4E96F4B752754B5BEFCF2B4124DBABDB23973 + DE18B7F46C04DEA65DAFBFF6DCA75EF8DD452354AD4E2727B0928E15C0728318 + B946D6D1225285F0DFA7D987597CCC6C809B764BFC3C53818FF8DDF1BA49276A + 4452CDE6BBD885B811B9F7A99E9F4CCF85C5AEB3188D82E863642306EFF774EB + 68635CE7824215BB6661139DC22861505E851EB32276E1637B8ACEB26BE4B35B + AC88DB198AC8ADD6F9E4EA0929E2D5AF13C248190C4D8B6DCB3421CEDA43317A + 6F384B9B636217E2F6BF5FAEA7FB1EDD417D03C9418558EC0AD07BE784EB94C3 + 5FA59CC1C744B7714FA8DBEA3A65B9A9483EF4CCE33DD33F735929FEDDB97B27 + D1FECD54E24F6F24925C9EA9253F38DC90F83B55EAE01DAF6C8C96EFDD7FDCDD + 2BAAADDDDD3F6DDAE440758D578E447A8E6E79C353311096267CFA33ED3DBD3D + 7D67FEEABE9347B03A4722131766CFE3E3FD59C17330F303062293B9F4BE7FD9 + 4624FD2CEA0F43B6F898A71A96475DC0E298CE1D26DFC5EE2EC1F3B0936294C5 + 6E6E10F1D134AA63086074122D47BB71886C885DA787EF8DEAD1AAD8C5328CBC + D9895F9E0E8CFCFD8CD48E8E969116D859C58AD82DF14874BA6E821A400485A3 + 7D512A567E9F5CE2A109C5EE94E20B93CF206A91C8E2B46A5F92A86D56F6B327 + 1E91618AB2AF1965C3C91FDE3C161A14C8A413BB4FBE7A5048ECCAF2F0EB190C + 45696F6317BDB8FD08351C337EA4EFFDF2429A326E3834D9D7FFF9758A4487C5 + EE43FF7026793DA9057D0A569CB768F2D8F5D9D513BC73D2DFC82EF92A915064 + E9A230B4BFFB3655CD9DB7853CDEC54D2FFF852A070E925BA09E2559FA42C9FD + 479ED02EFB60E5BF0425B7BB58BFEE5BCFFE6F4FD5D1A3A5FAE57059F0CF9ABD + BF5FA20AAFD7DF136C3F56E66A6B2B8A75766FB8B871CBDF8D70358E54DA59AB + E1C860B1FF07A574E9961BA5F534C22CAD6E36109D74F6AC523E99661DF82FA2 + 714F973227DFC5EE31C1EBBB375EAC0821B386560F8B5D67316AF8F5965DA3CC + 82763A344E920DB1EBE4E894D5840D66CF9F68C7DB0E46916C4683CB8AA35811 + BBB869671AC4D915057EB7FBBA22D41D1E168CE58AD03DB53AD95AACCDC88650 + 6798DC0634C92B1C89C6904BC67C340633BA574E3C5739996BA5148D97F7239F + 7D954AAB4D2DC1DDFBF75260EAF463B18181EA23CF3DE99A5825969E4F79081E + 29BDBFE92AEDB2F76E79E04F2EAFF792BE9E6E2AAF1E0AAE40DB5FDE182BDEB7 + CFE533A83AFFB469EF44FD7E4FD5FCF9D5A555D5135A0E1FDE77D6BAD52765A1 + FA464AECE2223199ED9716CEC5C87F573479839DE16C2D10E50882FF6732F61F + 4E0702E93F61633BFD35885AA859ECAAD819F614ADBB4210BB30EDA0B3D6E9E0 + 3ECD307263D0D7B1D924B55C8EDC382D768D9E3F279367987D33AD8A5D005F78 + 18129CB4AA9BB97EE5BA53E33856934ACC2AF70E5A6EAD0083EAA1DEC8A035D7 + E826D5957A686A20397D6F6B283AE8A30B4B71428DC037B82BCC62D788512376 + 1374DE5E3BCBEDF1DC24910CF159A1FDCDBD60F946576DDD32B36DE54884248F + 67D0C24B07B75169B1707ABEF6C09AA6A499DDCF4F5972B5AFA2F4FB3D7DA1CA + E9679FBD4511B24BDD6E8F2F3C30403B9EFD534F4D7B4769AA1D22C6EE89D77F + 69D98C55B764920658949112BBC02CE4901948E58A7BA70D346E4534E31C6F54 + CA6B16CE11FB3F8BD4F0567395721FA96944AD5A88AD460E3002421B82FD98C0 + BA2C7655AC0EB55A096796EF623731A1F43652475536D1C8B90A984D5033B2CC + 1B4D4C5C47E9276D8E144E8B5D4414429419FDA456A786F0CD5299DB11BB2311 + 95A18E8C2373A40BB738E6B02A7631D90CAE0C2E4175D51B91694F6778304499 + 19D8D5EC0A2FD516998B68CC1BDBDCD24FF1396A2C76758C3AB19BA0E9B60981 + 528F7493240D0A9FC161F47462B7BFF928859A0F77D087EF54C257D73573F1B3 + 21F2CF1878FFB57981A2D4C2D745D239C56B8E0CE5BFDE50B7B03212F60CF54E + 5D3E5F67D1A90B0E55CD9A33A3B5B1D15B5A5E7EF883679E3EB14A3ADE4F0242 + D7ED8D2E5B5EBF3D1B42178CA4D80556C39161E80C82EF03CDB23A32FE381A81 + F3C304175859D309560C95E2258618D7360C488E81891356B2F8E04B8261C36F + 5BAC1F2D98D4878FBD88E0C877B12BEAB33B92EE35F92E76B575818E267CCAFF + 9BEC8D6CA4630AA9915596EB961B4D3E33EA38E25DC4C4DFDF3B784E7090C47B + 9BCE4FD669B16B76BFCD5CB9AC1088EFFB7A83DFEC885D6094DAD92E3031A273 + 73976E7936429D651DAB62172029C49C722F4929141622287CD81BA123C1A870 + EF745AC043D39562B45F58860FF50CBDF62C76758C5AB1ABA567E5A42F2B02F2 + 2645EC06CDC46E6FC387D4BFED2F545E12C383D3E53DE7CA56C9E39BD1BA6D0B + 15B7BC4B5E4FEAC749F9F5C1D2354DB76B97BD30E58CEDC880765CA529C277C0 + EF1D7075F7D6EAFB593910BA60A4C52EB880D4F4B8A2E1C8F41F573C6B101ABF + B3704CA4BB8430C1D067BD6639A67FC2771B437CA9E2ADE258109F567CE844FD + 8B8DB0EAAF96EF62F72D0BEBFF3BA9D6DDB614EB40D82076312CF7A27EE4F92C + 76CD3AA10F923A1AE3646413B30C8BB024C34FF5A06EB999F53D5D9C6F2BE079 + 802511EFEC370CCE418B59084474165EB6797CA389B7990AFA74A11FED8A5D00 + 71FA63CA6C4E44AAF3C3FEF1DC89F90F8E11EC885D806C6AD34B3D54E5730F59 + 79617D859B019241A0C46C8CC1C0723CA9D84DE53E1779941D433463D25AC287 + 370E8B5D1D6342EC26E87BF4F2EFB9AAA77F43F27893C44DA8F51875EDD8D453 + 49AD83AE05EE598BB7BBA62D5818E9EDA596F54FA8A981D3558444BB4B5637CD + D52E5B3F75F12A45057F37EDC6C37446A29E851737BD5E9FE5AAC986D8B59A4E + 18E0A38FA1CCC42B6DD525C2093079265D43A845D4BFD88875646D9836DFC5EE + 5F492C12478237491547B85E885E3C3778EEA6922A4A3034FE71C17D25C857B1 + 5B12AFABEB4D7E475D427C20A6B6B03F970918BE82A04507D68AC0C1503FEA5F + FF2E392178F5298C13319DCD9E65B3FB636524468F59AA71BBD787F61856F35F + 937967CEAAD845075C3B511016D907C89E8517CFC1DF929AFA5B6F6040F4228C + E63558DDE968C7AED84D809BEA89ABDDB01D756B0F16BB3AC694D805F2865545 + 3DC79ABE25B9DD37FACACA2774EED949F2DECD14288A3F44C595EF7BCFFADCC9 + 480DDCFCD2F3035552B34FD477261A75CD2DFF51E3D0C7F2F9294B164A246F13 + 3CB54E99A4651734BCB93D07D5920DB10BAC862333F2DFB5EA12E104F0DF85D5 + B05F707DD144087AD0C0FDCAC2FA852076CD0441B6C857B10B20263141285544 + 0A3C3B10377807EDB836A0830AEB2946BDF44217D7001FD5FA14DB2F26754448 + 2FDEF0CCDD436A2C6A2B62DC15AF3FB828E95DA236C5CFC72CF280514C589194 + E766980DE727F6FB0D12F7A186951A1D0A58E51342121D75BD25DAAAD8BD9CD4 + 6FB0763FC8180A23CE36C1730378D6D031D0BB8B01A3EF7CDE90A9D8CD112C76 + 758C39B1ABE5C8F7977EABBFE1E0E5E3ABA27811A70D2EF4F8884E3A6B73EFDE + F76717875BAA44856EBC326E2F59D3F4A076D9FA298BEB29FD90692E852EC896 + D805A7923A44272A5E8CD209CF27554C6643F0A64B676C841D816627C44F2188 + 5D27FCA033219FC56EE2B81020A8E354AE375A9720FC3BD564407C35D1B14564 + 018CCE2C30584754E0605FB0EC62F8DCE83BFA54BCDE302135951847ECA58FC4 + AFD5286B1ECE07F33B3041D44CC099B928C10A0E21877BA217DE89BA801B17C2 + A9E9A30CCC22D53DC2E85B0697063C7F3F8B9F9FD179E1BACE217582A1F67D85 + 15FD99F8F568B193410DD7F410257F67706E7F886FB3D3A4EE71ED53E3F58D7A + 37BA7FD8CFD749F5CFCE550CE51185C56E6E60B16B80BCEA645F6F5FEB8D924B + C290DE29B6F743F44AE99AA673B4CB9E9FB2645D3C32448AEDA4453914BA209B + 62D74E3A61C4AF85FFAE36B310EE131A0291096B76B1E3B30B6065C1D0AC95D4 + CE7662041782D80556470444C0B95752FA8E68BE8B5D807712B16DE1D620FA3E + C1AA87FB0EE19B704140C704C27611A9CFA61956054E3AC10B2006372AE58DF8 + 79E19C6055C67702D661442730F3CDDF1D3F9F97D29C0F5C94F03C5C96A24E20 + BA0FC5FF9E4E6A9417D487D93B28FA3DDC1DDFF7AEF8DF65F1EB5A62705D890E + 3AC2563A952E18ED1AA2E118DD57B80C6D8AD73D3A41569E83BBE2D73E121322 + 47052C76B3C315CB665065A98FB6EE6DA537761D63B19B8EBEBB267E2526CB37 + 2897664B4485655F75E5FD87867AEF2F4C3EE35259929E325B5F11BAD7284277 + 5D8E2F3B9B6217E0C30A317893E0FA668D232CF2ABC8791F5E1C0F43B7685CED + CE3E5EA194E72DAC0FCBCF7A8BC72814B10B9C745F412706C3D16B05AEB110C4 + 6E0288435862D1A8894EE0B30A9E15C46DC53369C592A70D0FB8C4C1F3C1903C + 5C2C7609AE8F76E1511BF593EA1D843B030C22B8FF7626B66AC13707DFD50FC8 + 9974C1DA67ED74523BE54E1818ECBAA18C3958EC8E3C1EB78BAEBD681645A331 + 3AD8DC4BEBDF6A64B12B4AEF5D132F515EC11B952BBCC4CA762E89AE2A5EDDF4 + 48E26F7D08322DA344E8826C8B5D601686C80C0C956172833EE03A7CF03E41AA + 7871A211FC537C5FAF5366C36A98D481E7E07C8175F1427E85D4C920562824B1 + 0B4E886F6F67F21F4027063E9218E22D11BCC64212BB0970FFAF2635049693A2 + 17EF03DC25EA33D8472ADF4F2BC01A89A81CB0565AE9D0A2CDC3330FD7826A0B + DBA57B07B15F3C8B10F3734477AA03CFF52A1A1E89725AEC02270C0C4E3C0763 + 0616BBD9E1AC79B554E473D381A61EAA3FDAC362D72ABD774F5A2CC762374B24 + 5D29B48124FF31B0FAE8A5DA45EBA72E7E5A914D4943B0A348E8825C885D6035 + 9D702AFF59B80E2C8FFF8E214B2B0D214410ACEF68F8E03BEBC4909A59862023 + E08FFA03B22EAE0B4DEC02DCE74F29E55B24DEB949F81742DCC0275AB6708D85 + 2876B5E7044B3A3A62F87E59117709202AD1A9453DC27AEA945F265C04600DC5 + 64502B821C6E1778CF314A24FA3EE8C1BB8D217A749C3E25B03E9E3F7CE730C1 + AD27CDBA10937806F0CC89D637DEEF1F92EA86A1B5928E84D8D55E3F2CB3A21D + CFC437162E0B9B29CFADB95A58ECE60616BB3609DE3D795A2C26DF26C9F2D794 + ABF6A758756F604DD32CED020357865B56346C11C9DEC4D803BE7510424B49F5 + 6B83EF9AD65A02DF3AF8F86162C9A6F8DF23912A55247D30ACB9E81CBD91DB2A + 1B7360E87736A91D1BDC67DCDF45F1DF20B020C4619DDF18FF7F2641FA99E110 + 6EA8634C10C590363254A2A3951065897A4FBC5B1035E926B3650A2668C13F74 + 69BC4CD09CD3EEF8B9EC88FF1B0DDF87E49CD0C2A8D2C9A40ABE3349F50F4E08 + 6F3C7310F710A208DFD66C71DF898EC63252BF65DA7D27BE5F10B7988456EFC0 + 355911BB091293EF708E08E987FB80F8E5303440DC621ECA3BF13A78DD461DE4 + 058AD84DC4751F4B342862775FE28F59D73C394796E5E9B93E292BB85DD2D6DD + BFFE7C5BE67B522918B19B0093D982A1D6DB6459FA4749A2F1C7FDAE34AAA56B + 9A2AF4CB0705AF4BC203BF71C5E12D1B737D1D8C234048E35E1B856512F53185 + D50B2186AC646A63188671123B6297610A868213BB5A7A574EB89624E95645E1 + 0E8598522A6463C99A26513F5466ECA28DB800AB11428D2193529854EB171A0D + 11DF3BABB1751986619C86C52EC3A4A0A0C56E024C669364BA4D96A825B0BAE9 + F25C9F0F9315CC82DD5BC1EEC4348661182761B1CB302960B1CB14224E253A40 + 082684F2C9CB60EA0CC38C1958EC324C0A58EC32858813296CF336173CC33063 + 0E16BB0C930216BB4CA16125A49819988C06A1FBC75C5F0CC3300CB1D8659894 + B0D8650A8D19A4C6E8CC2493D79DA48AE5BC4D91C930CC9882C52EC3A480C52E + 536820B8FEAFC95E807DB08AD4280E7D36B7671886711A16BB0C930216BB4C21 + 62279D2A82ABAF52CAD35440D98318861913B0D8659814B0D8650A1964953A95 + D4B49CC8DE54476A262590C82605918BEC79C826C5D65C866146232C76192605 + 2C7619866118866198BC85C52EC3300CC3300C93B7B0D865188661188661F216 + 16BB0CC3300CC3304CDEC26297611886611886C95B58EC320CC3300CC330790B + 8B5D866118866118266F61B1CB300CC3300CC3E42DFF1FCED20B5884D2840600 + 00000049454E44AE426082} end object panContent: TPanel Left = 0 diff --git a/windows/src/desktop/setup/keyman_setup_sil.xcf b/windows/src/desktop/setup/keyman_setup_sil.xcf new file mode 100644 index 0000000000000000000000000000000000000000..61a535061318bda1af8bfbd614bb5ce2f1bd0c7a GIT binary patch literal 36105 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 literal 0 HcmV?d00001 From 00a88fbe304bea31b2fa5ec1fe3afe2ee9ecc625 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 28 Aug 2024 14:02:38 -0400 Subject: [PATCH 179/262] auto: increment master version to 18.0.101 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 0972d983247..1afecba6497 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 18.0.100 alpha 2024-08-28 + +* fix(windows): check IM window will be in a visible location (#11967) + ## 18.0.99 alpha 2024-08-27 * feat(web): import the generator for the pred-text wordbreaker's Unicode-property data-table (#10690) diff --git a/VERSION.md b/VERSION.md index 59ec57bac21..5e9ac7140c0 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.100 \ No newline at end of file +18.0.101 \ No newline at end of file From 0918b3985f68ff895fb25d53486e0cbad2307c0f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 29 Aug 2024 08:48:06 +0700 Subject: [PATCH 180/262] fix(linux): add dependency --- linux/debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/linux/debian/control b/linux/debian/control index 53d3710098b..fbe7066f5c2 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -91,6 +91,7 @@ Depends: gir1.2-webkit2-4.1 | gir1.2-webkit2-4.0, keyman-engine, python3-bs4, + python3-fonttools, python3-gi, python3-packaging, python3-sentry-sdk (>= 1.1), From 721bc9172eefd570bf59aa377dd455231cddae19 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 29 Aug 2024 10:01:45 +0700 Subject: [PATCH 181/262] fix(web): prevent unintuitive space-output blocking for mid-context suggestions Fixes: #12309 --- .../engine/interfaces/src/prediction/predictionContext.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index cd856153b23..3ce0e936b45 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -209,7 +209,9 @@ export default class PredictionContext extends EventEmitter Date: Thu, 29 Aug 2024 13:21:08 +0700 Subject: [PATCH 182/262] docs(web): add documentation comments for touch layout interfaces --- .../keyman-touch-layout-file.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts index 786d9213e2b..4512024541e 100644 --- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts @@ -7,6 +7,7 @@ // writing // +/** touch layout file consisting of specific layouts for tablet, phone, and desktop */ export interface TouchLayoutFile { tablet?: TouchLayoutPlatform; phone?: TouchLayoutPlatform; @@ -17,6 +18,7 @@ export type TouchLayoutFont = string; export type TouchLayoutFontSize = string; export type TouchLayoutDefaultHint = "none"|"dot"|"longpress"|"multitap"|"flick"|"flick-n"|"flick-ne"|"flick-e"|"flick-se"|"flick-s"|"flick-sw"|"flick-w"|"flick-nw"; +/** touch layout specification for a specific platform like phone or tablet */ export interface TouchLayoutPlatform { font?: TouchLayoutFont; fontsize?: TouchLayoutFontSize; @@ -27,6 +29,7 @@ export interface TouchLayoutPlatform { export type TouchLayoutLayerId = string; // pattern = /^[a-zA-Z0-9_-]+$/ +/** a layer with rows of keys on a touch layout */ export interface TouchLayoutLayer { id: TouchLayoutLayerId; row: TouchLayoutRow[]; @@ -34,6 +37,7 @@ export interface TouchLayoutLayer { export type TouchLayoutRowId = number; +/** a row of keys on a touch layout */ export interface TouchLayoutRow { id: TouchLayoutRowId; key: TouchLayoutKey[]; @@ -62,22 +66,37 @@ export const PRIVATE_USE_IDS = [ * * Make sure that when one is updated, the other also is. TS types are compile-time only, * so the run-time-accessible mapping in activeLayout.ts cannot be auto-generated by TS. */ +/** defines a key on a touch layout */ export interface TouchLayoutKey { + /** key id: used to find key in VKDictionary */ id?: TouchLayoutKeyId; + /** text to display on key cap */ text?: string; + /** current layer */ layer?: TouchLayoutLayerId; + /** next layer */ nextlayer?: TouchLayoutLayerId; + /** font */ font?: TouchLayoutFont; + /** fontsize */ fontsize?: TouchLayoutFontSize; + /** special key */ sp?: TouchLayoutKeySp; + /** padding */ pad?: TouchLayoutKeyPad; + /** width of the key */ width?: TouchLayoutKeyWidth; + /** subkeys */ sk?: TouchLayoutSubKey[]; + /** flicks */ flick?: TouchLayoutFlick; + /** multitaps */ multitap?: TouchLayoutSubKey[]; + /** hint e.g. for longpress */ hint?: string; }; +/** special keys like framekeys, deadkeys, blank, etc. */ export const enum TouchLayoutKeySp { normal=0, /** A 'frame' key, such as Shift or Enter, which is styled accordingly; uses @@ -102,29 +121,51 @@ export const enum TouchLayoutKeySp { spacer=10 }; +/** padding for a key */ export type TouchLayoutKeyPad = number; // 0-100000 +/** width of a key */ export type TouchLayoutKeyWidth = number; // 0-100000 +/** defines a subkey */ export interface TouchLayoutSubKey { + /** key id: used to find key in VKDictionary */ id: TouchLayoutKeyId; + /** text to display on key cap */ text?: string; + /** current layer */ layer?: TouchLayoutLayerId; + /** next layer */ nextlayer?: TouchLayoutLayerId; + /** font */ font?: TouchLayoutFont; + /** fontsize */ fontsize?: TouchLayoutFontSize; + /** special key */ sp?: TouchLayoutKeySp; + /** padding */ pad?: TouchLayoutKeyPad; + /** width of the key */ width?: TouchLayoutKeyWidth; + /** use this subkey if no other selected */ default?: boolean; // Only used for longpress currently }; +/** defines all possible flicks for a key */ export interface TouchLayoutFlick { + /** flick up */ n?: TouchLayoutSubKey; + /** flick down */ s?: TouchLayoutSubKey; + /** flick right */ e?: TouchLayoutSubKey; + /** flick left */ w?: TouchLayoutSubKey; + /** flick up-right */ ne?: TouchLayoutSubKey; + /** flick up-left */ nw?: TouchLayoutSubKey; + /** flick down-right */ se?: TouchLayoutSubKey; + /** flick down-left */ sw?: TouchLayoutSubKey; }; From 0f39a898412789f50f3ec016768c85df39f0dac0 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 29 Aug 2024 14:28:21 +0700 Subject: [PATCH 183/262] fix(android): Prioritize certain actions over multi-line for ENTER key --- .../java/com/keyman/engine/KMManager.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index b921ac6c0ad..89690dffec0 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -1274,17 +1274,13 @@ public boolean accept(File pathname) { /** * Sets enterMode which specifies how the System keyboard ENTER key is handled * - * @param imeOptions EditorInfo.imeOptions - * @param inputType InputType + * @param imeOptions EditorInfo.imeOptions used to determine the action + * @param inputType InputType used to determine if the text field is multi-line */ public static void setEnterMode(int imeOptions, int inputType) { - if ((inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0) { - enterMode = EnterModeType.NEWLINE; - return; - } - - int imeActions = imeOptions & EditorInfo.IME_MASK_ACTION; EnterModeType value = EnterModeType.DEFAULT; + int imeActions = imeOptions & EditorInfo.IME_MASK_ACTION; + boolean isMultiLine = (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0; switch (imeActions) { case EditorInfo.IME_ACTION_GO: @@ -1296,7 +1292,8 @@ public static void setEnterMode(int imeOptions, int inputType) { break; case EditorInfo.IME_ACTION_SEND: - value = EnterModeType.SEND; + value = isMultiLine ? + EnterModeType.NEWLINE :EnterModeType.SEND; break; case EditorInfo.IME_ACTION_NEXT: @@ -1304,7 +1301,8 @@ public static void setEnterMode(int imeOptions, int inputType) { break; case EditorInfo.IME_ACTION_DONE: - value = EnterModeType.DONE; + value = isMultiLine ? + EnterModeType.NEWLINE : EnterModeType.DONE; break; case EditorInfo.IME_ACTION_PREVIOUS: @@ -1312,7 +1310,8 @@ public static void setEnterMode(int imeOptions, int inputType) { break; default: - value = EnterModeType.DEFAULT; + value = isMultiLine ? + EnterModeType.NEWLINE : EnterModeType.DEFAULT; } enterMode = value; From 9fa3eb098a1466c6d9b208038159e02cb03b6f01 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 29 Aug 2024 14:54:35 +0700 Subject: [PATCH 184/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- .../keyman-touch-layout-file.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts index 4512024541e..7ea749906bc 100644 --- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts @@ -7,7 +7,11 @@ // writing // -/** touch layout file consisting of specific layouts for tablet, phone, and desktop */ +/** + * On screen keyboard description consisting of specific layouts for tablet, phone, + * and desktop. Despite its name, this format is used for both touch layouts and + * hardware-style layouts. + */ export interface TouchLayoutFile { tablet?: TouchLayoutPlatform; phone?: TouchLayoutPlatform; @@ -68,25 +72,28 @@ export const PRIVATE_USE_IDS = [ * so the run-time-accessible mapping in activeLayout.ts cannot be auto-generated by TS. */ /** defines a key on a touch layout */ export interface TouchLayoutKey { - /** key id: used to find key in VKDictionary */ + /** key id: used to find key in VKDictionary, or a standard key from the K_ enumeration */ id?: TouchLayoutKeyId; /** text to display on key cap */ text?: string; - /** current layer */ + /** + * the modifier combination (not layer) that should be used in key events, + * for this key, overriding the layer that the key is a part of. + */ layer?: TouchLayoutLayerId; - /** next layer */ + /** the next layer to switch to after this key is pressed */ nextlayer?: TouchLayoutLayerId; /** font */ font?: TouchLayoutFont; /** fontsize */ fontsize?: TouchLayoutFontSize; - /** special key */ + /** the type of key */ sp?: TouchLayoutKeySp; /** padding */ pad?: TouchLayoutKeyPad; /** width of the key */ width?: TouchLayoutKeyWidth; - /** subkeys */ + /** longpress keys, also known as subkeys */ sk?: TouchLayoutSubKey[]; /** flicks */ flick?: TouchLayoutFlick; @@ -152,7 +159,7 @@ export interface TouchLayoutSubKey { /** defines all possible flicks for a key */ export interface TouchLayoutFlick { - /** flick up */ + /** flick up (north) */ n?: TouchLayoutSubKey; /** flick down */ s?: TouchLayoutSubKey; From 1d55100cb3ba246c9f5c789faf8ab134fd73fa17 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 29 Aug 2024 14:54:48 +0700 Subject: [PATCH 185/262] chore(common): Update crowdin strings for Czech --- .../src/main/res/values-cs-rCZ/strings.xml | 19 +++++++++++--- .../src/main/res/values-cs-rCZ/strings.xml | 24 +++++++++--------- .../KeymanEngine/cs.lproj/Localizable.strings | 2 +- .../cs.lproj/preferences.strings | 2 +- .../cs.lproj/KMInfoWindowController.strings | 2 +- .../Keyman4MacIM/cs.lproj/Localizable.strings | 21 +++++++++------- .../desktop/kmshell/locale/cs-CZ/strings.xml | 25 +++++++++++-------- .../desktop/setup/locale/cs-CZ/strings.xml | 8 +++--- 8 files changed, 61 insertions(+), 42 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml index bec7931da85..97da3115df7 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml @@ -33,6 +33,8 @@ Velikost textu nahoru Velikost textu dolů + + Posuvník velikosti textu \nVeškerý text bude vymazán\n @@ -51,6 +53,7 @@ Pro instalaci balíčků klávesnice povolte klávesnici oprávnění ke čtení externího úložiště. Žádost o oprávnění k úložišti byla zamítnuta. Může selhat instalace balíčku klávesnice + Žádost o oprávnění k úložišti se nezdařila. Zkuste nastavení Keyman - Instalovat z místního souboru Nastavení @@ -67,6 +70,8 @@ Upravit výšku klávesnice + Upravit zpoždění dlouhého stisknutí + Titulek mezerníku Keyboard @@ -85,7 +90,7 @@ Nezobrazovat titulek v mezerníku - Vibrate when typing + Při psaní vibrovat Vždy zobrazovat banner @@ -122,6 +127,14 @@ Otočit zařízení pro nastavení na výšku a na šířku Obnovit výchozí nastavení + + Doba zpoždění: %1$.1f sekund + + Prodloužit dobu zpoždění + + Zkrátit dobu zpoždění + + Posuvník doby zpoždění dlouhého stisknutí Hledat nebo zadat URL @@ -157,7 +170,7 @@ Neplatná/chybějící metadata v balíčku - Keyboard requires a newer version of Keyman + Klávesnice vyžaduje novější verzi Keyman - Unable to launch web browser + Nelze spustit webový prohlížeč diff --git a/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml b/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml index 1bcc7926603..db45a6706e0 100644 --- a/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml +++ b/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml @@ -49,7 +49,7 @@ Aktualizovat - No internet connection + Žádné připojení k internetu Nelze se připojit k Keyman serveru! @@ -88,7 +88,7 @@ Chyba v klávesnici %1$s:%2$s pro jazyk %3$s. Kontroluji související slovník ke stažení - Cannot connect to Keyman server to check for associated dictionary to download + Nelze se připojit ke Keyman serveru a zkontrolovat, zda je možné stáhnout přidružený slovník Chcete stáhnout nejnovější verzi tohoto slovníku? @@ -111,7 +111,7 @@ Stahování slovníku začalo na pozadí Vybraný slovník se již stahuje; zkuste to prosím za chvíli znovu! - + Stahování slovníku je dokončeno. Stahování se nezdařilo @@ -123,7 +123,7 @@ "Všechny zdroje jsou aktuální!" Aktualizace jednoho nebo více zdrojů selhala! - + Zdroje byly úspěšně aktualizovány! Verze slovníku @@ -132,11 +132,11 @@ Chcete odstranit tento slovník? - Dictionary deleted + Slovník smazán Klávesnice %1$s nainstalována - Keyboard deleted + Klávesnice smazána Povolit opravy @@ -144,14 +144,14 @@ Slovník - Dictionary - Dictionaries - Dictionaries - Dictionaries + Slovník + Slovníky + Slovníků + Slovníků Zkontrolovat dostupný slovník - Check for dictionaries online + Kouknout se na slovníky online Slovník: %1$s @@ -176,5 +176,5 @@ Klepnutím sem změníte klávesnici - Unable to launch web browser + Nelze spustit webový prohlížeč diff --git a/ios/engine/KMEI/KeymanEngine/cs.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/cs.lproj/Localizable.strings index 86f138adffe..0653ed3aeb0 100644 --- a/ios/engine/KMEI/KeymanEngine/cs.lproj/Localizable.strings +++ b/ios/engine/KMEI/KeymanEngine/cs.lproj/Localizable.strings @@ -125,7 +125,7 @@ "kmp-error-missing-resource" = "Tento balíček neobsahuje požadovanou klávesnici ani slovník."; /* Error installing a Keyman package with a version of Keyman that does not support it */ -"kmp-error-unsupported-keyman-version" = "This package requires a newer version of Keyman."; +"kmp-error-unsupported-keyman-version" = "Tento balíček vyžaduje novější verzi Keyman."; /* Error opening a Keyman package - cannot parse contents */ "kmp-error-no-metadata" = "Tento balíček nebyl správně sestaven - obsah není znám."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/cs.lproj/preferences.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/cs.lproj/preferences.strings index 305368c9ca1..9477b4c6378 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/cs.lproj/preferences.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/cs.lproj/preferences.strings @@ -17,7 +17,7 @@ "JOK-JV-n8w.title" = "Zpět"; /* Keyboards button text */ -"MPN-9N-wWc.label" = "Keyboards"; +"MPN-9N-wWc.label" = "Klávesnice"; /* text to explain verbose Console logging option */ "MrI-GM-7d6.title" = "Když je zapnuta možnost detailního logování, Keyman zaznamená akce, které mohou pomoci Keymanovi diagnostikovat problém. Program konzole může být použit k zobrazení protokolu zpráv od Keymana. V konzoli filtr zobrazuje všechny zprávy z procesu \"Klíčové\". Tento log může být exportován a v případě potřeby odeslán na podporu klíče."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/cs.lproj/KMInfoWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/cs.lproj/KMInfoWindowController.strings index f3fe6a722b1..4c9f26c0f84 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/cs.lproj/KMInfoWindowController.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/cs.lproj/KMInfoWindowController.strings @@ -1,5 +1,5 @@ /* Package Information window title */ -"F0z-JX-Cv5.title" = "Package Information"; +"F0z-JX-Cv5.title" = "Informace o balíčku"; /* button text to show Details */ "Fvy-XJ-s38.label" = "Detaily"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/cs.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/cs.lproj/Localizable.strings index f192a323683..31b54577b28 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/cs.lproj/Localizable.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/cs.lproj/Localizable.strings @@ -26,10 +26,10 @@ "message-keyboard-file-unreadable" = "Nelze číst soubor Keymana '%@'."; /* Message displayed in Configuration window when keyboard cannot be loaded */ -"message-error-loading-keyboard" = "(error loading keyboard)"; +"message-error-loading-keyboard" = "(chyba při načítání klávesnice)"; /* Message displayed in Configuration window when keyboard metadata cannot be loaded */ -"message-error-unknown-metadata" = "unknown"; +"message-error-unknown-metadata" = "neznámé"; /* Button text to acknowledge that .kmp file could not be read */ "button-keyboard-file-unreadable" = "OK"; @@ -50,22 +50,25 @@ "button-download-complete" = "Hotovo"; /* keyboards label in the Package Information window */ -"keyboards-label" = "Keyboards:"; +"keyboards-label" = "Klávesnice:"; /* fonts label in the Package Information window */ -"fonts-label" = "Fonts:"; +"fonts-label" = "Písma:"; /* package version label in the Package Information window */ -"package-version-label" = "Package Version:"; +"package-version-label" = "Verze balíčku:"; /* author label in the Package Information window */ -"author-label" = "Author:"; +"author-label" = "Autor:"; /* website label in the Package Information window */ -"website-label" = "Website:"; +"website-label" = "Webová stránka:"; /* copyright label in the Package Information window */ -"copyright-label" = "Copyright:"; +"copyright-label" = "Autorská práva:"; /* message displayed to alert user to need grant accessibility permission */ -"privacy-alert-text" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Preferences, Security & Privacy.\nRestart your system."; +"privacy-alert-text" = "Ke správné funkci vyžaduje Keyman zpřístupnění:\n\nPovolte přístup v Předvolbách systému, Zabezpečení a soukromí.\nRestartujte systém."; + +/* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */ +"no-keyboard-configured-menu-placeholder" = "(Žádná klávesnice nenastavena)"; diff --git a/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml b/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml index 0248e5185b8..d8ae71a33b6 100644 --- a/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml +++ b/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml @@ -47,7 +47,7 @@ - Add/remove language... + Přidat/odebrat jazyk... @@ -71,15 +71,15 @@ - Uninstall + Odinstalovat - Enable + Povolit - Disable + Zakázat @@ -191,7 +191,7 @@ - Add/remove language + Přidat/odebrat jazyk @@ -243,7 +243,7 @@ - Keyboard options... + Možnosti klávesnice... @@ -361,7 +361,7 @@ - (no hotkey) + (žádná klávesová zkratka) @@ -610,13 +610,16 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky - Klíč Počáteční klíč + + Spustit %1$s + + Ukončit @@ -658,7 +661,6 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky - Klíč @@ -782,7 +784,6 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky - Klíč @@ -905,6 +906,10 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky Vyberte klávesnici klávesnice pro nalezení souvisejících písma. + + + + Pro klávesnici %1$s nebyla nalezena žádná písma. diff --git a/windows/src/desktop/setup/locale/cs-CZ/strings.xml b/windows/src/desktop/setup/locale/cs-CZ/strings.xml index c5b42a648a4..196dc26a818 100644 --- a/windows/src/desktop/setup/locale/cs-CZ/strings.xml +++ b/windows/src/desktop/setup/locale/cs-CZ/strings.xml @@ -72,9 +72,7 @@ Restartovat nyní? Stahování %0:s Stahování %0:s - $APPNAME Instalace se nemohla připojit k keyman.com a stáhnout další zdroje. - -Zkontrolujte, zda jste online, a povolte $APPNAME nastavení přístupu k Internetu v nastavení firewall. - -Kliknutím na tlačítko Zrušit instalaci, zkuste znovu stáhnout zdroje znovu, nebo ignorujte pro pokračování v režimu offline. + $APPNAME Nastavení se nepodařilo připojit ke keyman.com a stáhnout další zdroje. + Zkontrolujte, zda jste online a zda jste v nastavení brány firewall udělili aplikaci $APPNAME Setup oprávnění k přístupu na internet. + Kliknutím na Přerušit ukončíte nastavení, kliknutím na Zkusit znovu se pokusíte znovu stáhnout prostředky nebo kliknutím na Ignorovat budete pokračovat v offline režimu. From dc265debd85252eb132e7279ceee9296dafe992b Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 29 Aug 2024 15:00:10 +0700 Subject: [PATCH 186/262] docs(web): more adjustments based on review comments --- .../keyman-touch-layout-file.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts index 7ea749906bc..e24fd295c84 100644 --- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts @@ -7,9 +7,9 @@ // writing // -/** - * On screen keyboard description consisting of specific layouts for tablet, phone, - * and desktop. Despite its name, this format is used for both touch layouts and +/** + * On screen keyboard description consisting of specific layouts for tablet, phone, + * and desktop. Despite its name, this format is used for both touch layouts and * hardware-style layouts. */ export interface TouchLayoutFile { @@ -76,8 +76,8 @@ export interface TouchLayoutKey { id?: TouchLayoutKeyId; /** text to display on key cap */ text?: string; - /** - * the modifier combination (not layer) that should be used in key events, + /** + * the modifier combination (not layer) that should be used in key events, * for this key, overriding the layer that the key is a part of. */ layer?: TouchLayoutLayerId; @@ -103,7 +103,7 @@ export interface TouchLayoutKey { hint?: string; }; -/** special keys like framekeys, deadkeys, blank, etc. */ +/** key type like regular key, framekeys, deadkeys, blank, etc. */ export const enum TouchLayoutKeySp { normal=0, /** A 'frame' key, such as Shift or Enter, which is styled accordingly; uses @@ -135,19 +135,22 @@ export type TouchLayoutKeyWidth = number; // 0-100000 /** defines a subkey */ export interface TouchLayoutSubKey { - /** key id: used to find key in VKDictionary */ + /** key id: used to find key in VKDictionary, or a standard key from the K_ enumeration */ id: TouchLayoutKeyId; /** text to display on key cap */ text?: string; - /** current layer */ + /** + * the modifier combination (not layer) that should be used in key events, + * for this key, overriding the layer that the key is a part of. + */ layer?: TouchLayoutLayerId; - /** next layer */ + /** the next layer to switch to after this key is pressed */ nextlayer?: TouchLayoutLayerId; /** font */ font?: TouchLayoutFont; /** fontsize */ fontsize?: TouchLayoutFontSize; - /** special key */ + /** the type of key */ sp?: TouchLayoutKeySp; /** padding */ pad?: TouchLayoutKeyPad; @@ -161,18 +164,18 @@ export interface TouchLayoutSubKey { export interface TouchLayoutFlick { /** flick up (north) */ n?: TouchLayoutSubKey; - /** flick down */ + /** flick down (south) */ s?: TouchLayoutSubKey; - /** flick right */ + /** flick right (east) */ e?: TouchLayoutSubKey; - /** flick left */ + /** flick left (west) */ w?: TouchLayoutSubKey; - /** flick up-right */ + /** flick up-right (north-east) */ ne?: TouchLayoutSubKey; - /** flick up-left */ + /** flick up-left (north-west) */ nw?: TouchLayoutSubKey; - /** flick down-right */ + /** flick down-right (south-east) */ se?: TouchLayoutSubKey; - /** flick down-left */ + /** flick down-left (south-west) */ sw?: TouchLayoutSubKey; }; From 569a8a8a49f14c6153d688dd39787c1780b1b7d3 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Thu, 29 Aug 2024 15:38:00 +0700 Subject: [PATCH 187/262] fix(mac): eliminate all use of deprecated API beginSheetModalForWindow beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: was deprecated in macOS 10.10 and replaced by beginSheetModalForWindow:completionHandler: The newer API automatically closes the sheet. Explicitly closing the sheet also seemed to be causing the parent windows to close. Fixes: #9308 --- .../KMConfigurationWindowController.m | 43 +++++++++++-------- .../KMInfoWindow/KMInfoWindowController.m | 2 - .../Keyman4MacIM/KMInputMethodAppDelegate.m | 4 +- .../Keyman4MacIM/KMPackageReader.m | 7 --- mac/Keyman4MacIM/Keyman4MacIM/KeySender.m | 11 ++--- .../KME/OnScreenKeyboard/KeyView.m | 8 +++- .../KME/OnScreenKeyboard/OSKKey.m | 5 ++- .../KME/OnScreenKeyboard/OSKView.m | 2 +- 8 files changed, 43 insertions(+), 39 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 3e2ffe24100..4a5f194241b 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -398,8 +398,8 @@ - (void)helpAction:(id)sender { } - (void)removeAction:(id)sender { - NSButton *removeButton = (NSButton *)sender; - NSDictionary *info = [self.tableContents objectAtIndex:removeButton.tag]; + NSButton *deleteButton = (NSButton *)sender; + NSDictionary *info = [self.tableContents objectAtIndex:deleteButton.tag]; NSString *deleteKeyboardMessage = NSLocalizedString(@"message-confirm-delete-keyboard", nil); if ([info objectForKey:@"HeaderTitle"] != nil) @@ -407,10 +407,13 @@ - (void)removeAction:(id)sender { else [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, [info objectForKey:kKMKeyboardNameKey]]]; - [self.deleteAlertView beginSheetModalForWindow:self.window - modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) - contextInfo:(__bridge void *)([NSNumber numberWithInteger:removeButton.tag])]; + [self.deleteAlertView beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSAlertFirstButtonReturn) { + os_log_debug([KMLogs uiLog], "confirm delete keyboard alert dismissed"); + [self deleteFileAtIndex:[NSNumber numberWithInteger:deleteButton.tag]]; + self.deleteAlertView = nil; + } + }]; } - (IBAction)downloadAction:(id)sender { @@ -438,25 +441,29 @@ - (IBAction)useVerboseLoggingCheckBoxAction:(id)sender { } - (void)handleRequestToInstallPackage:(KMPackage *) package { + os_log_debug([KMLogs dataLog], "handleRequestToInstallPackage"); NSString *keyboardInfoString = NSLocalizedString(@"info-install-keyboard-filename", nil); [self.confirmKmpInstallAlertView setInformativeText:[NSString localizedStringWithFormat:keyboardInfoString, package.getOrigKmpFilename]]; os_log_debug([KMLogs uiLog], "Asking user to confirm installation of %{public}@, KMP - temp file name: %{public}@", package.getOrigKmpFilename, package.getTempKmpFilename); - [self.confirmKmpInstallAlertView beginSheetModalForWindow:self.window - modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) - contextInfo:(__bridge void *)(package)]; + [self.confirmKmpInstallAlertView beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { + os_log_debug([KMLogs uiLog], "confirm keyboard installation alert dismissed"); + if (returnCode == NSAlertFirstButtonReturn) { + [self installPackageFile: package.getTempKmpFilename]; + self.confirmKmpInstallAlertView = nil; + } + }]; } - (void)installPackageFile:(NSString *)kmpFile { // kmpFile could be a temp file (in fact, it always is!), so don't display the name. - - os_log_debug([KMLogs dataLog], "KMP - Ready to unzip/install Package File: %{public}@", kmpFile); + os_log_debug([KMLogs dataLog], "kmpFile - ready to unzip/install Package File: %{public}@", kmpFile); BOOL didUnzip = [self.AppDelegate unzipFile:kmpFile]; if (!didUnzip) { + os_log_debug([KMLogs dataLog], "kmpFile, unzipFile failed"); NSAlert *failure = [[NSAlert alloc] init]; [failure addButtonWithTitle:NSLocalizedString(@"button-keyboard-file-unreadable", @"Alert button")]; @@ -465,13 +472,12 @@ - (void)installPackageFile:(NSString *)kmpFile { [failure setIcon:[[NSBundle mainBundle] imageForResource:@"logo.png"]]; [failure setAlertStyle:NSAlertStyleWarning]; - [failure beginSheetModalForWindow:self.window - modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) - contextInfo:nil]; + [failure beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { + os_log_debug([KMLogs uiLog], "kmpFile, keyboard file unreadable alert dismissed with returnCode: %ld", (long)returnCode); + }]; } else { - os_log_debug([KMLogs dataLog], "Completed installation of KMP file."); + os_log_debug([KMLogs dataLog], "kmpFile, completed installation of KMP file"); } } @@ -547,6 +553,7 @@ - (NSAlert *)confirmKmpInstallAlertView { return _confirmKmpInstallAlertView; } +/* - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { os_log_debug([KMLogs uiLog], "User responded to NSAlert"); if (alert == _deleteAlertView) { @@ -568,7 +575,7 @@ - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInf } // else, just a message - nothing to do. } - +*/ - (void)deleteFileAtIndex:(NSNumber *) n { NSInteger index = [n integerValue]; NSString *path2Remove = nil; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m index 6be446ddd28..32a8377b64d 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m @@ -85,8 +85,6 @@ - (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)ta // TODO: refactor: any reason for this to be HTML? hard to read stringWithFormat applied to template with 16 arguments - (NSString *)detailsHtml { - NSString *errorString = NSLocalizedString(@"message-keyboard-file-unreadable", nil); - @try { NSString *htmlFormat = @"" diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 0482f784328..d95982687b3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -1340,7 +1340,9 @@ - (BOOL)unzipFile:(NSString *)filePath { NSError *error = nil; NSString *fileName = filePath.lastPathComponent; NSString *folderName = [fileName stringByDeletingPathExtension]; - + + os_log_debug([KMLogs keyboardLog], "unzipFile for filePath: %{public}@", filePath); + // First we unzip into a temp folder, and check kmp.json for the fileVersion // before we continue installation. We don't want to overwrite existing // package if it is there if the files are not compatible with the installed diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m index 7406488a8a0..6ee4402cb9a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -251,14 +251,12 @@ - (KMPackageInfo *) loadPackageInfoFromInfFile:(NSString *)path { NSString *s = [line substringFromIndex:kName.length+1]; NSArray *vs = [s componentsSeparatedByString:@"\","]; NSString *v1 = [[vs objectAtIndex:0] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - NSString *v2 = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; builder.packageName = v1; } else if ([[line lowercaseString] hasPrefix:[kVersion lowercaseString]]) { NSString *s = [line substringFromIndex:kVersion.length+1]; NSArray *vs = [s componentsSeparatedByString:@"\","]; NSString *v1 = [[vs objectAtIndex:0] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - NSString *v2 = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; builder.packageVersion = v1; } else if ([[line lowercaseString] hasPrefix:[kAuthor lowercaseString]]) { @@ -273,14 +271,12 @@ - (KMPackageInfo *) loadPackageInfoFromInfFile:(NSString *)path { NSString *s = [line substringFromIndex:kCopyright.length+1]; NSArray *vs = [s componentsSeparatedByString:@"\","]; NSString *v1 = [[vs objectAtIndex:0] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - NSString *v2 = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; builder.copyright = v1; } else if ([[line lowercaseString] hasPrefix:[kWebSite lowercaseString]]) { NSString *s = [line substringFromIndex:kWebSite.length+1]; NSArray *vs = [s componentsSeparatedByString:@"\","]; NSString *v1 = [[vs objectAtIndex:0] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - NSString *v2 = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; builder.website = v1; } @@ -294,20 +290,17 @@ - (KMPackageInfo *) loadPackageInfoFromInfFile:(NSString *)path { NSString *s = [line substringFromIndex:x+2]; if ([[s lowercaseString] hasPrefix:[kFile lowercaseString]]) { NSArray *vs = [s componentsSeparatedByString:@"\","]; - NSString *v1 = [[[vs objectAtIndex:0] substringFromIndex:kFile.length+1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; NSString *fileName = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; [files addObject:fileName]; } else if ([[s lowercaseString] hasPrefix:[kFont lowercaseString]]) { NSArray *vs = [s componentsSeparatedByString:@"\","]; NSString *fontName = [[[vs objectAtIndex:0] substringFromIndex:kFont.length+1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - NSString *fontFileName = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; [fontArray addObject:fontName]; } else if ([[s lowercaseString] hasPrefix:[kKeyboard lowercaseString]]) { NSArray *vs = [s componentsSeparatedByString:@"\","]; NSString *keyboardName = [[[vs objectAtIndex:0] substringFromIndex:kKeyboard.length+1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - NSString *keyboardFileName = [[vs objectAtIndex:1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; KMKeyboardInfoBuilder *builder = [[KMKeyboardInfoBuilder alloc] init]; builder.name = keyboardName; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m index 417475c777d..dc73edc11bd 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m @@ -1,9 +1,6 @@ /** * Keyman is copyright (C) SIL International. MIT License. * - * KeySender.m - * Keyman - * * Created by Shawn Schantz on 2023-04-17. * * Sends keydown events for the provided keycode to the frontmost application. @@ -56,15 +53,13 @@ - (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) v } /** - sendKeymanKeyCodeForEvent sends the kKeymanEventKeyCode to the - frontmost application to indicate that all the backspaces have been processed - and we can insert the queuedText to the client + * sendKeymanKeyCodeForEvent sends the kKeymanEventKeyCode to the + * frontmost application to indicate that all the backspaces have been processed + * and we can insert the queuedText to the client */ - (void)sendKeymanKeyCodeForEvent:(NSEvent *)event { os_log_debug([KMLogs keyLog], "KeySender sendKeymanKeyCodeForEvent"); - - ProcessSerialNumber psn; // Returns the frontmost app, which is the app that receives key events. NSRunningApplication *app = NSWorkspace.sharedWorkspace.frontmostApplication; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m index c1dfa75a87a..9dfd39856bc 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m @@ -41,7 +41,10 @@ @implementation KeyView - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { - os_log_debug([KMELogs oskLog], "KeyView initWithFrame: %{public}@, bounds: %{public}@, default clipsToBounds %{public}@", NSStringFromRect(frame), NSStringFromRect(self.bounds), self.clipsToBounds?@"YES":@"NO"); + /* + // usually too much, but uncomment for debugging + os_log_debug([KMELogs oskLog], "KeyView initWithFrame: %{public}@, bounds: %{public}@, default clipsToBounds %{public}@", NSStringFromRect(frame), NSStringFromRect(self.bounds), self.clipsToBounds?@"YES":@"NO"); + */ self.clipsToBounds = true; CGSize size = frame.size; CGFloat x = size.width*0.05; @@ -70,7 +73,10 @@ - (id)initWithFrame:(NSRect)frame { } - (void)drawRect:(NSRect)rect { + /* + // usually too much, but uncomment for debugging os_log_debug([KMELogs uiLog], "KeyView drawRect: %{public}@, bounds: %{public}@, keyCode: 0x%lx, caption: %{public}@, label: %{public}@", NSStringFromRect(rect), NSStringFromRect(self.bounds), self.keyCode, self.caption.stringValue, self.label.stringValue); + */ [[self getOpaqueColorWithRed:241 green:242 blue:242] setFill]; NSRectFillUsingOperation(rect, NSCompositingOperationSourceOver); diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKKey.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKKey.m index d81097d3e2f..7ff40ccffea 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKKey.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKKey.m @@ -14,7 +14,10 @@ @implementation OSKKey - (id)initWithKeyCode:(NSUInteger)keyCode caption:(NSString *)caption scale:(CGFloat)scale { self = [super init]; if (self) { - os_log_debug([KMELogs oskLog], "OSKKey initWithKeyCode: 0x%lx, caption: %{public}@, scale: %f", keyCode, caption, scale); + /* + // usually too much, but uncomment for debugging + os_log_debug([KMELogs oskLog], "OSKKey initWithKeyCode: 0x%lx, caption: %{public}@, scale: %f", keyCode, caption, scale); + */ _keyCode = keyCode; if (caption == nil) diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m index f5154fa76da..512920e0528 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m @@ -43,7 +43,7 @@ - (id)initWithFrame:(NSRect)frame { } - (void)drawRect:(NSRect)rect { - os_log_debug([KMELogs uiLog], "OSKView drawRect: %{public}@", NSStringFromRect(rect)); + os_log_debug([KMELogs oskLog], "OSKView drawRect: %{public}@", NSStringFromRect(rect)); CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] CGContext]; CGContextSetLineJoin(context, kCGLineJoinRound); From c8beeb9ab405fb41b96862bccd103f9cf528644a Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 29 Aug 2024 10:56:17 +0100 Subject: [PATCH 188/262] fix(developer): add two additional space test cases for index() in GetXStringImpl_type_i test --- .../kmcmplib/tests/gtest-compiler-test.cpp | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index aab01f8131c..07d08ad156e 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1091,34 +1091,45 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) { fileKeyboard.dpStoreArray = file_store; u16cpy(str, u"index(b,4)"); EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - const KMX_WCHAR tstr_index_comma_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 }; - EXPECT_EQ(0, u16cmp(tstr_index_comma_valid, tstr)); + const KMX_WCHAR tstr_index_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 }; + EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr)); // index, space before store, comma, valid fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; u16cpy(str, u"index( b,4)"); EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - const KMX_WCHAR tstr_index_initial_space_and_comma_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 }; - EXPECT_EQ(0, u16cmp(tstr_index_initial_space_and_comma_valid, tstr)); + EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr)); + + // index, space after store, comma, valid + fileKeyboard.cxStoreArray = 3u; + fileKeyboard.dpStoreArray = file_store; + u16cpy(str, u"index(b ,4)"); + EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr)); // index, comma and space, valid fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; u16cpy(str, u"index(b, 4)"); EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - const KMX_WCHAR tstr_index_comma_and_space_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 }; - EXPECT_EQ(0, u16cmp(tstr_index_comma_and_space_valid, tstr)); + EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr)); + + // index, comma, space after offset, valid + fileKeyboard.cxStoreArray = 3u; + fileKeyboard.dpStoreArray = file_store; + u16cpy(str, u"index(b,4 )"); + EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr)); // index, space, valid ... should not be valid (see issue #11833) u16cpy(str, u"index(b 4)"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - const KMX_WCHAR tstr_index_space_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 4, 0 }; - EXPECT_EQ(0, u16cmp(tstr_index_space_valid, tstr)); + EXPECT_EQ(0, u16cmp(tstr_index_valid, tstr)); - // index, two-digit parameter, valid + // index, two-digit offset, valid u16cpy(str, u"index(b,42)"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; @@ -1126,25 +1137,25 @@ TEST_F(CompilerTest, GetXStringImpl_type_i_test) { const KMX_WCHAR tstr_index_two_digit_valid[] = { UC_SENTINEL, CODE_INDEX, 2, 42, 0 }; EXPECT_EQ(0, u16cmp(tstr_index_two_digit_valid, tstr)); - // index, comma, non-digit parameter, KmnCompilerMessages::ERROR_InvalidIndex + // index, comma, non-digit offset, KmnCompilerMessages::ERROR_InvalidIndex u16cpy(str, u"index(b,g)"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - // index, comma, no parameter, KmnCompilerMessages::ERROR_InvalidIndex + // index, comma, no offset, KmnCompilerMessages::ERROR_InvalidIndex u16cpy(str, u"index(b,)"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - // index, space and comma, no parameter, KmnCompilerMessages::ERROR_InvalidIndex + // index, space and comma, no offset, KmnCompilerMessages::ERROR_InvalidIndex u16cpy(str, u"index(b ,)"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); - // index, comma, no parameter but space, KmnCompilerMessages::ERROR_InvalidIndex + // index, comma, no offset but space, KmnCompilerMessages::ERROR_InvalidIndex u16cpy(str, u"index(b, )"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; From 028a03c998f1a781816ee9ff71c2415a720457c7 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 29 Aug 2024 22:22:12 +1000 Subject: [PATCH 189/262] feat(windows): add sil global strings --- oem/firstvoices/windows/src/xml/keyman_support.xsl | 12 ++++++++++-- oem/firstvoices/windows/src/xml/strings.xml | 10 ++++++++++ windows/src/desktop/kmshell/xml/keyman_support.xsl | 12 ++++++++++-- windows/src/desktop/kmshell/xml/strings.xml | 10 ++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/oem/firstvoices/windows/src/xml/keyman_support.xsl b/oem/firstvoices/windows/src/xml/keyman_support.xsl index dfd8c8445bd..81f72fdb66f 100644 --- a/oem/firstvoices/windows/src/xml/keyman_support.xsl +++ b/oem/firstvoices/windows/src/xml/keyman_support.xsl @@ -32,11 +32,19 @@

- + + + + +
- + + + + +