diff --git a/.github/labeler.yml b/.github/labeler.yml index 5250e32193..19c858cc81 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -20,10 +20,8 @@ common/: - common/** - resources/** -common/models/: common/models/** -common/models/types/: common/models/types/** -common/models/templates/: common/models/templates/** -common/models/wordbreakers/: common/models/wordbreakers/** +common/models/templates/: web/src/engine/predictive-text/templates/** +common/models/wordbreakers/: web/src/engine/predictive-text/wordbreakers/** common/resources/: resources/** common/web/: common/web/** diff --git a/.gitignore b/.gitignore index 8534d172a9..772784fdb8 100644 --- a/.gitignore +++ b/.gitignore @@ -183,4 +183,3 @@ lcov.info /keyman*.buildinfo /keyman*.changes /keyman*.tar.?z - diff --git a/HISTORY.md b/HISTORY.md index 7bb236717b..5a962d0012 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,121 @@ # Keyman Version History +## 18.0.110 alpha 2024-09-12 + +* chore(common): Update to Unicode 16.0 (#12393) +* refactor(web): move `common/web/es-bundling` → `web/src/tools/es-bundling` (#12389) +* refactor(web): move `common/web/eslint` → `common/tools/eslint` (#12390) +* refactor(web): move sentry-manager → `web/src/engine/sentry-manager` (#12397) +* refactor(web): merge `device-detect` with `web/src/engine/main` (#12399) +* chore(web): allow to run unit tests in vscode test explorer (#12400) +* fix(developer): index() requires comma between parameters in kmcmplib compiler (#12328) + +## 18.0.109 alpha 2024-09-11 + +* chore(common): Update history with 17.0.329 stable (#12394) +* refactor(web): move `model/templates` to `web/src/engine/predictive/text` (#12382) +* refactor(web): move `common/models` to `web/src/engine/predictive-text` (#12383) +* refactor(web): move `common/web/utils` to `web/src/engine/common/web-utils/` (#12384) + +## 18.0.108 alpha 2024-09-10 + +* docs(android): Update help docs (#12367) +* fix(developer): fix building with Ubuntu 24.04 (#12379) +* refactor(android): Move build-publish.sh to builder script (#12351) +* fix(android): Separate `publishSentry` Gradle task to publish symbols to Sentry (#12358) +* refactor(web): move `model/types` to `web/types` (#12370) + +## 18.0.107 alpha 2024-09-09 + +* fix(android): Update Text Size menu icons for RTL support (#12290) + +## 18.0.106 alpha 2024-09-06 + +* feat(windows): add right modifier included in hotkey optional functionality (#12259) +* fix(android): Skip language counts for lexical-model packages (#12361) +* docs(web): fix link to documentation page (#12369) + +## 18.0.105 alpha 2024-09-05 + +* chore(common): Fix missing entries in HISTORY.md (#12352) +* docs(android): Add in-app help for adjusting longpress delay (#12359) +* fix(mac): avoid crash on startup with macOS 10.15 (Catalina) (#12354) +* chore(oem/fv): Update to fv_all 13.0 (#12362) +* feat(windows): Add two new strings for SIL Global name instead of SIL International (#12327) + +## 18.0.104 alpha 2024-09-03 + +* fix(mac): display package info after keyboard installation (#12326) + +## 18.0.103 alpha 2024-09-02 + +* feat(windows): Remove hotkey related feature flags (#12252) +* feat(windows): update SIL logo for Windows UI (#12250) + +## 18.0.102 alpha 2024-08-30 + +* docs(web): add documentation comments for touch layout interfaces (#12314) +* change(mac): store data in Library directory instead of Documents (#12106) +* change(mac): store partial path in UserDefaults (#12144) + +## 18.0.101 alpha 2024-08-29 + +* refactor(web): move `gesture-recognizer` → `gesture-processor` (#12194) +* docs(core): fix a typo in the KMX+ doc (#12302) +* chore(web): remove obsolete comment (#12304) +* chore(android,ios): Update FirstVoices keyboards to fv_all.kmp 12.15 (#12300) +* fix(developer): make LDML import path consistent for all bundlings of kmc (#12280) +* fix(core): properly support 'other' modifier state with `uint32_t` type (#12281) +* chore(windows): fix typo in environment.inc.sh (#12286) +* fix(android): Check in material-stepper as internal Maven dependency (#12267) +* docs(core): improve formatting of KMX+ doc (#12303) +* fix(android): Prioritize certain actions over multi-line for ENTER key (#12315) +* fix(linux): add `keymanFacename` to .ldml file (#12277) +* chore(common): Update crowdin strings for Czech (#12316) +* fix(web): prevent unintuitive space-output blocking for mid-context suggestions (#12313) + +## 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) +* feat(web): optimize the wordbreaker data table for filesize and ease of first-load parsing (#10692) +* fix(web): fixes wordbreaker test import path (#12297) +* feat(web): enable utf8 charset encoding for the build artifacts (#12115) + +## 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) +* 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) +* 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) + ## 18.0.94 alpha 2024-08-21 * fix(core): look for `emcc` instead of `emcc.py` (#12235) @@ -12,11 +128,11 @@ ## 18.0.93 alpha 2024-08-20 -* (#12188) +* refactor(web): remove engine/interfaces dependency on engine/js-processor (#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) +* fix(core): set mac build version for meson cli build to 10.13 (#12223) ## 18.0.92 alpha 2024-08-19 @@ -672,6 +788,29 @@ * chore(common): move to 18.0 alpha (#10713) * chore: move to 18.0 alpha +## 17.0.329 stable 2024-09-09 + +* chore(android,ios): Add ojibwa ifinal/rdot keyboards to FirstVoices (#12020) +* change(web): revert #11174, which loads keyboards before initializing the OSK (#12040) +* fix(web): unrevert #11258, leaving OSK hidden before instructed to display (#12058) +* chore(common): use `nvm` to select version of node for builds (#12074) +* fix(developer): ignore scan code if zero in debugger (#12182) +* fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler (#12206) +* fix(developer): enforce presence of kps Info.Description field in info compilers (#12207) +* fix(web): disable fat-finger data use when mayCorrect = false (#12226) +* chore(common): allow build agents to automatically select emsdk version, and enable support for 3.1.60+ (#12245) +* fix(web): fix documentation-keyboard spacebar-text scaling (#12240) +* fix(core): set mac build version for meson cli build to 10.13 (#12246) +* change(ios): defer registration of fonts past initialization (#12241) +* chore(android,ios): Update FirstVoices keyboards to 12.15 (#12301) +* fix(core): properly support 'other' modifier state with `uint32_t` type (#12285) +* fix(developer): find last matching key in LDML key bag when building KVK (#12284) +* fix(android): check in material-stepper as internal Maven dependency (#12324) +* fix(linux): add `keymanFacename` to .ldml file (#12283) +* chore(oem/fv): Update to fv_all 13.0 (#12363) +* fix(mac): avoid crash on startup with macOS 10.15 (Catalina) (#12364) +* fix(android): skip language counts for lexical-model packages (#12368) + ## 17.0.328 stable 2024-07-27 * fix(web): add nullish test in setOsk (#12041) diff --git a/VERSION.md b/VERSION.md index a41e5425c0..5cf92dc732 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.95 \ No newline at end of file +18.0.111 \ No newline at end of file diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index b1f2d240a7..5614fcae6a 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -2,8 +2,10 @@ buildscript { repositories { google() - jcenter() mavenCentral() + flatDir { + dirs "kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/" + } } dependencies { classpath 'com.android.tools.build:gradle:7.4.2' @@ -11,16 +13,16 @@ 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' + classpath 'com.stepstone.stepper:material-stepper:4.3.1@aar' } } allprojects { repositories { + maven { url uri("${projectDir}/libs") } google() - jcenter() mavenCentral() maven { url "https://jitpack.io" } } diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh index e9a5106210..1eafe1e667 100755 --- a/android/KMAPro/build.sh +++ b/android/KMAPro/build.sh @@ -10,6 +10,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/build-help.inc.sh" . "$KEYMAN_ROOT/resources/build/build-download-resources.sh" +. "$KEYMAN_ROOT/android/KMAPro/build-play-store-notes.inc.sh" + # ################################ Main script ################################ # Definition of global compile constants @@ -25,6 +27,7 @@ builder_describe "Builds Keyman for Android app." \ "configure" \ "build" \ "test Runs lint and unit tests." \ + "publish Publishes symbols to Sentry and the APK to the Play Store." \ "--ci Don't start the Gradle daemon. For CI" \ "--upload-sentry Upload to sentry" @@ -109,3 +112,13 @@ if builder_start_action test; then builder_finish_action success test fi + +if builder_start_action publish; then + # Copy Release Notes + generateReleaseNotes + + # Publish symbols and Keyman for Android to Play Store + ./gradlew $DAEMON_FLAG publishSentry publishReleaseApk + + builder_finish_action success publish +fi diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index 7eec37bcef..11ec4efd2d 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -105,12 +105,17 @@ android { } // how to configure the sentry android gradle plugin -sentry { - // Disables or enables the automatic configuration of Native symbols - uploadNativeSymbols = true +task publishSentry { + doLast { + println 'Publishing Keyman symbols to Sentry' + sentry { + // Disables or enables the automatic configuration of Native symbols + uploadNativeSymbols = true - // Does or doesn't include the source code of native code for Sentry - includeNativeSources = true + // Does or doesn't include the source code of native code for Sentry + includeNativeSources = true + } + } } String env_keys_json_file = System.getenv("keys_json_file") @@ -145,7 +150,6 @@ repositories { dirs 'libs' } google() - 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 0000000000..895d772c39 Binary files /dev/null and b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-javadoc.jar differ 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 0000000000..a3c4148387 Binary files /dev/null and b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1-sources.jar differ 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 0000000000..9bc9a3f05c Binary files /dev/null and b/android/KMAPro/kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/material-stepper-4.3.1.aar differ 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 0000000000..5bd1fc5d39 --- /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 + + + 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 2ecf62fe09..e79cb917ff 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 @@ -31,8 +31,6 @@ 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 currentDelayTimeMS = KMManager.KMDefault_LongpressDelay; // ms - private static int minLongpressTime = 300; // ms - private static int maxLongpressTime = 1500; // ms private static int delayTimeIncrement = 200; // ms /** @@ -112,7 +110,7 @@ public class AdjustLongpressDelayActivity extends BaseActivity { findViewById(R.id.delayTimeDownButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (currentDelayTimeMS > minLongpressTime) { + if (currentDelayTimeMS > KMManager.KMMinimum_LongpressDelay) { currentDelayTimeMS -= delayTimeIncrement; seekBar.setProgress(delayTimeToProgress()); } @@ -122,7 +120,7 @@ public class AdjustLongpressDelayActivity extends BaseActivity { findViewById(R.id.delayTimeUpButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (currentDelayTimeMS < maxLongpressTime) { + if (currentDelayTimeMS < KMManager.KMMaximum_LongpressDelay) { currentDelayTimeMS += delayTimeIncrement; seekBar.setProgress(delayTimeToProgress()); } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java index 3806d5f4b8..161d966308 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java @@ -112,7 +112,8 @@ public class PackageActivity extends AppCompatActivity implements // Number of languages associated with the first keyboard in a keyboard package. // lexical-model packages will be 0 - final int languageCount = kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0); + final int languageCount = (keyboardCount > 0) ? + kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0) : 0; // Sanity check for keyboard packages if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) { 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 3809ef2353..9c8e3354e6 100644 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_textsize.png differ 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 0000000000..c5d555caca Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/blank_osk.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png new file mode 100644 index 0000000000..6764f528f2 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-hdpi/ic_light_action_textsize.png differ 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 0000000000..3e41a359a0 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-land-hdpi/blank_osk.png differ 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 0000000000..a31690d127 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-mdpi/ic_light_action_textsize.png differ 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 0000000000..282606a9d5 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp-land/blank_osk.png differ 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 0000000000..8246a64168 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw600dp/blank_osk.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp-land/blank_osk.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp-land/blank_osk.png new file mode 100644 index 0000000000..740c8559c9 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp-land/blank_osk.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp/blank_osk.png b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp/blank_osk.png new file mode 100644 index 0000000000..0e3343bbd1 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-sw720dp/blank_osk.png differ 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 0000000000..ca9829d178 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xhdpi/ic_light_action_textsize.png differ 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 0000000000..d15adad0ac Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-ldrtl-xxhdpi/ic_light_action_textsize.png differ 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 0000000000..3e8ea253f7 --- /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 0000000000..b51b51139b --- /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/activity_adjust_longpress_delay.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml index ed0bed54a9..ef1ebd613b 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" /> 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 3b71e87178..314a503412 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"> - + + + + + + android:src="@drawable/textsize_decrease" /> + android:src="@drawable/textsize_increase" /> 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 bec7931da8..97da3115df 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/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml index b3d15fc631..a475ae3725 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml @@ -1,5 +1,6 @@ + Condividi @@ -32,6 +33,8 @@ Dimensione testo in alto Dimensione del testo giù + + Cursore dimensione testo \nTutto il testo sarà cancellato\n @@ -50,6 +53,7 @@ Per installare pacchetti di tastiera, consentire a Keyman di leggere la memoria esterna. La richiesta di autorizzazione all\'archiviazione è stata negata. Potrebbe non essere stato possibile installare il pacchetto tastiera + Richiesta di autorizzazione archiviazione non riuscita. Prova Impostazioni Keyman - Installa da file locale Impostazioni @@ -64,6 +68,8 @@ Regola altezza tastiera + Adjust longpress delay + Didascalia barra spaziatrice Keyboard @@ -82,7 +88,7 @@ Non mostrare la didascalia sulla barra spaziatrice - Vibrate when typing + Vibra durante la digitazione Mostra sempre il banner @@ -119,6 +125,14 @@ Ruota il dispositivo per regolare verticale e orizzontale Ripristina impostazioni predefinite + + Delay Time: %1$.1f seconds + + Delay time longer + + Delay time shorter + + Longpress delay time slider Cerca o digita URL @@ -154,7 +168,7 @@ Metadati non validi/mancanti nel pacchetto - Keyboard requires a newer version of Keyman + La tastiera richiede una nuova versione di Keyman - Unable to launch web browser + Impossibile avviare il browser web diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 62d396aae0..5e5d724123 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'); } } 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 b921ac6c0a..592f4be2a1 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 @@ -310,8 +310,11 @@ public final class KMManager { public static final String KMDefault_DictionaryVersion = "0.1.4"; public static final String KMDefault_DictionaryKMP = KMDefault_DictionaryPackageID + FileUtils.MODELPACKAGE; - // Default KeymanWeb longpress delay in milliseconds + // Default KeymanWeb longpress delay constants in milliseconds public static final int KMDefault_LongpressDelay = 500; + public static final int KMMinimum_LongpressDelay = 300; + public static final int KMMaximum_LongpressDelay = 1500; + // Keyman files protected static final String KMFilename_KeyboardHtml = "keyboard.html"; @@ -1274,17 +1277,13 @@ public final class KMManager { /** * 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 +1295,8 @@ public final class KMManager { break; case EditorInfo.IME_ACTION_SEND: - value = EnterModeType.SEND; + value = isMultiLine ? + EnterModeType.NEWLINE :EnterModeType.SEND; break; case EditorInfo.IME_ACTION_NEXT: @@ -1304,7 +1304,8 @@ public final class KMManager { break; case EditorInfo.IME_ACTION_DONE: - value = EnterModeType.DONE; + value = isMultiLine ? + EnterModeType.NEWLINE : EnterModeType.DONE; break; case EditorInfo.IME_ACTION_PREVIOUS: @@ -1312,7 +1313,8 @@ public final class KMManager { break; default: - value = EnterModeType.DEFAULT; + value = isMultiLine ? + EnterModeType.NEWLINE : EnterModeType.DEFAULT; } enterMode = value; @@ -2098,14 +2100,22 @@ public final class KMManager { /** * Set the longpress delay (in milliseconds) as a stored preference. + * Valid range is 300 ms to 1500 ms. Returns true if the preference is successfully stored. * @param longpressDelay - int longpress delay in milliseconds + * @return boolean */ - public static void setLongpressDelay(int longpressDelay) { + public static boolean setLongpressDelay(int longpressDelay) { + if (longpressDelay < KMMinimum_LongpressDelay || longpressDelay > KMMaximum_LongpressDelay) { + return false; + } + 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(); + + return true; } /** 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 1bcc792660..db45a6706e 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/android/KMEA/app/src/main/res/values-it-rIT/strings.xml b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml index 2b9c950cc6..da1e3531a2 100644 --- a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml +++ b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml @@ -45,7 +45,7 @@ Aggiorna - No internet connection + Nessuna connessione internet Impossibile connettersi al server Keyman! @@ -84,7 +84,7 @@ Errore nella tastiera %1$s:%2$s per %3$s lingua. Controllo del dizionario associato da scaricare - Cannot connect to Keyman server to check for associated dictionary to download + Non è possibile connettersi al server Keyman per verificare dizionari associati da scaricare Vuoi scaricare l\'ultima versione di questo dizionario? @@ -107,7 +107,7 @@ Scaricamento del dizionario avviato in background Il dizionario selezionato è già in download; riprova tra un momento! - + Il download del dizionario è terminato. Download non riuscito @@ -119,7 +119,7 @@ "Tutte le risorse sono aggiornate!" Una o più risorse non sono state aggiornate! - + Risorse aggiornate con successo! Versione dizionario @@ -128,11 +128,11 @@ Vuoi eliminare questo dizionario? - Dictionary deleted + Dizionario eliminato %1$s tastiera installata - Keyboard deleted + Tastiera eliminata Abilita correzioni @@ -140,12 +140,12 @@ Dizionario - Dictionary - Dictionaries + Dizionario + Dizionari Controlla il dizionario disponibile - Check for dictionaries online + Controlla i dizionari online Dizionario: %1$s @@ -168,5 +168,5 @@ Tocca qui per cambiare la tastiera - Unable to launch web browser + Impossibile avviare il browser web diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh index 878b54797d..29cb519cf5 100755 --- a/android/KMEA/build.sh +++ b/android/KMEA/build.sh @@ -23,7 +23,7 @@ JUNIT_RESULTS="##teamcity[importData type='junit' path='keyman\android\KMEA\app\ builder_describe "Builds Keyman Engine for Android." \ "@/web/src/app/webview" \ - "@/common/web/sentry-manager" \ + "@/web/src/engine/sentry-manager" \ "clean" \ "configure" \ "build" \ @@ -84,7 +84,7 @@ if builder_start_action build:engine; then cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/keymanweb-osk.ttf" cp "$KEYMAN_ROOT/node_modules/@sentry/browser/build/bundle.min.js" "$ENGINE_ASSETS/sentry.min.js" - cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js" + cp "$KEYMAN_ROOT/web/src/engine/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js" echo "Copying es6-shim polyfill" cp "$KEYMAN_ROOT/node_modules/es6-shim/es6-shim.min.js" "$ENGINE_ASSETS/es6-shim.min.js" diff --git a/android/build-publish.sh b/android/build-publish.sh deleted file mode 100755 index 29c152c993..0000000000 --- a/android/build-publish.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash -# CI script to publish specified app APKs to the Play Store. -# The APKs should already have been built from a separate script - -# set -x: Debugging use, print each statement -# set -x - -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../resources/build/build-utils.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -. "$KEYMAN_ROOT/android/KMAPro/build-play-store-notes.inc.sh" - -echo Publishing APKs to Play Store - -# -# Prevents 'clear' on exit of mingw64 bash shell -# -SHLVL=0 - -display_usage ( ) { - echo "build-publish.sh [-no-daemon] [-kmapro] [-fv]" - echo - echo "Publish app to the Play Store" - echo " -no-daemon Don't start the Gradle daemon. Use for CI" - echo " -kmapro Keyman for Android" - echo " -fv First Voices" - exit 1 -} - -NO_DAEMON=false -DO_KMAPRO=false -DO_FV=false - -# Parse args -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - -no-daemon) - NO_DAEMON=true - ;; - -kmapro) - DO_KMAPRO=true - ;; - -fv) - DO_FV=true - ;; - -h|-\?) - display_usage - ;; - esac - shift # past argument -done - -# Override JAVA_HOME to OpenJDK 11 -set_java_home - -echo -echo "NO_DAEMON: $NO_DAEMON" -echo "DO_KMAPRO: $DO_KMAPRO" -echo "DO_FV: $DO_FV" -echo - -if [ "$NO_DAEMON" = true ]; then - DAEMON_FLAG=--no-daemon -else - DAEMON_FLAG= -fi - -BUILD_FLAGS="publishReleaseApk" -echo "BUILD_FLAGS $BUILD_FLAGS" - -# Publish Keyman for Android -if [ "$DO_KMAPRO" = true ]; then - # Copy Release Notes - generateReleaseNotes - cd "$KEYMAN_ROOT/android/KMAPro/" - ./gradlew $DAEMON_FLAG $BUILD_FLAGS -fi - -# Publish FV app -if [ "$DO_FV" = true ]; then - cd "$KEYMAN_ROOT/oem/firstvoices/android/" - ./gradlew $DAEMON_FLAG $BUILD_FLAGS -fi diff --git a/android/build.sh b/android/build.sh index 88048c7aa3..d876d882d7 100755 --- a/android/build.sh +++ b/android/build.sh @@ -18,7 +18,7 @@ builder_describe \ configure \ build \ test \ - "publish Publishes the APKs to the Play Store." \ + "publish Publishes symbols to Sentry and the APKs to the Play Store." \ --ci+ \ --upload-sentry+ \ ":engine=KMEA Keyman Engine for Android" \ diff --git a/android/help/about/privacy.md b/android/help/about/privacy.md index 6173d6c3c3..c3dbd000ee 100644 --- a/android/help/about/privacy.md +++ b/android/help/about/privacy.md @@ -19,7 +19,7 @@ Keyman does not use user accounts, so we are unable to identify which crash repo ### Developer Information -Keyman is developed and published by SIL International. +Keyman is developed and published by SIL Global. You can contact SIL or provide any feedback using the app store. diff --git a/android/help/about/whatsnew.md b/android/help/about/whatsnew.md index 96bd458a0c..500d0a9593 100644 --- a/android/help/about/whatsnew.md +++ b/android/help/about/whatsnew.md @@ -3,3 +3,7 @@ title: What's New --- Here are some of the new features we have added to Keyman 18.0 for Android: + +* New menu to adjust longpress delay time (#12170, #12185) +* Support localizations for right-to-left languages (#12215) +* Handle additional actions for ENTER key (#12125, #12315) diff --git a/android/help/android_images/ic_timelapse.png b/android/help/android_images/ic_timelapse.png new file mode 100644 index 0000000000..9f948825b2 Binary files /dev/null and b/android/help/android_images/ic_timelapse.png differ diff --git a/android/help/android_images/longpress-slider.png b/android/help/android_images/longpress-slider.png new file mode 100644 index 0000000000..f1ab61f7b3 Binary files /dev/null and b/android/help/android_images/longpress-slider.png differ diff --git a/android/help/android_images/settings-screen-ap.png b/android/help/android_images/settings-screen-ap.png index ec85e24e81..f316fefd73 100644 Binary files a/android/help/android_images/settings-screen-ap.png and b/android/help/android_images/settings-screen-ap.png differ diff --git a/android/help/basic/config/adjust-longpress-delay.md b/android/help/basic/config/adjust-longpress-delay.md new file mode 100644 index 0000000000..0f3e740bdf --- /dev/null +++ b/android/help/basic/config/adjust-longpress-delay.md @@ -0,0 +1,13 @@ +--- +title: Adjust Longpress Delay - Keyman for Android Help +--- + +## Adjust Longpress Delay + +This menu is used to adjust the duration for triggering a longpress gesture. + +Drag the slider to set the delay between 0.3 seconds to 1.5 seconds. + +The default value is 0.5 seconds. + +![](../../android_images/longpress-slider.png) diff --git a/android/help/basic/config/index.md b/android/help/basic/config/index.md index 1c45d27508..4d55e98839 100644 --- a/android/help/basic/config/index.md +++ b/android/help/basic/config/index.md @@ -44,6 +44,11 @@ Click on this to bring up the Android system configuration for setting Keyman as Click on this to [adjust the keyboard height](adjust-keyboard-height). +## Adjust longpress delay +![](../../android_images/ic_timelapse.png) + +Click on this to [adjust the delay time](adjust-longpress-delay) for a longpress gesture. + ## Spacebar Caption Click on this to [change the displayed keyboard name](spacebar-caption) on the spacebar. diff --git a/common/cpp/km_u16.cpp b/common/cpp/km_u16.cpp index 84a6c4a75e..78077202fc 100644 --- a/common/cpp/km_u16.cpp +++ b/common/cpp/km_u16.cpp @@ -5,6 +5,7 @@ */ #include +#include #include "utfcodec.hpp" #include "km_u16.h" @@ -387,3 +388,43 @@ double u16tof(KMX_WCHAR* str16) { std::string str = string_from_u16string(str16); return strtof(str.c_str(), &pEnd); } + +/** + * @brief Trim whitespace from the start (left) of a string + * @param p Pointer to u16string + * @return Pointer to the string modified to remove leading whitespace + */ +KMX_WCHAR* u16ltrim(KMX_WCHAR* p) { + if (p && (u16len(p) > 0)) { + PKMX_WCHAR q = p; + while(iswspace(*q)) q++; + u16cpy(p, q); + } + return p; +} + +/** + * @brief Trim whitespace from the end (right) of a string + * @param p Pointer to u16string + * @return Pointer to the string modified to remove trailing whitespace + */ +KMX_WCHAR* u16rtrim(KMX_WCHAR *p) { + if (p && (u16len(p) > 0)) { + PKMX_WCHAR q = p + u16len(p) - 1; + if (iswspace(*q)) { + while (iswspace(*q) && q > p) q--; + if (!iswspace(*q)) q++; + *q = '\0'; // delete first following whitespace + } + } + return p; +} + +/** + * @brief Trim whitespace from both the start and end of a string + * @param p Pointer to u16string + * @return Pointer to the string modified to remove leading and trailing whitespace + */ +KMX_WCHAR* u16trim(KMX_WCHAR *p) { + return u16rtrim(u16ltrim(p)); +} \ No newline at end of file diff --git a/common/include/km_u16.h b/common/include/km_u16.h index f0c2c2d16c..91df6b0eb9 100644 --- a/common/include/km_u16.h +++ b/common/include/km_u16.h @@ -74,3 +74,12 @@ KMX_CHAR* strrchr_slash(KMX_CHAR* Name); const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name); std::string toHex(int num1); + +/** @brief Trim whitespace from the start (left) of a string */ +KMX_WCHAR* u16ltrim(KMX_WCHAR* p); + +/** @brief Trim whitespace from the end (right) of a string */ +KMX_WCHAR* u16rtrim(KMX_WCHAR *p); + +/** @brief Trim whitespace from both the start and end of a string */ +KMX_WCHAR* u16trim(KMX_WCHAR *p); diff --git a/common/models/tsconfig.kmw-worker-base.json b/common/models/tsconfig.kmw-worker-base.json deleted file mode 100644 index 0b581af1be..0000000000 --- a/common/models/tsconfig.kmw-worker-base.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../web/tsconfig.base.json", - - "compilerOptions": { - // 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 - } -} diff --git a/common/models/types/.build-builder b/common/models/types/.build-builder deleted file mode 100644 index 6db097fddb..0000000000 --- a/common/models/types/.build-builder +++ /dev/null @@ -1 +0,0 @@ -The presence of this file tells CI to use the new builder_ style parameters for build.sh. \ No newline at end of file diff --git a/common/models/types/.gitignore b/common/models/types/.gitignore deleted file mode 100644 index e113ad0abd..0000000000 --- a/common/models/types/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Specifically here: -test.js - -node_modules/ -build/ diff --git a/common/models/types/README.md b/common/models/types/README.md deleted file mode 100644 index 3d7dbf7d59..0000000000 --- a/common/models/types/README.md +++ /dev/null @@ -1,68 +0,0 @@ -`@keymanapp/models-types` -========================= - -Declares TypeScript types and interfaces required to define **Lexical -Models**, **Word breaking functions**, and all things in between. - -Install -------- - -> (see below if you are working in the keymanapp/keyman repo!) - - npm install --save-dev @keymanapp/models-types - -Usage ------ - -Say you are creating a **custom lexical model**. Then import this module -in your lexical model file! - -```Typescript -// my-nifty-model.ts - -import "@keymanapp/models-types"; - -export class MyNiftyClass implements LexicalModel { - configure(capabilities: Capabilities): Configuration { - // TODO: implement me! - throw new Error("Method not implemented."); - } - predict(transform: Transform, context: Context): ProbabilityMass[] { - // TODO: implement me! - throw new Error("Method not implemented."); - } - wordbreak(context: Context): string { - // TODO: implement me! - throw new Error("Method not implemented."); - } - punctuation?: LexicalModelPunctuation; -} -``` - -If your are creating a **custom word breaker**, you would do the same: - -```typescript -import "@keymanapp/models-types"; - -export let myShinyWordBreaker: WordBreakingFunction; -myShinyWordBreaker = function (phrase: string): Span[] { - // TODO: implement me! - throw new Error("Function not implemented") -} -``` - -Look at [index.d.ts](./index.d.ts) for documentation on each and every -type! - - -Usage within keymanapp/keyman repo ----------------------------------- - -To use it in other subprojects within keymanapp/keyman, ensure that this package -is linked using `"*"`. Add this dependency to your subproject like so: - -```json - "dependencies": { - "@keymanapp/models-types": "*", - } -``` diff --git a/common/models/types/build.sh b/common/models/types/build.sh deleted file mode 100755 index 25bf4515c4..0000000000 --- a/common/models/types/build.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# -# Packages the @keymanapp/models-types package. -# -## 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 - -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" -. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" - -builder_describe "Keyman model types package" \ - "clean" \ - "configure" \ - "build" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" - -builder_describe_outputs \ - configure /node_modules \ - build /common/models/types/build/common/models/types/tsconfig.tsbuildinfo - -builder_parse "$@" - -#------------------------------------------------------------------------------------------------------------------- - -builder_run_action clean rm -rf ./build/ -builder_run_action configure verify_npm_setup -builder_run_action build tsc --build -builder_run_action test tsc test.ts -lib es6 -builder_run_action publish builder_publish_npm diff --git a/common/models/types/package.json b/common/models/types/package.json deleted file mode 100644 index a9f68f4ea1..0000000000 --- a/common/models/types/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@keymanapp/models-types", - "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", - "types": "./index.d.ts", - "scripts": { - "test": "tsc test.ts -lib es6" - }, - "repository": { - "type": "git", - "url": "https://github.com/keymanapp/keyman" - }, - "keywords": [ - "lmlayer", - "keyman", - "predictive", - "text", - "types", - "typing", - "typescript" - ], - "author": "Marc Durdin (https://github.com/mcdurdin)", - "contributors": [ - "Eddie Antonio Santos (https://github.com/eddieantonio)", - "Joshua Horton" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/keymanapp/keyman/issues" - }, - "homepage": "https://github.com/keymanapp/keyman/tree/master/common/models/types#readme", - "devDependencies": { - "typescript": "^5.4.5" - }, - "files": [ - "README.md", - "index.d.ts" - ] -} diff --git a/common/models/types/tsconfig.json b/common/models/types/tsconfig.json deleted file mode 100644 index ddd1c5b196..0000000000 --- a/common/models/types/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../tsconfig.kmw-worker-base.json", - "compilerOptions": { - "declaration": true, - "outDir": "build/", - }, - "include": ["./*.ts"], - "exclude": ["test.ts"] -} diff --git a/common/models/wordbreakers/.gitignore b/common/models/wordbreakers/.gitignore deleted file mode 100644 index d16386367f..0000000000 --- a/common/models/wordbreakers/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ \ No newline at end of file diff --git a/common/models/wordbreakers/src/default/data.ts b/common/models/wordbreakers/src/default/data.ts deleted file mode 100644 index abd128f020..0000000000 --- a/common/models/wordbreakers/src/default/data.ts +++ /dev/null @@ -1,1802 +0,0 @@ -// Automatically generated file. DO NOT MODIFY. - -/** - * Valid values for a word break property. - */ -export const enum WordBreakProperty { // Scary bit: this does not exist as an object at run-time! - 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 -}; - -// 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. -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 -} - -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.Format], - [/*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.Format], - [/*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.Format], - [/*start*/ 0x710, 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*/ 0x8A0, WordBreakProperty.ALetter], - [/*start*/ 0x8B5, WordBreakProperty.Other], - [/*start*/ 0x8B6, WordBreakProperty.ALetter], - [/*start*/ 0x8C8, WordBreakProperty.Other], - [/*start*/ 0x8D3, WordBreakProperty.Extend], - [/*start*/ 0x8E2, WordBreakProperty.Format], - [/*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*/ 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*/ 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*/ 0xCDE, 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.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*/ 0xECE, 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*/ 0x170D, WordBreakProperty.Other], - [/*start*/ 0x170E, WordBreakProperty.ALetter], - [/*start*/ 0x1712, WordBreakProperty.Extend], - [/*start*/ 0x1715, WordBreakProperty.Other], - [/*start*/ 0x1720, 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.Other], - [/*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*/ 0x1AC1, WordBreakProperty.Other], - [/*start*/ 0x1B00, WordBreakProperty.Extend], - [/*start*/ 0x1B05, WordBreakProperty.ALetter], - [/*start*/ 0x1B34, WordBreakProperty.Extend], - [/*start*/ 0x1B45, WordBreakProperty.ALetter], - [/*start*/ 0x1B4C, 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*/ 0x1DFA, WordBreakProperty.Other], - [/*start*/ 0x1DFB, 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*/ 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], - [/*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*/ 0xA7C0, WordBreakProperty.Other], - [/*start*/ 0xA7C2, WordBreakProperty.ALetter], - [/*start*/ 0xA7CB, WordBreakProperty.Other], - [/*start*/ 0xA7F5, 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*/ 0x10600, WordBreakProperty.ALetter], - [/*start*/ 0x10737, WordBreakProperty.Other], - [/*start*/ 0x10740, WordBreakProperty.ALetter], - [/*start*/ 0x10756, WordBreakProperty.Other], - [/*start*/ 0x10760, WordBreakProperty.ALetter], - [/*start*/ 0x10768, 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*/ 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*/ 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.Other], - [/*start*/ 0x1107F, WordBreakProperty.Extend], - [/*start*/ 0x11083, WordBreakProperty.ALetter], - [/*start*/ 0x110B0, WordBreakProperty.Extend], - [/*start*/ 0x110BB, WordBreakProperty.Other], - [/*start*/ 0x110BD, WordBreakProperty.Format], - [/*start*/ 0x110BE, WordBreakProperty.Other], - [/*start*/ 0x110CD, WordBreakProperty.Format], - [/*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.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*/ 0x11AC0, 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*/ 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*/ 0x13000, WordBreakProperty.ALetter], - [/*start*/ 0x1342F, WordBreakProperty.Other], - [/*start*/ 0x13430, WordBreakProperty.Format], - [/*start*/ 0x13439, 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*/ 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*/ 0x1B000, WordBreakProperty.Katakana], - [/*start*/ 0x1B001, 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*/ 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*/ 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*/ 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*/ 0x1E2C0, WordBreakProperty.ALetter], - [/*start*/ 0x1E2EC, WordBreakProperty.Extend], - [/*start*/ 0x1E2F0, WordBreakProperty.Numeric], - [/*start*/ 0x1E2FA, 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/tsconfig.json b/common/models/wordbreakers/tsconfig.json deleted file mode 100644 index 54d12d5d8c..0000000000 --- a/common/models/wordbreakers/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../tsconfig.kmw-worker-base.json", - - "compilerOptions": { - "baseUrl": "./", - "outDir": "build/obj", - "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo", - "rootDir": "./src" - }, - "references": [ - { "path": "../types" } - ], - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "test/**/*.ts" - ] -} diff --git a/common/web/eslint/eslintNoNodeImports.js b/common/tools/eslint/eslintNoNodeImports.js similarity index 100% rename from common/web/eslint/eslintNoNodeImports.js rename to common/tools/eslint/eslintNoNodeImports.js diff --git a/common/web/build.sh b/common/web/build.sh index fbc015613f..798db3b231 100755 --- a/common/web/build.sh +++ b/common/web/build.sh @@ -8,15 +8,9 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE -# -# TODO: future modules may include -# :sentry-manager \ -# - builder_describe "Keyman common web modules" \ :keyman-version \ :types \ - :utils \ clean \ configure \ build \ diff --git a/common/web/gesture-recognizer/build.sh b/common/web/gesture-recognizer/build.sh deleted file mode 100755 index a27cd3f8d1..0000000000 --- a/common/web/gesture-recognizer/build.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash - -## 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/shellHelperFunctions.sh" - -BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs" - -################################ Main script ################################ - -builder_describe "Builds the gesture-recognition model for Web-based on-screen keyboards" \ - "@/common/web/es-bundling build" \ - "@/common/web/utils build" \ - "clean" \ - "configure" \ - "build" \ - "test" \ - ":module" \ - ":tools tools for testing & developing test resources for this module" \ - "--ci sets the --ci option for child scripts (i.e, the $(builder_term test) action)" - -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 - -builder_parse "$@" - -# TODO: build if out-of-date if test is specified -# TODO: configure if npm has not been run, and build is specified - -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 -} - -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 - # 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" \ - --format esm - - builder_finish_action success build:module -fi - -if builder_start_action build:tools; then - src/tools/build.sh build - builder_finish_action success build:tools -fi - -if builder_start_action test:module; then - if builder_has_option --ci; then - ./test.sh --ci - else - ./test.sh - fi - builder_finish_action success test:module -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 diff --git a/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs b/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs deleted file mode 100644 index d18a8a9cb0..0000000000 --- a/common/web/gesture-recognizer/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/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs b/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs deleted file mode 100644 index 0d5bac8508..0000000000 --- a/common/web/gesture-recognizer/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 = '/common/web/gesture-recognizer/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/common/web/gesture-recognizer/src/test/tsconfig.json b/common/web/gesture-recognizer/src/test/tsconfig.json deleted file mode 100644 index 4eea3ef803..0000000000 --- a/common/web/gesture-recognizer/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/common/web/gesture-recognizer/test.sh b/common/web/gesture-recognizer/test.sh deleted file mode 100755 index 03d0e32fa1..0000000000 --- a/common/web/gesture-recognizer/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-recognizer module" \ - "@/common/web/gesture-recognizer" \ - "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/common/web/tsconfig.kmw-main-base.json b/common/web/tsconfig.kmw-main-base.json deleted file mode 100644 index 0ed8703e5e..0000000000 --- a/common/web/tsconfig.kmw-main-base.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../web/tsconfig.base.json" -} diff --git a/common/web/types/.eslintrc.cjs b/common/web/types/.eslintrc.cjs index 98db7f812e..b28ba0941b 100644 --- a/common/web/types/.eslintrc.cjs +++ b/common/web/types/.eslintrc.cjs @@ -14,7 +14,7 @@ module.exports = { overrides: [ { files: "src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], }, ], rules: { diff --git a/common/web/types/package.json b/common/web/types/package.json index 20f84bc140..1ba487aafd 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -8,9 +8,11 @@ "unicode" ], "type": "module", + "types": "./build/src/main.d.ts", "exports": { ".": { "es6-bundling": "./src/main.ts", + "types": "./build/src/main.d.ts", "default": "./build/src/main.js" } }, @@ -68,6 +70,7 @@ "src/kmx/kmx-plus.ts", "src/kmx/kmx-builder.ts", "src/kmx/element-string.ts", + "src/lexical-model-types.ts", "src/kmx/string-list.ts", "src/ldml-keyboard/ldml-keyboard-testdata-xml.ts", "src/ldml-keyboard/unicodeset-parser-api.ts", 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 786d9213e2..e24fd295c8 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,11 @@ // 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 + * hardware-style layouts. + */ export interface TouchLayoutFile { tablet?: TouchLayoutPlatform; phone?: TouchLayoutPlatform; @@ -17,6 +22,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 +33,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 +41,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 +70,40 @@ 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, or a standard key from the K_ enumeration */ id?: TouchLayoutKeyId; + /** text to display on key cap */ text?: string; + /** + * 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; + /** the next layer to switch to after this key is pressed */ nextlayer?: TouchLayoutLayerId; + /** font */ font?: TouchLayoutFont; + /** fontsize */ fontsize?: TouchLayoutFontSize; + /** the type of key */ sp?: TouchLayoutKeySp; + /** padding */ pad?: TouchLayoutKeyPad; + /** width of the key */ width?: TouchLayoutKeyWidth; + /** longpress keys, also known as subkeys */ sk?: TouchLayoutSubKey[]; + /** flicks */ flick?: TouchLayoutFlick; + /** multitaps */ multitap?: TouchLayoutSubKey[]; + /** hint e.g. for longpress */ hint?: string; }; +/** 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 @@ -102,29 +128,54 @@ 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, or a standard key from the K_ enumeration */ id: TouchLayoutKeyId; + /** text to display on key cap */ text?: string; + /** + * 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; + /** the next layer to switch to after this key is pressed */ nextlayer?: TouchLayoutLayerId; + /** font */ font?: TouchLayoutFont; + /** fontsize */ fontsize?: TouchLayoutFontSize; + /** the type of 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 (north) */ n?: TouchLayoutSubKey; + /** flick down (south) */ s?: TouchLayoutSubKey; + /** flick right (east) */ e?: TouchLayoutSubKey; + /** flick left (west) */ w?: TouchLayoutSubKey; + /** flick up-right (north-east) */ ne?: TouchLayoutSubKey; + /** flick up-left (north-west) */ nw?: TouchLayoutSubKey; + /** flick down-right (south-east) */ se?: TouchLayoutSubKey; + /** flick down-left (south-west) */ sw?: TouchLayoutSubKey; }; diff --git a/common/web/types/src/kmx/kmx-plus/element-string.ts b/common/web/types/src/kmx/kmx-plus/element-string.ts index c7597b4f6b..b389107cce 100644 --- a/common/web/types/src/kmx/kmx-plus/element-string.ts +++ b/common/web/types/src/kmx/kmx-plus/element-string.ts @@ -1,7 +1,7 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; import { DependencySections, StrsItem, UsetItem } from './kmx-plus.js'; import { ElementParser, ElementSegment, ElementType } from '../../ldml-keyboard/pattern-parser.js'; -import { util } from '@keymanapp/common-types'; +import * as util from '../../util/util.js'; import MATCH_HEX_ESCAPE = util.MATCH_HEX_ESCAPE; import unescapeOneQuadString = util.unescapeOneQuadString; diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 1d19cdd4d0..cc8ff772b7 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -2,8 +2,8 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; import * as r from 'restructure'; import { ElementString } from './element-string.js'; import { ListItem } from '../../ldml-keyboard/string-list.js'; -import { util } from '@keymanapp/common-types'; -import { KMX } from '@keymanapp/common-types'; +import * as util from '../../util/util.js'; +import * as KMX from '../kmx.js'; import { UnicodeSetParser, UnicodeSet } from '../../ldml-keyboard/unicodeset-parser-api.js'; import { VariableParser } from '../../ldml-keyboard/pattern-parser.js'; import { MarkerParser } from '../../ldml-keyboard/pattern-parser.js'; diff --git a/common/models/types/index.d.ts b/common/web/types/src/lexical-model-types.ts similarity index 96% rename from common/models/types/index.d.ts rename to common/web/types/src/lexical-model-types.ts index 0d6223bc64..7c5cfdf643 100644 --- a/common/models/types/index.d.ts +++ b/common/web/types/src/lexical-model-types.ts @@ -15,14 +15,14 @@ * * See also: https://developer.mozilla.org/en-US/docs/Web/API/USVString */ -declare type USVString = string; +export type USVString = string; -declare type CasingForm = 'lower' | 'initial' | 'upper'; +export type CasingForm = 'lower' | 'initial' | 'upper'; /** * Represents one lexical entry and its probability.. */ -type TextWithProbability = { +export type TextWithProbability = { /** * A lexical entry (word) offered by the model. * @@ -42,7 +42,7 @@ type TextWithProbability = { * Used to facilitate edit-distance calculations by allowing the LMLayer to * efficiently search the model's lexicon in a Trie-like manner. */ -declare interface LexiconTraversal { +export interface LexiconTraversal { /** * Provides an iterable pattern used to search for words with a 'keyed' prefix matching * the current traversal state's prefix when a new character is appended. Iterating @@ -116,7 +116,7 @@ declare interface LexiconTraversal { /** * The model implementation, within the Worker. */ -declare interface LexicalModel { +export interface LexicalModel { /** * Processes `config` messages, configuring the newly-loaded model based on the host * platform's capability restrictions. @@ -263,7 +263,7 @@ declare interface LexicalModel { * first, you delete the specified amount amount from the left * and right, then you insert the provided text. */ -declare interface Transform { +export interface Transform { /** * Facilitates use of unique identifiers for tracking the Transform and * any related data from its original source, as the reference cannot be @@ -298,7 +298,7 @@ declare interface Transform { /** * A concrete suggestion */ -declare interface Suggestion { +export interface Suggestion { /** * Indicates the externally-supplied id of the Transform that prompted * the Suggestion. Automatically handled by the LMLayer; models should @@ -341,11 +341,11 @@ declare interface Suggestion { autoAccept?: boolean } -interface Reversion extends Suggestion { +export interface Reversion extends Suggestion { tag: 'revert'; } -interface Keep extends Suggestion { +export interface Keep extends Suggestion { tag: 'keep'; /** @@ -368,12 +368,12 @@ interface Keep extends Suggestion { * * If left undefined, the consumers will assume this is a prediction. */ -type SuggestionTag = undefined | 'keep' | 'revert' | 'correction' | 'emoji'; +export type SuggestionTag = undefined | 'keep' | 'revert' | 'correction' | 'emoji'; /** * The text and environment surrounding the insertion point (text cursor). */ -declare interface Context { +export interface Context { /** * Up to maxLeftContextCodeUnits code units of Unicode scalar value * (i. e., characters) to the left of the insertion point in the @@ -413,7 +413,7 @@ declare interface Context { * from ambiguous text sequences. Designed for use with fat-finger correction * and similar typing ambiguities. */ -interface ProbabilityMass { +export interface ProbabilityMass { /** * An individual sample from a Distribution over the same type. */ @@ -426,12 +426,12 @@ interface ProbabilityMass { p: number; } -declare type Distribution = ProbabilityMass[]; +export type Distribution = ProbabilityMass[]; /** * A type augmented with an optional probability. */ -type Outcome = T & { +export type Outcome = T & { /** * [optional] probability of this outcome. */ @@ -441,7 +441,7 @@ type Outcome = T & { /** * A type augmented with a probability. */ -type WithOutcome = T & { +export type WithOutcome = T & { /** * Probability of this outcome. */ @@ -458,7 +458,7 @@ type WithOutcome = T & { * prediction, as well as what operations the keyboard is allowed to do on the * underlying buffer. */ -declare interface Capabilities { +export interface Capabilities { /** * The maximum amount of UTF-16 code points that the keyboard will provide to * the left of the cursor, as an integer. @@ -482,7 +482,7 @@ declare interface Capabilities { /** * Configuration of the LMLayer, sent back to the keyboard. */ -declare interface Configuration { +export interface Configuration { /** * How many UTF-16 code units maximum to send as the context to the * left of the cursor ("left" in the Unicode character stream). @@ -538,14 +538,14 @@ declare interface Configuration { * @returns an array of spans from the phrase, in order as they appear in the * phrase, each span which representing a word. */ -declare interface WordBreakingFunction { +export interface WordBreakingFunction { // invariant: span[i].end <= span[i + 1].start // invariant: for all span[i] and span[i + 1], there does not exist a span[k] // where span[i].end <= span[k].start AND span[k].end <= span[i + 1].start (phrase: string): Span[]; } -declare interface CasingFunction { +export interface CasingFunction { (caseToApply: CasingForm, text: string, defaultApplyCasing?: CasingFunction): string; } @@ -553,7 +553,7 @@ declare interface CasingFunction { * A span of text in a phrase. This is usually meant to represent words from a * pharse. */ -declare interface Span { +export interface Span { // invariant: start < end (empty spans not allowed) readonly start: number; // invariant: end > end (empty spans not allowed) @@ -572,7 +572,7 @@ declare interface Span { /** * Options for various punctuation to use in suggestions. */ -interface LexicalModelPunctuation { +export interface LexicalModelPunctuation { /** * The quotes that appear in "keep" suggestions, e.g., keep what the user * typed verbatim. diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 7e0dc2f7b2..df21b0fed3 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -29,3 +29,5 @@ export * as KMXPlus from './kmx/kmx-plus/kmx-plus.js'; export { UnicodeSetParser, UnicodeSet } from './ldml-keyboard/unicodeset-parser-api.js'; export { VariableParser, MarkerParser } from './ldml-keyboard/pattern-parser.js'; export { ElementString } from './kmx/kmx-plus/element-string.js'; + +export { USVString, CasingForm, CasingFunction, TextWithProbability, LexiconTraversal, LexicalModel, LexicalModelPunctuation, Transform, Suggestion, Reversion, Keep, SuggestionTag, Context, Distribution, Outcome, WithOutcome, ProbabilityMass, Configuration, Capabilities, WordBreakingFunction, Span } from './lexical-model-types.js'; diff --git a/common/models/types/test.ts b/common/web/types/test/lexical-model-types.tests.ts similarity index 54% rename from common/models/types/test.ts rename to common/web/types/test/lexical-model-types.tests.ts index a360741809..8944342ed6 100644 --- a/common/models/types/test.ts +++ b/common/web/types/test/lexical-model-types.tests.ts @@ -1,12 +1,12 @@ -/// - /** * This file "tests" the exports from the main module. - * + * * Since the exports are all types, the "test" here is that the type can be * imported and compiled without any compiler errors. */ +import { USVString, Transform, Suggestion, SuggestionTag, Context, Capabilities, Configuration, Distribution, WordBreakingFunction, Span, LexicalModelPunctuation, ElementString, KMXPlus } from '@keymanapp/common-types'; + export let u: USVString; export let l: Transform export let s: Suggestion; @@ -17,4 +17,9 @@ export let conf: Configuration; export let d: Distribution; export let wbf: WordBreakingFunction; export let sp: Span; -export let lmp: LexicalModelPunctuation; \ No newline at end of file +export let lmp: LexicalModelPunctuation; + + +// try some of the other types - that should still work +export let elemString: ElementString; +export let section: KMXPlus.Section; diff --git a/common/web/types/test/tsconfig.json b/common/web/types/test/tsconfig.json index b034236bd7..9678c49945 100644 --- a/common/web/types/test/tsconfig.json +++ b/common/web/types/test/tsconfig.json @@ -11,11 +11,12 @@ }, "include": [ "**/test-*.ts", + "**/*.tests.ts", "./helpers/*.ts", ], "references": [ { "path": "../../keyman-version" }, { "path": "../../../../core/include/ldml/"}, { "path": "../" }, - ] + ], } diff --git a/common/windows/.gitignore b/common/windows/.gitignore index 7af23e7e1a..a6cb808a34 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/cpp/include/registry.h b/common/windows/cpp/include/registry.h index 67cdfc2f69..2627d06522 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) +/* Default is to only use left modifier in hotkeys trigger */ +#define REGSZ_AllowRightModifierHotKey "allow right modifier for hotkey" + + /* Debug flags These are all stored in HKCU\Software\Keyman\Debug @@ -121,13 +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_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" /* DWORD: Enable/disable deep TSF integration, default enabled; 0 = disabled, 1 = enabled, 2 = default */ diff --git a/common/windows/delphi/Defines.mak b/common/windows/delphi/Defines.mak deleted file mode 100644 index 50210e5fea..0000000000 --- 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 6c78b3724e..0000000000 --- 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 777bd84beb..0000000000 --- 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/common/windows/delphi/components/common_components.dproj b/common/windows/delphi/components/common_components.dproj index 2bd91473ab..4ecda90464 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 b9b013c212..0000000000 Binary files a/common/windows/delphi/ext/cef4delphi/packages/CEF4Delphi.res and /dev/null differ diff --git a/common/windows/delphi/general/RegistryKeys.pas b/common/windows/delphi/general/RegistryKeys.pas index 7e7fe54c38..511f36e6d9 100644 --- a/common/windows/delphi/general/RegistryKeys.pas +++ b/common/windows/delphi/general/RegistryKeys.pas @@ -113,10 +113,11 @@ const SRegValue_ShowWelcome = 'show welcome'; // CU SRegValue_UseAdvancedInstall = 'use advanced install'; // CU - SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU - SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU + SRegValue_AltGrCtrlAlt = 'simulate altgr'; // CU + SRegValue_KeyboardHotKeysAreToggle = 'hotkeys are toggles'; // CU + SRegValue_AllowRightModifierHotKey = 'allow right modifier for hotkey'; // CU SRegValue_ReleaseShiftKeysAfterKeyPress = 'release shift keys after key press'; // CU - SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true + SRegValue_TestKeymanFunctioning = 'test keyman functioning'; // CU, default true SRegValue_CreateStartMenuAsSubfolders = 'create start menu as subfolders'; // CU SRegValue_CreateUninstallEntries = 'create uninstall entries'; // CU @@ -383,8 +384,6 @@ const SRegKey_KeymanEngineDebug_CU = SRegKey_KeymanEngineRoot_CU + '\Debug'; - SRegValue_Flag_UseRegisterHotkey = 'Flag_UseRegisterHotkey'; - SRegValue_Flag_UseCachedHotkeyModifierState = 'Flag_UseCachedHotkeyModifierState'; SRegValue_Flag_ShouldSerializeInput = 'Flag_ShouldSerializeInput'; SRegValue_Flag_UseAutoStartTask = 'Flag_UseAutoStartTask'; SRegValue_Flag_SyncLanguagesToCloud = 'Flag_SyncLanguagesToCloud'; diff --git a/common/windows/delphi/tools/certificates/build.sh b/common/windows/delphi/tools/certificates/build.sh index 378723a125..10ab4c75ce 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/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp index f90fcfaa94..fcadda629c 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 07a2ff08ad..f79f82141f 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 ed3368dc7e..6a54d77065 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 @@ public: 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 diff --git a/core/tests/unit/ldml/test_unicode.cpp b/core/tests/unit/ldml/test_unicode.cpp index b36f1a3e38..937125d172 100644 --- a/core/tests/unit/ldml/test_unicode.cpp +++ b/core/tests/unit/ldml/test_unicode.cpp @@ -171,8 +171,9 @@ const std::string &block_unicode_ver) { // the cxx_icu can come from the Ubuntu environment, so do not depend on it // for now. + // TODO: Resolve with ICU4C 76 in #12398 //assert_basic_equal(node_icu_unicode_major, cxx_icu_unicode_major); - assert_basic_equal(node_icu_unicode_major, block_ver_major); + //assert_basic_equal(node_icu_unicode_major, block_ver_major); // seems less important if the C++ ICU verison matches the Node.js ICU version. //assert_basic_equal(cxx_icu_major, node_icu_major); diff --git a/developer/docs/api/etc/kmc-model.api.md b/developer/docs/api/etc/kmc-model.api.md index df76354c23..51ea5dffb8 100644 --- a/developer/docs/api/etc/kmc-model.api.md +++ b/developer/docs/api/etc/kmc-model.api.md @@ -4,7 +4,7 @@ ```ts -/// +/// import { CompilerCallbacks } from '@keymanapp/common-types'; import { CompilerEvent } from '@keymanapp/common-types'; diff --git a/developer/src/README.md b/developer/src/README.md index 7e7ea9d336..d789b63f08 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/developer/src/build.sh b/developer/src/build.sh index b45dea38f5..c85851f1f6 100755 --- a/developer/src/build.sh +++ b/developer/src/build.sh @@ -151,7 +151,6 @@ function do_publish() { ../../common/web/keyman-version/build.sh publish $DRY_RUN $NPM_PUBLISH ../../common/web/types/build.sh publish $DRY_RUN $NPM_PUBLISH - ../../common/models/types/build.sh publish $DRY_RUN $NPM_PUBLISH ../../core/include/ldml/build.sh publish $DRY_RUN $NPM_PUBLISH # end TODO #-------------------------------------------------------- diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh index 8829765237..2209463e18 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 162cac4aac..ddeaf719ab 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 { diff --git a/developer/src/kmc-analyze/.eslintrc.cjs b/developer/src/kmc-analyze/.eslintrc.cjs index bd0835f0be..148ad7579f 100644 --- a/developer/src/kmc-analyze/.eslintrc.cjs +++ b/developer/src/kmc-analyze/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { overrides: [ { files:"src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], } ], rules: { diff --git a/developer/src/kmc-analyze/src/analyzer-messages.ts b/developer/src/kmc-analyze/src/analyzer-messages.ts index 1e4204595e..c895a2f367 100644 --- a/developer/src/kmc-analyze/src/analyzer-messages.ts +++ b/developer/src/kmc-analyze/src/analyzer-messages.ts @@ -1,6 +1,3 @@ -/// -// above is required for now, seemingly https://github.com/microsoft/TypeScript/issues/42873 -// probably addressed in ts 5.5.2 import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils"; const Namespace = CompilerErrorNamespace.Analyzer; diff --git a/developer/src/kmc-kmn/.eslintrc.cjs b/developer/src/kmc-kmn/.eslintrc.cjs index 156e7b5cf0..9fb322a1e4 100644 --- a/developer/src/kmc-kmn/.eslintrc.cjs +++ b/developer/src/kmc-kmn/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { overrides: [ { files:"src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], } ], ignorePatterns: ["test/fixtures/*"], diff --git a/developer/src/kmc-ldml/.eslintrc.cjs b/developer/src/kmc-ldml/.eslintrc.cjs index 09038ae929..41a487fe41 100644 --- a/developer/src/kmc-ldml/.eslintrc.cjs +++ b/developer/src/kmc-ldml/.eslintrc.cjs @@ -6,7 +6,7 @@ module.exports = { overrides: [ { files:"src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], } ], rules: { 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 6182e6e2c8..6d76bc403e 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 +// 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) { + 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( diff --git a/developer/src/kmc-model-info/.eslintrc.cjs b/developer/src/kmc-model-info/.eslintrc.cjs index d37a461073..494ae6b00a 100644 --- a/developer/src/kmc-model-info/.eslintrc.cjs +++ b/developer/src/kmc-model-info/.eslintrc.cjs @@ -8,7 +8,7 @@ module.exports = { overrides: [ { files: "src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], } ], rules: { diff --git a/developer/src/kmc-model-info/build.sh b/developer/src/kmc-model-info/build.sh index 334bfbd07b..b1ba95c34b 100755 --- a/developer/src/kmc-model-info/build.sh +++ b/developer/src/kmc-model-info/build.sh @@ -9,7 +9,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Build Keyman kmc Lexical Model model-info Compiler module" \ "@/common/web/types" \ - "@/common/models/types" \ "clean" \ "configure" \ "build" \ diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index 23297a07ba..20be72d189 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "@keymanapp/models-types": "*", + "@keymanapp/common-types": "*", "@keymanapp/developer-utils": "*" }, "devDependencies": { diff --git a/developer/src/kmc-model-info/tsconfig.json b/developer/src/kmc-model-info/tsconfig.json index 24475de019..e4d68f11bf 100644 --- a/developer/src/kmc-model-info/tsconfig.json +++ b/developer/src/kmc-model-info/tsconfig.json @@ -10,7 +10,6 @@ ], "references": [ { "path": "../../../common/web/types" }, - { "path": "../../../common/models/types" }, { "path": "../common/web/utils" }, ] } diff --git a/developer/src/kmc-model/.eslintrc.cjs b/developer/src/kmc-model/.eslintrc.cjs index 3d339676e2..efa3d1a96a 100644 --- a/developer/src/kmc-model/.eslintrc.cjs +++ b/developer/src/kmc-model/.eslintrc.cjs @@ -9,7 +9,7 @@ module.exports = { overrides: [ { files: "src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], }, ], rules: { diff --git a/developer/src/kmc-model/build.sh b/developer/src/kmc-model/build.sh index c549648b45..372a145312 100755 --- a/developer/src/kmc-model/build.sh +++ b/developer/src/kmc-model/build.sh @@ -8,12 +8,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" -# TODO: "@/common/models/types" \ - builder_describe "Keyman kmc Lexical Model Compiler module" \ "@/common/web/keyman-version" \ "@/developer/src/common/web/test-helpers" \ - "@/common/models/templates test" \ + "@/web/src/engine/predictive-text/templates/ test" \ "clean" \ "configure" \ "build" \ diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json index da73aabe7b..9f95de51f8 100644 --- a/developer/src/kmc-model/package.json +++ b/developer/src/kmc-model/package.json @@ -32,7 +32,7 @@ "dependencies": { "@keymanapp/common-types": "*", "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", + "@keymanapp/common-types": "*", "typescript": "^5.4.5" }, "devDependencies": { diff --git a/developer/src/kmc-model/src/join-word-breaker-decorator.ts b/developer/src/kmc-model/src/join-word-breaker-decorator.ts index 6d0426938a..dec58d583a 100644 --- a/developer/src/kmc-model/src/join-word-breaker-decorator.ts +++ b/developer/src/kmc-model/src/join-word-breaker-decorator.ts @@ -1,4 +1,4 @@ -/// +import { Span, WordBreakingFunction } from '@keymanapp/common-types'; /** * Returns a word breaker that joins spans of an existing word breaker. diff --git a/developer/src/kmc-model/src/lexical-model.ts b/developer/src/kmc-model/src/lexical-model.ts index 32d5bf5775..f0fd95c19e 100644 --- a/developer/src/kmc-model/src/lexical-model.ts +++ b/developer/src/kmc-model/src/lexical-model.ts @@ -3,6 +3,8 @@ * the LMLayer's internal worker code, so we provide those definitions too. */ +import { CasingFunction, LexicalModelPunctuation, WordBreakingFunction } from '@keymanapp/common-types'; + export interface LexicalModelDeclaration { readonly format: 'trie-1.0'|'fst-foma-1.0'|'custom-1.0', //... metadata ... diff --git a/developer/src/kmc-model/src/model-defaults.ts b/developer/src/kmc-model/src/model-defaults.ts index 541cadcb49..4d6c40d3a1 100644 --- a/developer/src/kmc-model/src/model-defaults.ts +++ b/developer/src/kmc-model/src/model-defaults.ts @@ -1,3 +1,5 @@ +import { CasingForm, CasingFunction } from '@keymanapp/common-types'; + /** * Converts wordforms into an indexable form. It does this by * normalizing the letter case of characters INDIVIDUALLY (to disregard @@ -104,4 +106,4 @@ export function defaultApplyCasing(casing: CasingForm, text: string): string { return text.substring(0, headUnitLength).toUpperCase() // head - uppercased .concat(text.substring(headUnitLength)); // tail - lowercased } -} \ No newline at end of file +} diff --git a/developer/src/kmc-model/src/model-definitions.ts b/developer/src/kmc-model/src/model-definitions.ts index d05a8254ce..ccef469c91 100644 --- a/developer/src/kmc-model/src/model-definitions.ts +++ b/developer/src/kmc-model/src/model-definitions.ts @@ -5,6 +5,7 @@ import { defaultApplyCasing, import KEYMAN_VERSION from "@keymanapp/keyman-version"; import { LexicalModelSource, WordformToKeySpec } from "./lexical-model.js"; +import { CasingForm, CasingFunction } from '@keymanapp/common-types'; /** * Processes certain defined model behaviors in such a way that the needed closures @@ -219,4 +220,4 @@ export class ModelDefinitions { } // Because it references the class field, this line must come afterward. -const PSEUDOCLOSURE = ModelDefinitions.COMPILED_NAME; \ No newline at end of file +const PSEUDOCLOSURE = ModelDefinitions.COMPILED_NAME; diff --git a/developer/src/kmc-model/src/script-overrides-decorator.ts b/developer/src/kmc-model/src/script-overrides-decorator.ts index 544f1b0386..ebf08b819e 100644 --- a/developer/src/kmc-model/src/script-overrides-decorator.ts +++ b/developer/src/kmc-model/src/script-overrides-decorator.ts @@ -1,3 +1,4 @@ +import { Span, WordBreakingFunction } from '@keymanapp/common-types'; import { OverrideScriptDefaults } from "./lexical-model.js"; import { ModelCompilerError, ModelCompilerMessages } from "./model-compiler-messages.js"; diff --git a/developer/src/kmc-model/test/helpers/index.ts b/developer/src/kmc-model/test/helpers/index.ts index e7d274f571..d58e0834d1 100644 --- a/developer/src/kmc-model/test/helpers/index.ts +++ b/developer/src/kmc-model/test/helpers/index.ts @@ -1,5 +1,3 @@ -/// - /** * Helpers and utilities for the Mocha tests. */ diff --git a/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts b/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts index 08247e17c1..049776ec20 100644 --- a/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts +++ b/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts @@ -4,6 +4,7 @@ import 'mocha'; import { makePathToFixture, compileModelSourceCode } from './helpers/index.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { CasingForm, CasingFunction } from '@keymanapp/common-types'; describe('LexicalModelCompiler - pseudoclosure compilation + use', function () { const callbacks = new TestCompilerCallbacks(); diff --git a/developer/src/kmc-model/test/test-compile-model.ts b/developer/src/kmc-model/test/test-compile-model.ts index e3074d697a..7de9d6d5da 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', diff --git a/developer/src/kmc-model/test/test-compile-trie.ts b/developer/src/kmc-model/test/test-compile-trie.ts index a982956837..845022cef5 100644 --- a/developer/src/kmc-model/test/test-compile-trie.ts +++ b/developer/src/kmc-model/test/test-compile-trie.ts @@ -8,6 +8,7 @@ import { createTrieDataStructure } from '../src/build-trie.js'; import { ModelCompilerError } from '../src/model-compiler-messages.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { TrieModel } from '@keymanapp/models-templates'; +import { Span } from '@keymanapp/common-types'; describe('LexicalModelCompiler', function () { const callbacks = new TestCompilerCallbacks(); diff --git a/developer/src/kmc-model/test/test-default-search-term-to-key.ts b/developer/src/kmc-model/test/test-default-search-term-to-key.ts index b777c7c79c..0f8120e6ba 100644 --- a/developer/src/kmc-model/test/test-default-search-term-to-key.ts +++ b/developer/src/kmc-model/test/test-default-search-term-to-key.ts @@ -4,6 +4,7 @@ import {assert} from 'chai'; import { defaultSearchTermToKey, defaultCasedSearchTermToKey, defaultApplyCasing } from '../src/model-defaults.js'; +import { CasingForm, CasingFunction } from '@keymanapp/common-types'; describe('The default searchTermToKey() function', function () { diff --git a/developer/src/kmc-model/test/test-join-word-breaker.ts b/developer/src/kmc-model/test/test-join-word-breaker.ts index b08411cb56..ff220088f5 100644 --- a/developer/src/kmc-model/test/test-join-word-breaker.ts +++ b/developer/src/kmc-model/test/test-join-word-breaker.ts @@ -1,6 +1,7 @@ import { assert } from "chai"; import defaultWordBreaker from '@keymanapp/models-wordbreakers'; import {decorateWithJoin} from '../src/join-word-breaker-decorator.js'; +import { Span } from '@keymanapp/common-types'; describe('The join word breaker decorator', function () { it('should decorate an existing word breaker', function () { diff --git a/developer/src/kmc-model/test/test-model-definitions.ts b/developer/src/kmc-model/test/test-model-definitions.ts index 297bb1a995..ee569a01b1 100644 --- a/developer/src/kmc-model/test/test-model-definitions.ts +++ b/developer/src/kmc-model/test/test-model-definitions.ts @@ -2,6 +2,7 @@ import 'mocha'; import { assert } from 'chai'; import { ModelDefinitions } from '../src/model-definitions.js'; import { LexicalModelSource } from '../src/lexical-model.js'; +import { CasingForm, CasingFunction } from '@keymanapp/common-types'; describe('Model definition pseudoclosures', function () { describe('14.0 defaults', function() { @@ -195,4 +196,4 @@ describe('Model definition pseudoclosures', function () { }); } }); -}); \ No newline at end of file +}); diff --git a/developer/src/kmc-model/test/test-override-script-defaults.ts b/developer/src/kmc-model/test/test-override-script-defaults.ts index a0500320fd..b8afb18900 100644 --- a/developer/src/kmc-model/test/test-override-script-defaults.ts +++ b/developer/src/kmc-model/test/test-override-script-defaults.ts @@ -1,6 +1,7 @@ import { assert } from "chai"; import defaultWordBreaker from '@keymanapp/models-wordbreakers'; import {decorateWithScriptOverrides} from '../src/script-overrides-decorator.js'; +import { Span } from '@keymanapp/common-types'; const THIN_SPACE = "\u2009"; diff --git a/developer/src/kmc-model/tsconfig.json b/developer/src/kmc-model/tsconfig.json index 724ebd74d0..2400856d26 100644 --- a/developer/src/kmc-model/tsconfig.json +++ b/developer/src/kmc-model/tsconfig.json @@ -12,7 +12,6 @@ ], "references": [ { "path": "../../../common/web/keyman-version" }, - { "path": "../../../common/models/types" }, { "path": "../../../common/web/types" }, ] } diff --git a/developer/src/kmc-package/.eslintrc.cjs b/developer/src/kmc-package/.eslintrc.cjs index 09038ae929..41a487fe41 100644 --- a/developer/src/kmc-package/.eslintrc.cjs +++ b/developer/src/kmc-package/.eslintrc.cjs @@ -6,7 +6,7 @@ module.exports = { overrides: [ { files:"src/**/*.ts", - extends: ["../../../common/web/eslint/eslintNoNodeImports.js"], + extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"], } ], rules: { diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index d6486e1685..4d36bb1e98 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -44,7 +44,7 @@ "@keymanapp/kmc-model": "*", "@keymanapp/kmc-model-info": "*", "@keymanapp/kmc-package": "*", - "@keymanapp/models-types": "*", + "@keymanapp/common-types": "*", "@sentry/node": "^7.57.0", "chalk": "^2.4.2", "commander": "^10.0.0", diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index c41ba53eaa..536d4fff0d 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 3893a72855..57702ee1f0 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -2213,10 +2213,9 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX { KMX_WCHAR *context = NULL; - KMX_WCHAR sep_com[3] = u" ,"; - PKMX_WCHAR p_sep_com = sep_com; - r = u16tok(q, p_sep_com, &context); // I3481 + r = u16tok(q, ',', &context); if (!r) return KmnCompilerMessages::ERROR_InvalidIndex; + r = u16trim(r); for (i = 0; i < fk->cxStoreArray; i++) { @@ -2226,8 +2225,10 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX kmcmp::CheckStoreUsage(fk, i, TRUE, FALSE, FALSE); - r = u16tok(NULL, p_sep_com, &context); // I3481 - if (!r || !*r || !isIntegerWstring(r) || atoiW(r) < 1) return KmnCompilerMessages::ERROR_InvalidIndex; + r = context; + if (!r || !*r ) return KmnCompilerMessages::ERROR_InvalidIndex; + r = u16trim(r); + if (!isIntegerWstring(r) || atoiW(r) < 1) return KmnCompilerMessages::ERROR_InvalidIndex; } tstr[mx++] = UC_SENTINEL; tstr[mx++] = CODE_INDEX; @@ -2310,11 +2311,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 1c20597645..3d82947b96 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1091,34 +1091,50 @@ 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, space, valid ... should not be valid (see issue #11833) - u16cpy(str, u"index(b 4)"); + // 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)); - 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, space, KmnCompilerMessages::ERROR_StoreDoesNotExist (see issue #11833) + u16cpy(str, u"index(b 4)"); // store name appears to be 'b 4' + fileKeyboard.cxStoreArray = 3u; + fileKeyboard.dpStoreArray = file_store; + EXPECT_EQ(KmnCompilerMessages::ERROR_StoreDoesNotExist, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // index, two commas and extra parameter, KmnCompilerMessages::ERROR_InvalidIndex + u16cpy(str, u"index(b,4,5)"); + fileKeyboard.cxStoreArray = 3u; + fileKeyboard.dpStoreArray = file_store; + EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidIndex, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // index, two-digit offset, valid u16cpy(str, u"index(b,42)"); fileKeyboard.cxStoreArray = 3u; fileKeyboard.dpStoreArray = file_store; @@ -1126,25 +1142,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; @@ -1225,6 +1241,94 @@ 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].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"); + + // 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)); + + // 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); + + // 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) // 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) diff --git a/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp index b67957bd31..75d74607cd 100644 --- a/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp @@ -166,3 +166,91 @@ TEST(km_u16_Test, u16tok_str_compare_to_strtok) { EXPECT_TRUE(!strcmp("ghi", strtok(nullptr, reverse_delim))); EXPECT_EQ(nullptr, strtok(nullptr, reverse_delim)); } + +TEST(km_u16_Test, u16ltrim) { + KMX_WCHAR str[LINESIZE]; + PKMX_WCHAR q; + + EXPECT_TRUE(!u16ltrim(nullptr)); + + std::map m{ + // input output + {u"", u"" }, + {u" ", u"" }, + {u" ", u"" }, + {u"abc", u"abc" }, + {u" abc", u"abc" }, + {u" abc", u"abc" }, + {u"abc ", u"abc " }, + {u"\tabc", u"abc" }, + {u"abc def", u"abc def"}, + {u" abc def", u"abc def"}, + }; + + for (auto i = m.begin(); i != m.end(); i++) { + u16cpy(str, i->first); + q = u16ltrim(str); + EXPECT_TRUE(!u16cmp(i->second, q)); + EXPECT_EQ(str, q); + } +} + +TEST(km_u16_Test, u16rtrim) { + KMX_WCHAR str[LINESIZE]; + PKMX_WCHAR q; + + EXPECT_TRUE(!u16rtrim(nullptr)); + + std::map m{ + // input output + {u"", u"" }, + {u" ", u"" }, + {u" ", u"" }, + {u"abc", u"abc" }, + {u"abc ", u"abc" }, + {u"abc ", u"abc" }, + {u" abc", u" abc" }, + {u"abc\t", u"abc" }, + {u"abc def", u"abc def"}, + {u"abc def ", u"abc def"}, + }; + + for (auto i = m.begin(); i != m.end(); i++) { + u16cpy(str, i->first); + q = u16rtrim(str); + EXPECT_TRUE(!u16cmp(i->second, q)); + EXPECT_EQ(str, q); + } +} + +TEST(km_u16_Test, u16trim) { + KMX_WCHAR str[LINESIZE]; + PKMX_WCHAR q; + + EXPECT_TRUE(!u16trim(nullptr)); + + std::map m{ + // input output + {u"", u"" }, + {u" ", u"" }, + {u" ", u"" }, + {u"abc", u"abc" }, + {u"abc ", u"abc" }, + {u"abc ", u"abc" }, + {u" abc", u"abc" }, + {u" abc", u"abc" }, + {u" abc ", u"abc" }, + {u" abc ", u"abc" }, + {u"abc\t", u"abc" }, + {u"\tabc", u"abc" }, + {u"abc def", u"abc def" }, + {u" abc def ", u"abc def"}, + }; + + for (auto i = m.begin(); i != m.end(); i++) { + u16cpy(str, i->first); + q = u16trim(str); + EXPECT_TRUE(!u16cmp(i->second, q)); + EXPECT_EQ(str, q); + } +} diff --git a/developer/src/packages.inc.sh b/developer/src/packages.inc.sh index 28f2513f97..0726209973 100644 --- a/developer/src/packages.inc.sh +++ b/developer/src/packages.inc.sh @@ -6,7 +6,6 @@ readonly PACKAGES=( common/web/keyman-version common/web/types - common/models/types core/include/ldml developer/src/common/web/utils developer/src/kmc-analyze diff --git a/developer/src/tike/build.sh b/developer/src/tike/build.sh index 1ab9dbe320..fd81ca692d 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 diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 75530e6bde..dcf54ce78c 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -8,7 +8,6 @@ /common/ @mcdurdin @rc-swag /core/ @mcdurdin @rc-swag /common/lexical-model-types/ @jahorton @mcdurdin -/common/models/ @jahorton @mcdurdin /common/schemas/ @mcdurdin @jahorton /common/test/ @mcdurdin @ermshiperete /common/web/ @jahorton @mcdurdin diff --git a/docs/build/linux-ubuntu.md b/docs/build/linux-ubuntu.md index 0107491dda..a45d9390ba 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 81b1bcd0e7..25b0c1f82a 100644 --- a/docs/build/macos.md +++ b/docs/build/macos.md @@ -91,16 +91,38 @@ 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. -```shell -brew install emscripten +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 ``` -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. +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 +export KEYMAN_USE_EMSDK=1 +``` ## Keyman for iOS Dependencies diff --git a/docs/build/windows.md b/docs/build/windows.md index dcb55ce4a4..3168bcd0ae 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: @@ -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 @@ -150,28 +123,36 @@ PowerShell. # for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) $ProgressPreference = 'SilentlyContinue' choco install git jq python ninja pandoc meson -refreshenv ``` **Environment variables**: -* [`KEYMAN_ROOT`](#keyman_root) -* `PATH`: add your Python scripts folder to your path: it will normally be `%appdata%\Python\Python310\Scripts`. + +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 -SET PATH=%path%;%appdata%\Python\Python310\Scripts +SETX KEYMAN_ROOT "c:\Projects\keyman\keyman" ``` -To check whether environment variables are set, run `SET ` in command -prompt. +> [!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. -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. +> [!TIP] +> +> To check whether environment variables are set, run `SET ` in command +> prompt. +> +> You can alternatively 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 @@ -181,32 +162,46 @@ 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 bash, run the following commands: -choco install emscripten --version 3.1.46 +```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, go ahead -and remove those paths from your PATH variable before continuing. +> ![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 (most likely %LocalAppData%\emsdk\upstream\emscripten) +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**: -* `EMSCRIPTEN_BASE`: `\upstream\emscripten` -After installing emscripten, you'll need to install node.js and openjdk. +```bat +SETX EMSCRIPTEN_BASE "\upstream\emscripten" +``` + +**Optional environment variables**: + +To let the Keyman build scripts control the version of Emscripten +installed on your computer: + +```bat +SETX KEYMAN_USE_EMSDK 1 +``` #### node.js @@ -214,30 +209,22 @@ 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 +```bat +nvm install 20.16.0 +nvm use 20.16.0 ``` -See [node.md](node.md) for more information. +**Optional environment variables**: -#### openjdk +To let the Keyman build scripts control the version of node.js installed +and active on your computer: -Use Powershell + Chocolatey to install OpenJDK: - -```ps1 -# Elevated PowerShell - -# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) -$ProgressPreference = 'SilentlyContinue' -choco install openjdk -``` +```bat +SETX KEYMAN_USE_NVM 1 +```` +See [node.md](node.md) for more information, including automatic selection +of appropriate node versions during builds. ### Windows Platform Dependencies @@ -260,8 +247,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 @@ -273,10 +259,12 @@ choco install openjdk ```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. @@ -284,55 +272,15 @@ choco install openjdk 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 + * 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) - -**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**: -* [Certificates](#certificates) -* [7-Zip](http://www.7-zip.org/), used for archiving build files -* [HTML Help Workshop](http://web.archive.org/web/20160201063255/http://download.microsoft.com/download/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe) note: Microsoft no longer offer this download... -* [WiX 3.11.1](https://github.com/wixtoolset/wix3/releases/tag/wix3111rtm) -* [CEF4Delphi_Binary](https://github.com/keymanapp/CEF4Delphi_Binary) repository - -```ps1 -# Elevated PowerShell -choco install 7zip html-help-workshop -choco install wixtoolset --version=3.11.1 -git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4Delphi_Binary -``` - -## Certificates - -In order to make a release build, you need to sign all the executables. See -[windows/src/README.md#Certificates](../../windows/src/README.md#Certificates) -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 @@ -349,41 +297,85 @@ 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: +The [`KEYMAN_CEF4DELPHI_ROOT`](#keyman_cef4delphi_root) variable is +used to specify the path to the CEF4Delphi binaries. ```bat -SET GIT_BASH_FOR_KEYMAN="C:\Program Files\Git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l +SETX KEYMAN_CEF4DELPHI_ROOT "c:\Projects\keyman\CEF4Delphi_Binary" ``` -You should verify the install location of Git on your computer as it may vary. +**Additional requirements for release builds**: +* [Certificates](#certificates) +* [7-Zip](http://www.7-zip.org/), used for archiving build files +* [HTML Help Workshop](http://web.archive.org/web/20160201063255/http://download.microsoft.com/download/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe) note: Microsoft no longer offer this download... +* [WiX 3.11.1](https://github.com/wixtoolset/wix3/releases/tag/wix3111rtm) +* [CEF4Delphi_Binary](https://github.com/keymanapp/CEF4Delphi_Binary) repository -### 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" +```ps1 +# Elevated PowerShell +choco install 7zip html-help-workshop +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 +* JDK 11 (Temurin11) + +#### JDK 11 + +Use Powershell + Chocolatey to install JDK 11: + +```ps1 +# Elevated PowerShell + +# for *much* faster download, hide progress bar (PowerShell/PowerShell#2138) +$ProgressPreference = 'SilentlyContinue' +choco install temurin11 +``` + +**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. + +#### Android Studio and friends + +```ps1 +# Elevated PowerShell +choco install androidstudio ant gradle maven android-sdk +``` + +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 + such as emulator images and SDK updates. + +## Certificates + +In order to make a release build, you need to sign all the executables. See +[windows/src/README.md#Certificates](../../windows/src/README.md#Certificates) +for details on how to create test code signing certificates or specify your own +certificates for the build. + ## Optional Tools * sentry-cli (optional) diff --git a/docs/file-formats/kmx-plus-file-format.md b/docs/file-formats/kmx-plus-file-format.md index 75e1df4295..a7b7e190a6 100644 --- a/docs/file-formats/kmx-plus-file-format.md +++ b/docs/file-formats/kmx-plus-file-format.md @@ -173,8 +173,8 @@ Then for each string: | ∆ | Bits | Name | Description | |---|------|---------------|-----------------------------------------------| -|16+| 32 | offset | off: Offset to string | -|20+| 32 | length | int: Length of string in UTF-16LE code units | +|12+| 32 | offset | off: Offset to string | +|16+| 32 | length | int: Length of string in UTF-16LE code units | After the string offset table comes the actual UTF-16LE data. There is a null (\u0000) after each string, which is _not_ included in the string length. @@ -362,14 +362,14 @@ For each element: | ∆ | Bits | Name | Description | |---|------|---------|------------------------------------------| -|32+| 32 | to | str: to string | -|36+| 32 | id | str: id string | -|40+| 32 | display | str: output display string | +|16+| 32 | to | str: to string | +|20+| 32 | id | str: id string | +|24+| 32 | display | str: output display string | 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 @@ -421,24 +423,28 @@ For each flicks in the flick list: | ∆ | Bits | Name | Description | |---|------|---------------- |----------------------------------------------------------| | 0+| 32 | count | int: number of flick elements in this flick | -|12+| 32 | flick | int: index into `flick` subtable for first flick element | -|16+| 32 | id | str: flick id | +| 4+| 32 | flick | int: index into `flick` subtable for first flick element | +| 8+| 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: | ∆ | Bits | Name | Description | |---|------|---------------- |----------------------------------------------------------| -| 0+| 32 | directions | list: index into `list` section with direction list | -| 8+| 32 | keyId | str: id of key | +| 0+| 32 | directions | list: index into `list` section with direction sequence | +| 4+| 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. @@ -456,7 +462,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 diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md index a9a2ead26b..cce0405050 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 | @@ -69,6 +68,6 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN_VERSION_JAVA | 11 | | KEYMAN_VERSION_LANGTAGS | 2024-05-22 | | KEYMAN_VERSION_LANGTAGS_SUBTAG_REGISTRY | 2024-05-16 | -| KEYMAN_VERSION_UNICODE | 15.1.0 | +| KEYMAN_VERSION_UNICODE | 16.0.0 | > ### This file is auto-generated by publish-minimum-versions.sh. Do not modify diff --git a/docs/npm-packages.md b/docs/npm-packages.md index 36e5d9a6de..494bec0044 100644 --- a/docs/npm-packages.md +++ b/docs/npm-packages.md @@ -19,13 +19,13 @@ Packages are **never** published from a developer's machine. * `@keymanapp/kmc-model` -- located at `developer/src/kmc-model` * `@keymanapp/kmc-model-info` -- located at `developer/src/kmc-model-info` * `@keymanapp/kmc-package` -- located at `developer/src/kmc-package` -* `@keymanapp/models-types` -- located at `common/models/types` ### Deprecated npm packages * `@keymanapp/developer-lexical-model-compiler` -- replaced by `@keymanapp/kmc` * `@keymanapp/lexical-model-compiler` -- replaced by `@keymanapp/kmc` -* `@keymanapp/lexical-model-types` -- replaced by `@keymanapp/models-types` +* `@keymanapp/lexical-model-types` -- replaced by `@keymanapp/common-types` +* `@keymanapp/models-types` -- replaced by `@keymanapp/common-types` * `@keymanapp/lmlayer-cli` -- replaced by `@keymanapp/kmc` * `@keymanapp/models-wordbreakers` -- published accidentally diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 5b77f79307..d1d134b249 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() + } } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Data/FontManager.swift index d64019cc27..6070de2c96 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,50 @@ 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. + * 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 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. + // 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 +96,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 +118,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 +173,53 @@ 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 { + var fontsToFind = fontNames + + UIFont.familyNames.forEach { familyName in + if fontsToFind.count == 0 { + return + } + + let familyFonts = UIFont.fontNames(forFamilyName: familyName) + + for font in familyFonts { + if fontsToFind.contains(font) { + fontsToFind.remove(font) + } + + if fontsToFind.count == 0 { + break + } + } } + + return fontsToFind } } diff --git a/ios/engine/KMEI/KeymanEngine/cs.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/cs.lproj/Localizable.strings index 86f138adff..0653ed3aeb 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/ios/engine/KMEI/KeymanEngine/it.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/it.lproj/Localizable.strings index 262a99756d..bd12409118 100644 --- a/ios/engine/KMEI/KeymanEngine/it.lproj/Localizable.strings +++ b/ios/engine/KMEI/KeymanEngine/it.lproj/Localizable.strings @@ -125,7 +125,7 @@ "kmp-error-missing-resource" = "Questo pacchetto non contiene la tastiera o il dizionario richiesto."; /* 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" = "Questo pacchetto richiede una nuova versione di Keyman."; /* Error opening a Keyman package - cannot parse contents */ "kmp-error-no-metadata" = "Questo pacchetto non è stato costruito correttamente - contenuti sconosciuti."; diff --git a/ios/engine/build.sh b/ios/engine/build.sh index 600b93154b..f6687f0a00 100755 --- a/ios/engine/build.sh +++ b/ios/engine/build.sh @@ -15,7 +15,7 @@ verify_on_mac builder_describe "Builds Keyman Engine for use on iOS devices - iPhone and iPad." \ "@/web/src/app/webview build" \ - "@/common/web/sentry-manager build" \ + "@/web/src/engine/sentry-manager build" \ "clean" \ "configure" \ "build" \ @@ -109,7 +109,7 @@ function update_bundle ( ) { cp "$KMW_PRODUCT/keymanweb-webview.js" "$BUNDLE_PATH/keymanweb-webview.js" cp "$KEYMAN_ROOT/node_modules/@sentry/browser/build/bundle.min.js" "$BUNDLE_PATH/sentry.min.js" - cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "$BUNDLE_PATH/keyman-sentry.js" + cp "$KEYMAN_ROOT/web/src/engine/sentry-manager/build/lib/index.js" "$BUNDLE_PATH/keyman-sentry.js" } # First things first - update our dependencies. @@ -140,4 +140,4 @@ function build_engine() { builder_run_action clean do_clean builder_run_action configure do_configure -builder_run_action build build_engine \ No newline at end of file +builder_run_action build build_engine diff --git a/linux/debian/control b/linux/debian/control index f729999731..fbe7066f5c 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -26,6 +26,7 @@ Build-Depends: pkgconf, python3-all (>= 3.5), python3-dbus, + python3-fonttools, python3-gi, python3-lxml, python3-magic, @@ -90,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), diff --git a/linux/keyman-config/keyman_config/__init__.py b/linux/keyman-config/keyman_config/__init__.py index 2bb3cdbaa2..26fbeddf7d 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/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index d82c8e06e5..fed67952ca 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -177,8 +177,8 @@ class InstallKmp(): # 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 5b3ab85ae8..ddef89241a 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -1,11 +1,15 @@ #!/usr/bin/python3 import logging +import os import struct import sys +from fontTools import ttLib from lxml import etree +from keyman_config.kmpmetadata import parsemetadata + # .kvk file format # KVK files are variable length files with variable sized structures. @@ -164,43 +168,43 @@ VKey_to_Iso = { } -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 +228,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("CFBundlePackageType APPL CFBundleShortVersionString - Keyman $(PRODUCT_VERSION) for macOS + $(PRODUCT_VERSION) CFBundleSignature ???? CFBundleURLTypes diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 3e2ffe2410..c99d632665 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 *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 @@ } 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 @@ 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 @@ - (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]; } } @@ -398,8 +409,8 @@ } - (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 +418,13 @@ 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 +452,30 @@ } - (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]; + } + [package releaseTempKMPFile]; + 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 +484,12 @@ [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,28 +565,6 @@ return _confirmKmpInstallAlertView; } -- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - os_log_debug([KMLogs uiLog], "User responded to NSAlert"); - if (alert == _deleteAlertView) { - if (returnCode == NSAlertFirstButtonReturn) { // Delete - [self deleteFileAtIndex:(__bridge NSNumber *)contextInfo]; - } - - _deleteAlertView = nil; - } - else if (alert == _confirmKmpInstallAlertView) { - KMPackage *package = (__bridge KMPackage *)contextInfo; - os_log_debug([KMLogs uiLog], "KMP - Temp file: %{public}@", package.getTempKmpFilename); - if (returnCode == NSAlertFirstButtonReturn) { // Install - [self installPackageFile: package.getTempKmpFilename]; - } - - [package releaseTempKMPFile]; - _confirmKmpInstallAlertView = nil; - } - // else, just a message - nothing to do. -} - - (void)deleteFileAtIndex:(NSNumber *) n { NSInteger index = [n integerValue]; NSString *path2Remove = nil; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/cs.lproj/preferences.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/cs.lproj/preferences.strings index 305368c9ca..9477b4c637 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/KMConfiguration/it.lproj/preferences.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/it.lproj/preferences.strings index e83bbe7c45..23333e41df 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/it.lproj/preferences.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/it.lproj/preferences.strings @@ -17,7 +17,7 @@ "JOK-JV-n8w.title" = "Indietro"; /* Keyboards button text */ -"MPN-9N-wWc.label" = "Keyboards"; +"MPN-9N-wWc.label" = "Tastiere"; /* text to explain verbose Console logging option */ "MrI-GM-7d6.title" = "Quando l'opzione di registrazione dettagliata è attiva, Keyman registrerà le azioni che potrebbero aiutare Keyman Supporto diagnosticare un problema. Il programma Console può essere utilizzato per vedere un registro dei messaggi da Keyman. In Console, filtrare per mostrare tutti i messaggi dal processo \"Keyman\". Questo registro può essere esportato e inviato al supporto di Keyman, se necessario."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h new file mode 100644 index 0000000000..35d3f427e6 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -0,0 +1,28 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Created by Shawn Schantz on 2024-07-30. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KMDataRepository : NSObject +// keymanDataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' +@property (readonly) NSURL *keymanDataDirectory; + +// keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' +@property (readonly) NSURL *keymanKeyboardsDirectory; + ++ (KMDataRepository *)shared; +- (void)createDataDirectoryIfNecessary; +- (void)createKeyboardsDirectoryIfNecessary; +- (BOOL)migrateData; +- (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 new file mode 100644 index 0000000000..4722a86fec --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -0,0 +1,230 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * 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" + +@interface KMDataRepository () +@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 keymanDataDirectory = _keymanDataDirectory; +@synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; +@synthesize documentsSubDirectory = _documentsSubDirectory; +@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.) + */ +NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; + ++ (KMDataRepository *)shared { + static KMDataRepository *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[KMDataRepository alloc] init]; + }); + return shared; +} + +- (NSURL *)documentsSubDirectory { + if (_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 dataLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "Documents subdirectory: '%{public}@'", documentsUrl); + _documentsSubDirectory = documentsUrl; + } + } + return _documentsSubDirectory; +} + +- (NSURL *)applicationSupportSubDirectory { + if (_applicationSupportSubDirectory == nil) { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; + + if (directoryError) { + os_log_error([KMLogs dataLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); + _applicationSupportSubDirectory = applicationSupportUrl; + } + } + return _applicationSupportSubDirectory; +} + +- (NSURL *)keymanDataDirectory { + if (_keymanDataDirectory == nil) { + NSURL *keymanDataUrl = [self.applicationSupportSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; + _keymanDataDirectory = keymanDataUrl; + } + return _keymanDataDirectory; +} + +- (NSURL *)keymanKeyboardsDirectory { + if (_keymanKeyboardsDirectory == nil) { + NSURL *keyboardsUrl = [self.keymanDataDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; + _keymanKeyboardsDirectory = keyboardsUrl; + } + return _keymanKeyboardsDirectory; +} + +/** + * 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 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 dataLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); + } + } else { + os_log_info([KMLogs dataLog], "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)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 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 dataLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); + } + } else { + os_log_info([KMLogs dataLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); + } +} + +- (NSURL *)obsoleteKeymanKeyboardsDirectory { + if (_obsoleteKeymanKeyboardsDirectory == nil) { + NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; + _obsoleteKeymanKeyboardsDirectory = keymanUrl; + } + 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; + BOOL exists = ([fileManager fileExistsAtPath:self.obsoleteKeymanKeyboardsDirectory.path isDirectory:&isDir]); + return exists; +} + +/** + * 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; + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL dataExistsInOldLocation = [self keyboardsExistInObsoleteDirectory]; + os_log_debug([KMLogs dataLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + + // 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 dataLog], "data migration failed: '%{public}@'", moveError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); + } + } + + return didMoveData; +} + +- (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/KMInfoWindow/KMInfoWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m index 6be446ddd2..32a8377b64 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m @@ -85,8 +85,6 @@ // 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/KMInfoWindow/cs.lproj/KMInfoWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/cs.lproj/KMInfoWindowController.strings index f3fe6a722b..4c9f26c0f8 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/KMInfoWindow/it.lproj/KMInfoWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/it.lproj/KMInfoWindowController.strings index 5d3902de43..3d2755066c 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/it.lproj/KMInfoWindowController.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/it.lproj/KMInfoWindowController.strings @@ -1,5 +1,5 @@ /* Package Information window title */ -"F0z-JX-Cv5.title" = "Package Information"; +"F0z-JX-Cv5.title" = "Informazioni pacchetto"; /* button text to show Details */ "Fvy-XJ-s38.label" = "Dettagli"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 4bf463c177..ce70724c3a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -23,8 +23,6 @@ typedef void(^PostEventCallback)(CGEventRef eventToPost); -extern NSString *const kKMSelectedKeyboardKey; -extern NSString *const kKMActiveKeyboardsKey; extern NSString *const kKeymanKeyboardDownloadCompletedNotification; typedef struct { @@ -99,8 +97,7 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; - (NSMenu *)menu; - (void)saveActiveKeyboards; -- (void)readPersistedOptions; -- (void)writePersistedOptions:(NSString *)storeKey withValue:(NSString* )value; +- (void)applyPersistedOptions; - (void)showAboutWindow; - (void)showOSK; - (void)showConfigurationWindow; @@ -126,7 +123,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 9189e39d58..78c6362de9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -6,16 +6,9 @@ // 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" #import "KMConfigurationWindowController.h" #import "KMDownloadKBWindowController.h" #import "ZipArchive.h" @@ -26,21 +19,6 @@ #import "KMLogs.h" @import Sentry; -/** 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"; @implementation NSString (VersionNumbers) @@ -83,7 +61,6 @@ NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboard @synthesize alwaysShowOSK = _alwaysShowOSK; id _lastServerWithOSKShowing = nil; -NSString* _keymanDataPath = nil; - (id)init { self = [super init]; @@ -168,9 +145,11 @@ NSString* _keymanDataPath = nil; [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }]; [KMLogs reportLogStatus]; - [self startSentry]; + [self setDefaultKeymanMenuItems]; + [self updateKeyboardMenuItems]; [self setPostLaunchKeymanSentryTags]; + // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; } - (void)startSentry { @@ -183,7 +162,6 @@ NSString* _keymanDataPath = nil; options.environment = keymanVersionInfo.sentryEnvironment; //options.debug = YES; }]; - } - (void)setPostLaunchKeymanSentryTags { @@ -407,7 +385,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (KMPackageReader *)packageReader { if (_packageReader == nil) { _packageReader = [[KMPackageReader alloc] init]; - [_packageReader setDebugMode:self.debugMode]; } return _packageReader; @@ -438,55 +415,17 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [_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"]; @@ -496,9 +435,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (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 { @@ -506,50 +443,27 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef _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 /** - * 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"]; - - NSFileManager *fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_keymanDataPath]) { - [fm createDirectoryAtPath:_keymanDataPath withIntermediateDirectories:YES attributes:nil error:nil]; - } - } - return _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) + * Returns the root folder where keyboards are stored */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { - _keyboardsPath = [self keymanDataPath]; + _keyboardsPath = [KMDataRepository shared].keymanKeyboardsDirectory.path; } return _keyboardsPath; @@ -557,10 +471,12 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (NSArray *)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"]) { @@ -664,7 +580,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (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) { @@ -675,8 +591,9 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (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]; @@ -691,52 +608,56 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (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); + return _selectedKeyboard; } - (void)setSelectedKeyboard:(NSString *)selectedKeyboard { + [[KMSettingsRepository shared] writeSelectedKeyboard:selectedKeyboard]; _selectedKeyboard = selectedKeyboard; - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:_selectedKeyboard forKey:kKMSelectedKeyboardKey]; - [userData synchronize]; } - (NSMutableArray *)activeKeyboards { if (!_activeKeyboards) { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - _activeKeyboards = [[userData arrayForKey:kKMActiveKeyboardsKey] mutableCopy]; - if (!_activeKeyboards) - _activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + os_log_debug([KMLogs dataLog], "initializing activeKeyboards"); + _activeKeyboards = [[KMSettingsRepository.shared readActiveKeyboards] mutableCopy]; } - + return _activeKeyboards; } - (void)saveActiveKeyboards { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:_activeKeyboards forKey:kKMActiveKeyboardsKey]; - [userData synchronize]; + os_log_debug([KMLogs dataLog], "saveActiveKeyboards"); + [KMSettingsRepository.shared writeActiveKeyboards:_activeKeyboards]; [self resetActiveKeyboards]; [self updateKeyboardMenuItems]; } - (void)clearActiveKeyboards { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:nil forKey:kKMActiveKeyboardsKey]; - [userData synchronize]; + [KMSettingsRepository.shared clearActiveKeyboards]; [self updateKeyboardMenuItems]; } +- (void)addActiveKeyboard:(NSString *) partialPath { + if (![self.activeKeyboards containsObject:partialPath]) { + os_log_debug([KMLogs keyboardLog], "addActiveKeyboard, adding '%{public}@' to list of active keyboards: ", partialPath); + [self.activeKeyboards addObject:partialPath]; + } +} + - (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; @@ -759,9 +680,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } if (found) { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:_activeKeyboards forKey:kKMActiveKeyboardsKey]; - [userData synchronize]; + [KMSettingsRepository.shared writeActiveKeyboards:_activeKeyboards]; } } @@ -781,8 +700,23 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (void)awakeFromNib { - [self setDefaultKeymanMenuItems]; - [self updateKeyboardMenuItems]; + [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 + */ +- (void)prepareStorage { + [KMDataRepository.shared createDataDirectoryIfNecessary]; + + if ([KMSettingsRepository.shared dataMigrationNeeded]) { + BOOL movedData = [KMDataRepository.shared migrateData]; + [KMSettingsRepository.shared convertSettingsForMigration]; + } + + [KMDataRepository.shared createKeyboardsDirectoryIfNecessary]; + [KMSettingsRepository.shared setDataModelVersionIfNecessary]; } - (void)setDefaultKeymanMenuItems { @@ -809,6 +743,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (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; @@ -830,6 +765,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (void)addDynamicKeyboardMenuItems { + os_log_debug([KMLogs startupLog], "addDynamicKeyboardMenuItems, entered"); BOOL didSetSelectedKeyboard = NO; NSInteger itag = KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG; NSString *keyboardMenuName = @""; @@ -841,7 +777,9 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef // 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; } @@ -871,10 +809,12 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (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]; @@ -885,7 +825,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [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 @@ -926,10 +866,13 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } 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]; @@ -943,45 +886,48 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; [self setContextBuffer:nil]; [self setSelectedKeyboard:path]; - [self readPersistedOptions]; + [self applyPersistedOptions]; if (kvk != nil && self.alwaysShowOSK) [self showOSK]; } -- (NSArray *)KMXFiles { - return [self KMXFilesAtPath:self.keyboardsPath]; +- (NSArray *)getKmxFilesInKeyboardsDirectory { + return [self getKmxFilesAtPath:self.keyboardsPath]; } -- (NSArray *)KMXFilesAtPath:(NSString *)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; 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; } -- (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; @@ -1037,6 +983,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef return _configWindow; } +// TODO: rewrite confusing pattern, multiple methods differing only by underscore - (NSWindowController *)aboutWindow_ { return _aboutWindow; } @@ -1119,14 +1066,19 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef * 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) { + else if (returnCode == NSModalResponseOK) { + os_log_debug([KMLogs uiLog], "downloadComplete, returnCode == NSModalResponseOK"); + [_downloadKBWindow close]; + 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 +1087,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [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 +1105,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (NSAlert *)downloadInfoView { + os_log_debug([KMLogs uiLog], "downloadInfoView"); if (_downloadInfoView == nil) { _downloadInfoView = [[NSAlert alloc] init]; [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-downloading", nil)]; @@ -1165,6 +1119,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (NSProgressIndicator *)progressIndicator { + os_log_debug([KMLogs uiLog], "progressIndicator"); if (_progressIndicator == nil) { _progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 300, 20)]; [_progressIndicator setIndeterminate:NO]; @@ -1184,38 +1139,51 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [self downloadKeyboardFromURL:url]; } + - (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]; + + /* + Open sheet off of config window, not the download window. + This is because, if the download is successful, + the download window will be closed by the sheet. + */ + [self.downloadInfoView beginSheetModalForWindow:self.configWindow.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]; + /* + Open sheet off of config window, same as above. + */ + [self.downloadInfoView beginSheetModalForWindow:self.configWindow.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]; @@ -1224,7 +1192,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef _receivedData = [[NSMutableData alloc] initWithLength:0]; _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; } - } } @@ -1257,6 +1224,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (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 +1232,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [_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 @@ -1330,7 +1299,9 @@ extern const CGKeyCode kProcessPendingBuffer; 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 @@ -1341,9 +1312,9 @@ extern const CGKeyCode kProcessPendingBuffer; 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]; @@ -1351,11 +1322,11 @@ extern const CGKeyCode kProcessPendingBuffer; } 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]; @@ -1363,10 +1334,10 @@ extern const CGKeyCode kProcessPendingBuffer; // 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; } } @@ -1379,7 +1350,7 @@ extern const CGKeyCode kProcessPendingBuffer; 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; } } @@ -1387,7 +1358,7 @@ extern const CGKeyCode kProcessPendingBuffer; 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; @@ -1396,11 +1367,12 @@ extern const CGKeyCode kProcessPendingBuffer; // 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]]; + [self addActiveKeyboard:partialPath]; } [self saveActiveKeyboards]; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index ab1b515e43..d102e69ca3 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; @@ -416,11 +417,11 @@ NSString* const kEasterEggKmxName = @"EnglishSpanish.kmx"; 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); - [self.appDelegate writePersistedOptions:key withValue:value]; + os_log_debug([KMLogs keyLog], "persistOptions, key: %{public}@, value: %{public}@", key, value); + [[KMSettingsRepository shared] writeOptionForSelectedKeyboard: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/KMPackageReader.h b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.h index 2c138d8215..008f0c025e 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 7406488a8a..12c0b3c564 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -44,9 +44,6 @@ typedef enum { - (instancetype)init { self = [super init]; - if (self) { - _debugMode = NO; - } return self; } @@ -69,15 +66,6 @@ typedef enum { return packageInfo; } -- (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 @@ -251,14 +239,12 @@ typedef enum { 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 +259,12 @@ typedef enum { 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 +278,17 @@ typedef enum { 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/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h new file mode 100644 index 0000000000..8d7b114f2b --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -0,0 +1,33 @@ +/* + * 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)dataMigrationNeeded; +- (void)convertSettingsForMigration; +- (void)setDataModelVersionIfNecessary; +- (NSString *)readSelectedKeyboard; +- (void)writeSelectedKeyboard:(NSString *)selectedKeyboard; +- (NSArray *)readActiveKeyboards; +- (void)writeActiveKeyboards: (NSArray *) keyboards; +- (void)clearActiveKeyboards; +- (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 new file mode 100644 index 0000000000..bbd87a40ec --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -0,0 +1,329 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * 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 store application settings. + */ + +#import "KMSettingsRepository.h" +#import "KMLogs.h" +#import "KMDataRepository.h" + +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/Keyman-Keyboards"; +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; +NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirectory; + +@implementation KMSettingsRepository + ++ (KMSettingsRepository *)shared +{ + static KMSettingsRepository *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[KMSettingsRepository alloc] init]; + }); + return shared; +} + +- (void)setDataModelVersionIfNecessary { + if (![self dataModelWithKeyboardsInLibrary]) { + [[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: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]; + 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 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 { + 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]; +} + +/** + * 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)dataModelWithKeyboardsInLibrary { + // [NSUserDefaults integerForKey] 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 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 kVersionStoreDataInLibraryDirectory is < 1, + */ +- (BOOL)dataMigrationNeeded { + BOOL keymanSettingsExist = [self settingsExist]; + os_log([KMLogs dataLog], "keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); + + BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; + os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); + + BOOL migrationNeeded = keymanSettingsExist && !keyboardsStoredInLibrary; + os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); + + return migrationNeeded; +} + +- (NSString *)readSelectedKeyboard { + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; +} + +- (void)writeSelectedKeyboard:(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; +} + +- (NSArray *)readActiveKeyboards { + os_log_debug([KMLogs dataLog], "KMSettingsRepository readActiveKeyboards"); + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + NSArray *keyboardsArray = [userData arrayForKey:kActiveKeyboardsKey]; + + // if the kActiveKeyboardsKey does not exist, then create an empty array + if (!keyboardsArray) { + os_log_debug([KMLogs dataLog], "KMActiveKeyboardsKey key not found in NSUserDefualts"); + keyboardsArray = [[NSArray alloc] init]; + } + return keyboardsArray; +} + +- (void)writeActiveKeyboards: (NSArray *) keyboards { + os_log_debug([KMLogs dataLog], "KMSettingsRepository writeActiveKeyboards"); + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:keyboards forKey:kActiveKeyboardsKey]; +} + +- (void)clearActiveKeyboards { + os_log_debug([KMLogs dataLog], "KMSettingsRepository clearActiveKeyboards"); + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:nil forKey:kActiveKeyboardsKey]; +} + + +/** + * returns dictionary of persisted options for the single selected keyboard + */ +- (NSDictionary *)readOptionsForSelectedKeyboard { + 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 selectedOptionsMap) { + NSString *value = [selectedOptionsMap 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 + * (options are stored in UserDefaults as a map of maps) + */ +- (NSDictionary *)readFullOptionsMap { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData dictionaryForKey:kPersistedOptionsKey]; +} + +- (void)writeFullOptionsMap:(NSDictionary *) fullOptionsMap { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:fullOptionsMap forKey:kPersistedOptionsKey]; +} + +- (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 convertOptionsPathsForMigration]; +} + +- (void)convertSelectedKeyboardPathForMigration { + NSString *selectedKeyboardPath = [self readSelectedKeyboard]; + if (selectedKeyboardPath != nil) { + NSString *newPathString = [self trimObsoleteKeyboardPath:selectedKeyboardPath]; + + if ([selectedKeyboardPath isNotEqualTo:newPathString]) { + [self writeSelectedKeyboard:newPathString]; + os_log_debug([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, 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 { + NSMutableArray *keyboards = [self activeKeyboards]; + NSMutableArray *convertedActiveKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + BOOL didConvert = NO; + + for (NSString *oldPath in keyboards) { + NSString *newPath = [self trimObsoleteKeyboardPath:oldPath]; + if ([oldPath isNotEqualTo:newPath]) { + [convertedActiveKeyboards addObject: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 { + // if, somehow, the path does not need converting then retain it in new array + [convertedActiveKeyboards addObject:oldPath]; + } + } + + // only write array to UserDefaults if we actually converted something + if (didConvert) { + [self writeActiveKeyboards:convertedActiveKeyboards]; + } +} + +- (void)convertOptionsPathsForMigration { + NSDictionary * optionsMap = [self readFullOptionsMap]; + 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 trimObsoleteKeyboardPath: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_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]; + } + } + if (optionsChanged) { + [self writeFullOptionsMap: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 diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m b/mac/Keyman4MacIM/Keyman4MacIM/KeySender.m index 417475c777..dc73edc11b 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 @@ const CGKeyCode kKeymanEventKeyCode = 0xFF; } /** - 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/Keyman4MacIM/Keyman4MacIM/Privacy/PrivacyConsent.m b/mac/Keyman4MacIM/Keyman4MacIM/Privacy/PrivacyConsent.m index 057f401cbe..89e6338852 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Privacy/PrivacyConsent.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/Privacy/PrivacyConsent.m @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * PrivacyConsent.m @@ -7,8 +7,8 @@ * Created by Shawn Schantz on 2022-09-22. * * Used to determine if the user has provided consent to the services it needs - * and, if not, to request that consent. For versions of macOS prior to 10.15, - * Keyman requires Accessibility access. For 10.15 and later, Keyman requires + * and, if not, to request that consent. For versions of macOS prior to 11.0, + * Keyman requires Accessibility access. For 11.0 and later, Keyman requires * PostEvent access. Both are presented to the user as a need for Accessibility * when prompted by the system and are listed under Accessibility in the Privacy * tab in System Preferences, Security & Privacy. Both Accessibility and @@ -16,7 +16,11 @@ * that access is not explicitly needed as long as one of the others is provided * first. * - * Access this functionality through the shared instance. Call + * Note that Apple's documentation states that the PostEvent access APIs are + * available with macOS 10.15 (Catalina) but testing showed that it was only + * available with macOS 11.0 (Big Sur). This is documented with issue #12295. + * + * To access this functionality, used the shared instance. Call * requestPrivacyAccess which will check whether access has been granted. If * not, then a dialog will be presented to the user to inform them that * Accessibility is required. Dismissing this dialog will trigger the API call @@ -24,7 +28,7 @@ * only happen once. If the user was prompted earlier and did not grant access, * they can still go to System Preferences and enable it there. So it is useful * to prompt with a dialog from Keyman at startup. Without Accessibility, the - * shift layer and the OSK do not work -- so Keyman is basically useless. + * shift layer and the OSK do not work. */ #import "PrivacyConsent.h" @@ -43,7 +47,7 @@ } /** - * For macOS earlier than 10.15: check for Accessibility access. + * For macOS earlier than 11.0: check for Accessibility access. */ - (BOOL)checkAccessibility { @@ -66,7 +70,7 @@ BOOL hasAccessibility = NO; // check if we already have accessibility - if (@available(macOS 10.15, *)) { + if (@available(macOS 11.0, *)) { hasAccessibility = [self checkPostEventAccess]; } else { hasAccessibility = [self checkAccessibility]; @@ -85,7 +89,7 @@ } /** - * For macOS earlier than 10.15: request Accessibility access. + * For macOS earlier than 11.0: request Accessibility access. */ - (void)requestAccessibility { @@ -113,18 +117,18 @@ */ - (void)configureDialogForOsVersion { - if (@available(macOS 10.15, *)) { - [self configureDialogForCatalinaAndLater]; + if (@available(macOS 11.0, *)) { + [self configureDialogForBigSurAndLater]; } else { - [self configureDialogForPreCatalina]; + [self configureDialogForPreBigSur]; } } /** - * Initialize privacy dialog for macOS versions prior to Catalina (10.15). + * Initialize privacy dialog for macOS versions prior to Big Sur (11.0). * In this case, request Accessibility access, not PostEvent. */ -- (void)configureDialogForPreCatalina { +- (void)configureDialogForPreBigSur { void (^consentPrompt)(void) = ^(void) { [self requestAccessibility]; @@ -136,10 +140,10 @@ } /** - * Initialize privacy dialog for macOS versions of Catalina (10.15) or later. + * Initialize privacy dialog for macOS versions of Big Sur (11.0) or later. * In this case, request PostEvent access, not Accessibility. */ -- (void)configureDialogForCatalinaAndLater { +- (void)configureDialogForBigSurAndLater { void (^consentPrompt)(void) = ^(void) { [self requestPostEventAccess]; @@ -160,7 +164,7 @@ /** * Check whether the user has allowed ListenEvent access. - * Only available with macOS Catalina (10.15) or later. + * Only available with macOS Big Sur (11.0) or later. * * If user has granted Accessibility or PostEvent access, then * ListenEvent access is also granted. @@ -170,18 +174,18 @@ BOOL hasAccess = NO; // below checks for ListenEvent access - if (@available(macOS 10.15, *)) { + if (@available(macOS 11.0, *)) { hasAccess = CGPreflightListenEventAccess(); os_log([KMLogs privacyLog], "CGPreflightListenEventAccess() returned %@", hasAccess ? @"YES" : @"NO"); } else { - os_log([KMLogs privacyLog], "CGPreflightListenEventAccess not available before macOS version 10.15"); + os_log([KMLogs privacyLog], "CGPreflightListenEventAccess not available before macOS version 11.0"); } return hasAccess; } /** * Check whether the user has allowed PostEvent access. - * Only available with macOS Catalina (10.15) or later. + * Only available with macOS Big Sur (11.0) or later. * * If user has granted Accessibility, then PostEvent access is also granted. */ @@ -190,46 +194,46 @@ BOOL hasAccess = NO; // below checks for PostEvent access - if (@available(macOS 10.15, *)) { + if (@available(macOS 11.0, *)) { hasAccess = CGPreflightPostEventAccess(); os_log([KMLogs privacyLog], "CGPreflightPostEventAccess() returned %@", hasAccess ? @"YES" : @"NO"); } else { - os_log([KMLogs privacyLog], "CGPreflightPostEventAccess not available before macOS version 10.15"); + os_log([KMLogs privacyLog], "CGPreflightPostEventAccess not available before macOS version 11.0"); } return hasAccess; } /** * Request ListenEvent access. - * Only available with macOS Catalina (10.15) or later. + * Only available with macOS Big Sur (11.0) or later. */ - (BOOL)requestListenEventAccess { BOOL granted = NO; - if (@available(macOS 10.15, *)) { + if (@available(macOS 11.0, *)) { granted = CGRequestListenEventAccess(); os_log([KMLogs privacyLog], "CGRequestListenEventAccess() returned %@", granted ? @"YES" : @"NO"); } else { - os_log([KMLogs privacyLog], "CGRequestListenEventAccess not available before macOS version 10.15"); + os_log([KMLogs privacyLog], "CGRequestListenEventAccess not available before macOS version 11.0"); } return granted; } /** * Request PostEvent access. - * Only available with macOS Catalina (10.15) or later. + * Only available with macOS Big Sur (11.0) or later. */ - (BOOL)requestPostEventAccess { BOOL granted = NO; - if (@available(macOS 10.15, *)) { + if (@available(macOS 11.0, *)) { granted = CGRequestPostEventAccess(); os_log([KMLogs privacyLog], "CGRequestPostEventAccess() returned %@", granted ? @"YES" : @"NO"); } else { - os_log([KMLogs privacyLog], "CGRequestPostEventAccess not available before macOS version 10.15"); + os_log([KMLogs privacyLog], "CGRequestPostEventAccess not available before macOS version 11.0"); } return granted; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.h b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.h index 6c80417faf..81cc4c3b34 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 0f62fa61a8..e83439794b 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m @@ -73,7 +73,7 @@ NSString *const kKMLegacyApps = @"KMLegacyApps"; -(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/Keyman4MacIM/cs.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/cs.lproj/Localizable.strings index f192a32368..31b54577b2 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/mac/Keyman4MacIM/Keyman4MacIM/it.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/it.lproj/Localizable.strings index adb2ea787e..f1264dd403 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/it.lproj/Localizable.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/it.lproj/Localizable.strings @@ -26,10 +26,10 @@ "message-keyboard-file-unreadable" = "Impossibile leggere il file Keyman «%@»."; /* Message displayed in Configuration window when keyboard cannot be loaded */ -"message-error-loading-keyboard" = "(error loading keyboard)"; +"message-error-loading-keyboard" = "(errore caricamento tastiera)"; /* Message displayed in Configuration window when keyboard metadata cannot be loaded */ -"message-error-unknown-metadata" = "unknown"; +"message-error-unknown-metadata" = "sconosciuto"; /* Button text to acknowledge that .kmp file could not be read */ "button-keyboard-file-unreadable" = "OK"; @@ -50,22 +50,25 @@ "button-download-complete" = "Fatto"; /* keyboards label in the Package Information window */ -"keyboards-label" = "Keyboards:"; +"keyboards-label" = "Tastiere:"; /* fonts label in the Package Information window */ -"fonts-label" = "Fonts:"; +"fonts-label" = "Caratteri:"; /* package version label in the Package Information window */ -"package-version-label" = "Package Version:"; +"package-version-label" = "Versione pacchetto:"; /* author label in the Package Information window */ -"author-label" = "Author:"; +"author-label" = "Autore:"; /* website label in the Package Information window */ -"website-label" = "Website:"; +"website-label" = "Sito web:"; /* copyright label in the Package Information window */ "copyright-label" = "Copyright:"; /* 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" = "Per funzionare correttamente, Keyman richiede funzionalità di accessibilità:\n\nConsenti l'accesso nelle preferenze di sistema, Sicurezza e Privacy.\nRiavvia il sistema."; + +/* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */ +"no-keyboard-configured-menu-placeholder" = "(Nessuna tastiera configurata)"; diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 8eb9d974ce..45e5e9855a 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -13,25 +13,32 @@ #import "KMInputMethodEventHandler.h" #import "AppleCompliantTestClient.h" #import "TextApiCompliance.h" +#import "KMSettingsRepository.h" +#import "KMDataRepository.h" +#import 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 +92,43 @@ KMInputMethodEventHandler *testEventHandler = nil; 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]; + 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 diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist b/mac/KeymanEngine4Mac/KeymanEngine4Mac/Info.plist index 970606a26a..3f42d3e63f 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 diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m index c1dfa75a87..5b31cf8ffa 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/KeyView.m @@ -41,7 +41,10 @@ static CGFloat const kRelativeModifierLabelHeight = 0.30f; - (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 logging, 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 @@ static CGFloat const kRelativeModifierLabelHeight = 0.30f; } - (void)drawRect:(NSRect)rect { + /* + // usually too much logging, 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 d81097d3e2..b1144a17fa 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKKey.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKKey.m @@ -14,7 +14,10 @@ - (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 logging, 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 f5154fa76d..512920e052 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m @@ -43,7 +43,7 @@ } - (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); diff --git a/oem/firstvoices/android/app/build.gradle b/oem/firstvoices/android/app/build.gradle index 0837c381ad..fa5dc5a127 100644 --- a/oem/firstvoices/android/app/build.gradle +++ b/oem/firstvoices/android/app/build.gradle @@ -73,12 +73,17 @@ java { } // how to configure the sentry android gradle plugin -sentry { - // Disables or enables the automatic configuration of Native symbols - uploadNativeSymbols = true +task publishSentry { + doLast { + println 'Publishing FirstVoices symbols to Sentry' + sentry { + // Disables or enables the automatic configuration of Native symbols + uploadNativeSymbols = true - // Does or doesn't include the source code of native code for Sentry - includeNativeSources = true + // Does or doesn't include the source code of native code for Sentry + includeNativeSources = true + } + } } String env_keys_json_file = System.getenv("oem_firstvoices_keys_json_file") diff --git a/oem/firstvoices/android/app/src/main/assets/setup/main.html b/oem/firstvoices/android/app/src/main/assets/setup/main.html index 331251981d..d9cc9893da 100644 --- a/oem/firstvoices/android/app/src/main/assets/setup/main.html +++ b/oem/firstvoices/android/app/src/main/assets/setup/main.html @@ -98,7 +98,7 @@ diff --git a/oem/firstvoices/android/build.sh b/oem/firstvoices/android/build.sh index e27be06974..260f882368 100755 --- a/oem/firstvoices/android/build.sh +++ b/oem/firstvoices/android/build.sh @@ -25,6 +25,7 @@ builder_describe "Builds FirstVoices for Android app." \ "configure" \ "build" \ "test Runs lint and tests." \ + "publish Publishes symbols to Sentry and the APK to the Play Store." \ "--ci Don't start the Gradle daemon. For CI" # parse before describe_outputs to check debug flags @@ -85,3 +86,6 @@ if builder_start_action test; then builder_finish_action success test fi + +builder_run_action publish ./gradlew $DAEMON_FLAG publishSentry publishReleaseApk + diff --git a/oem/firstvoices/keyboards.csv b/oem/firstvoices/keyboards.csv index cbede23edc..d1f1b92180 100644 --- a/oem/firstvoices/keyboards.csv +++ b/oem/firstvoices/keyboards.csv @@ -7,61 +7,62 @@ 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_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_haisla,Haisla,BC Coast,fv_haisla.js,2.1.3,has-Latn,Haisla (Latin) +fv,fv_halqemeylem,Halq'eméylem,BC Coast,fv_halqemeylem_kmw-9.0.js,9.2.1,hur-Latn,Halkomelem (Latin) +fv,fv_henqeminem,Hǝn̓q̓ǝmin̓ǝm,BC Coast,fv_henqeminem_kmw-9.0.js,10.2.1,hur-Latn,Halkomelem (Latin) +fv,fv_klahoose,Homalco-Klahoose-Sliammon,BC Coast,fv_klahoose_kmw-9.0.js,10.2,coo,Comox +fv,fv_hulquminum,Hul’q’umi’num’,BC Coast,fv_hulquminum_kmw-9.0.js,9.1,hur,Halkomelem 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_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_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_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_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,Lekwungen (Lək̓ʷəŋən),BC Coast,fv_lekwungen_kmw-9.0.js,1.0.1,str,Straits Salish +fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,11.0,clm,Clallam +fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.2.1,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.3,str,Straits Salish +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.1,sec-Latn,Sechelt (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.3,tsi-Latn,Tsimshian (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_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_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_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.2,caf-Latn,Southern Carrier (Latin) +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.2,thp,Thompson +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.1,oka,Okanagan +fv,fv_secwepemctsin,Secwepemctsín,BC Interior,fv_secwepemctsin_kmw-9.0.js,9.3,shs,Shuswap +fv,fv_stlatlimxec,Sƛ̓aƛ̓imxəc,BC Interior,fv_stlatlimxec_kmw-9.0.js,9.4,lil-Latn,Lillooet (Latin) +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.3,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.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.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.3,oj,Ojibwa +fv,fv_ojibwa_ifinal,ᐊᓂᔑᓇᐯᒧᐎᣙ (i-finals),Eastern Subarctic,fv_ojibwa_ifinal_kmw-9.0.js,1.0.2,oj,Ojibwa +fv,fv_ojibwa_ifinal_rdot,ᐊᓂᔑᓇᐯᒧᐏᣙ (i-finals right w-dot),Eastern Subarctic,fv_ojibwa_ifinal_rdot_kmw-9.0.js,1.0.2,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) -fv,fv_anishinaabemowin,Anishinaabemowin,Great Lakes - St. Lawrence,fv_anishinaabemowin_kmw-9.0.js,10.1,oj,Ojibwa +fv,fv_anishinaabemowin,Anishinaabemowin,Great Lakes - St. Lawrence,fv_anishinaabemowin_kmw-9.0.js,10.2,oj,Ojibwa fv,fv_bodewadminwen,Bodéwadminwen-Nishnabémwen,Great Lakes - St. Lawrence,fv_bodewadminwen_kmw-9.0.js,9.1.1,pot-Latn,Potawatomi (Latin) fv,fv_goyogohono,Goyogo̱hó:nǫ',Great Lakes - St. Lawrence,fv_goyogohono_kmw-9.0.js,9.2.1,cay-Latn,Cayuga (Latin) fv,fv_kanienkeha_e,Kanien'kéha-Kanyen'kéha,Great Lakes - St. Lawrence,fv_kanienkeha_e_kmw-9.0.js,9.1.3,moh-Latn,Mohawk (Latin) @@ -82,24 +83,24 @@ 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_dane_zaa_zaage,Dane-Z̲aa Z̲áágéʔ,Western Subarctic,fv_dane_zaa_zaage_kmw-9.0.js,9.4.1,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_tagizi_dene,Tāgizi Dene,Western Subarctic,fv_tagizi_dene_kmw-9.0.js,9.3,tgx-Latn,Tagish (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.4,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) fv,fv_dene_nt,ᑌᓀ ᔭᕱᐁ (Dene NT),Western Subarctic,fv_dene_nt_kmw-9.0.js,9.2.1,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics) diff --git a/oem/firstvoices/windows/src/xml/config.css b/oem/firstvoices/windows/src/xml/config.css index 28f0946476..763c7229a1 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/oem/firstvoices/windows/src/xml/keyman_support.xsl b/oem/firstvoices/windows/src/xml/keyman_support.xsl index dfd8c8445b..81f72fdb66 100644 --- a/oem/firstvoices/windows/src/xml/keyman_support.xsl +++ b/oem/firstvoices/windows/src/xml/keyman_support.xsl @@ -32,11 +32,19 @@
- + + + + +
- + + + + +