From 465c4bfdc0c934e60d9f1553a8779b6c06695b21 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 24 May 2024 09:24:53 -0500 Subject: [PATCH 01/14] feat(core): add a KMN_NO_ICU internal switch to start being able to turn off ICU - always set to 0 for now (keep ICU around) - set KMN_IN_LDML_TESTS in tests to keep ICU there for test and comparison - add core_icu.cpp and put some utils there. #9467 --- core/src/actions_normalize.cpp | 26 +-------- core/src/core_icu.cpp | 69 +++++++++++++++++++++++ core/src/core_icu.h | 61 +++++++++++++++++++- core/src/meson.build | 1 + core/src/util_normalize.cpp | 29 ---------- core/tests/unit/ldml/ldml_test_source.cpp | 3 + core/tests/unit/ldml/test_unicode.cpp | 3 + 7 files changed, 137 insertions(+), 55 deletions(-) create mode 100644 core/src/core_icu.cpp diff --git a/core/src/actions_normalize.cpp b/core/src/actions_normalize.cpp index 3384fda975..799b84425e 100644 --- a/core/src/actions_normalize.cpp +++ b/core/src/actions_normalize.cpp @@ -20,7 +20,6 @@ // forward declarations icu::UnicodeString context_items_to_unicode_string(km::core::context const *context); -km_core_usv *unicode_string_to_usv(icu::UnicodeString& src); /** * Normalize the output from an action to NFC, across the context | output @@ -182,7 +181,7 @@ bool km::core::actions_normalize( return false; } - auto new_output = unicode_string_to_usv(output_nfc); + auto new_output = km::core::util::unicode_string_to_usv(output_nfc); if(!new_output) { // error logging handled in unicode_string_to_usv return false; @@ -255,29 +254,6 @@ icu::UnicodeString context_items_to_unicode_string(km::core::context const *cont return result; } -/** - * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, - * nul-terminated - */ -km_core_usv *unicode_string_to_usv(icu::UnicodeString& src) { - UErrorCode icu_status = U_ZERO_ERROR; - - km_core_usv *dst = new km_core_usv[src.length() + 1]; - - src.toUTF32(reinterpret_cast(dst), src.length(), icu_status); - - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - DebugLog("toUTF32 failed with %x", icu_status); - delete[] dst; - return nullptr; - } - - dst[src.length()] = 0; - return dst; -} - - /** * Refresh app_context to match the cached_context. Does not do normalization, diff --git a/core/src/core_icu.cpp b/core/src/core_icu.cpp new file mode 100644 index 0000000000..e8b1e4ae6b --- /dev/null +++ b/core/src/core_icu.cpp @@ -0,0 +1,69 @@ +/* + Copyright: © SIL International. + Description: Common LDML utilities + Create Date: 24 May 2024 + Authors: Steven R. Loomis +*/ + +#include "core_icu.h" + +#if !KMN_NO_ICU + +namespace km { +namespace core { +namespace util { + +/** + * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, + * nul-terminated + */ +km_core_usv *unicode_string_to_usv(icu::UnicodeString& src) { + UErrorCode icu_status = U_ZERO_ERROR; + + km_core_usv *dst = new km_core_usv[src.length() + 1]; + + src.toUTF32(reinterpret_cast(dst), src.length(), icu_status); + + assert(U_SUCCESS(icu_status)); + if(!U_SUCCESS(icu_status)) { + DebugLog("toUTF32 failed with %x", icu_status); + delete[] dst; + return nullptr; + } + + dst[src.length()] = 0; + return dst; +} + +/** + * Internal function to normalize with a specified mode. + * Note: that this function _does_ assert failure, so it is not + * required to assert its return code. The return is provided so + * that callers can exit (such as making no change) if there was failure. + * + * Also note that "failure" here is something catastrophic: ICU not initialized, + * or, more likely, some low memory situation. Does not fail on "bad" data. + * @param n the ICU Normalizer to use + * @param str input/output string + * @param status error code, must be initialized on input + * @return false if failure + */ +bool normalize(const icu::Normalizer2 *n, std::u16string &str, UErrorCode &status) { + UASSERT_SUCCESS(status); + assert(n != nullptr); + icu::UnicodeString dest; + icu::UnicodeString src = icu::UnicodeString(str.data(), (int32_t)str.length()); + n->normalize(src, dest, status); + // the next line here will assert + if (UASSERT_SUCCESS(status)) { + str.assign(dest.getBuffer(), dest.length()); + } + return U_SUCCESS(status); +} + + +} +} +} /* end km::core::util */ + +#endif diff --git a/core/src/core_icu.h b/core/src/core_icu.h index d95d198637..dadf4e8448 100644 --- a/core/src/core_icu.h +++ b/core/src/core_icu.h @@ -3,8 +3,32 @@ */ #pragma once +#define KMN_NO_ICU 0 /* Temporary - keep ICU in for now while checking out build issues */ + +#ifdef __EMSCRIPTEN__ +// define this in tests to keep ICU around +# if !defined(KMN_IN_LDML_TESTS) +# if !defined(KMN_NO_ICU) +// under wasm, turn off ICU except in tests. +# define KMN_NO_ICU 1 +# endif +# endif +#elif !defined(KMN_NO_ICU) +# define KMN_NO_ICU 0 +#endif + +#if KMN_NO_ICU + +// NO ICU + +// any shims needed here for disabling ICU + +#else + +// YES ICU + #if !defined(HAVE_ICU4C) -#error icu4c is required for this code +# error icu4c is required for this code #endif #define U_FALLTHROUGH @@ -12,6 +36,7 @@ #include "unicode/unistr.h" #include "unicode/normalizer2.h" +#include "keyman_core.h" #include "debuglog.h" #include @@ -31,3 +56,37 @@ inline bool uassert_success(const char *file, int line, const char *function, UE * the first assert is for debug builds, the second triggers the debuglog and has the return value. * */ #define UASSERT_SUCCESS(status) (assert(U_SUCCESS(status)), uassert_success(__FILE__, __LINE__, __FUNCTION__, status)) + +// ------------------ some ICU C++ utilities ---------------------------- + +namespace km { +namespace core { +namespace util { + +/** + * Convert a UnicodeString to a km_core_usv array + * @return the 0-terminated array. Caller owns storage. + */ +km_core_usv *unicode_string_to_usv(icu::UnicodeString& src); + +/** + * Internal function to normalize with a specified mode. + * Note: that this function _does_ assert failure, so it is not + * required to assert its return code. The return is provided so + * that callers can exit (such as making no change) if there was failure. + * + * Also note that "failure" here is something catastrophic: ICU not initialized, + * or, more likely, some low memory situation. Does not fail on "bad" data. + * @param n the ICU Normalizer to use + * @param str input/output string + * @param status error code, must be initialized on input + * @return false if failure + */ +bool normalize(const icu::Normalizer2 *n, std::u16string &str, UErrorCode &status); + + +} +} +} + +#endif /* KMN_NO_ICU */ diff --git a/core/src/meson.build b/core/src/meson.build index e8008fb78a..5632c22257 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -60,6 +60,7 @@ kmx_files = files( 'km_core_processevent_api.cpp', 'jsonpp.cpp', 'util_normalize.cpp', + 'core_icu.cpp', 'ldml/ldml_processor.cpp', 'ldml/ldml_transforms.cpp', 'ldml/ldml_markers.cpp', diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index c21b75ea5b..c0367b5ef9 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -27,35 +27,6 @@ namespace km { namespace core { namespace util { -#ifndef __EMSCRIPTEN__ - -/** - * Internal function to normalize with a specified mode. - * Note: that this function _does_ assert failure, so it is not - * required to assert its return code. The return is provided so - * that callers can exit (such as making no change) if there was failure. - * - * Also note that "failure" here is something catastrophic: ICU not initialized, - * or, more likely, some low memory situation. Does not fail on "bad" data. - * @param n the ICU Normalizer to use - * @param str input/output string - * @param status error code, must be initialized on input - * @return false if failure - */ -static bool normalize(const icu::Normalizer2 *n, std::u16string &str, UErrorCode &status) { - UASSERT_SUCCESS(status); - assert(n != nullptr); - icu::UnicodeString dest; - icu::UnicodeString src = icu::UnicodeString(str.data(), (int32_t)str.length()); - n->normalize(src, dest, status); - // the next line here will assert - if (UASSERT_SUCCESS(status)) { - str.assign(dest.getBuffer(), dest.length()); - } - return U_SUCCESS(status); -} -#endif - bool normalize_nfd(std::u32string &str) { std::u16string rstr = km::core::kmx::u32string_to_u16string(str); if(!km::core::util::normalize_nfd(rstr)) { diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp index 439236fd77..f2cca9ee5f 100644 --- a/core/tests/unit/ldml/ldml_test_source.cpp +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -18,6 +18,9 @@ #include +// Ensure that ICU gets included even on wasm. +#define KMN_IN_LDML_TESTS + #include // for char to vk mapping tables #include // for surrogate pair macros #include diff --git a/core/tests/unit/ldml/test_unicode.cpp b/core/tests/unit/ldml/test_unicode.cpp index 77ae0202ac..ae41853153 100644 --- a/core/tests/unit/ldml/test_unicode.cpp +++ b/core/tests/unit/ldml/test_unicode.cpp @@ -10,6 +10,9 @@ #include #include +// Ensure that ICU gets included even on wasm. +#define KMN_IN_LDML_TESTS + #include "keyman_core.h" #include "path.hpp" From a80a0a7dcba90b2c33cbd2ad6ad57f08cfd1feec Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 24 May 2024 10:42:57 -0500 Subject: [PATCH 02/14] feat(core): move more normalization logic into JS - add a normalize_nfd() which takes a single codepoint - temporarily keep ICU in actions_normalize.cpp and ldml_transforms.cpp - expand wasm opts in unit tests --- core/src/actions_normalize.cpp | 30 ++++++++++++++++++++++ core/src/core_icu.h | 2 -- core/src/ldml/ldml_markers.cpp | 35 ++++++++++--------------- core/src/ldml/ldml_transforms.cpp | 2 ++ core/src/util_normalize.cpp | 41 ++++++++++++++++++++++++++++++ core/src/util_normalize.hpp | 3 +++ core/tests/unit/kmnkbd/meson.build | 2 +- 7 files changed, 90 insertions(+), 25 deletions(-) diff --git a/core/src/actions_normalize.cpp b/core/src/actions_normalize.cpp index 799b84425e..8a7cf57eaf 100644 --- a/core/src/actions_normalize.cpp +++ b/core/src/actions_normalize.cpp @@ -1,3 +1,5 @@ +// TEMP +#define KMN_NO_ICU 0 /* Copyright: © 2024 SIL International. Description: Implementation of the action output normalization. @@ -21,6 +23,34 @@ icu::UnicodeString context_items_to_unicode_string(km::core::context const *context); + +// TEMP +namespace km { +namespace core { +namespace util { + +/** + * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, + * nul-terminated + */ +inline km_core_usv *unicode_string_to_usv(icu::UnicodeString& src) { + UErrorCode icu_status = U_ZERO_ERROR; + + km_core_usv *dst = new km_core_usv[src.length() + 1]; + + src.toUTF32(reinterpret_cast(dst), src.length(), icu_status); + + assert(U_SUCCESS(icu_status)); + if(!U_SUCCESS(icu_status)) { + DebugLog("toUTF32 failed with %x", icu_status); + delete[] dst; + return nullptr; + } + + dst[src.length()] = 0; + return dst; +} +}}} /** * Normalize the output from an action to NFC, across the context | output * boundary, fixing up the app_context and the output actions to take into diff --git a/core/src/core_icu.h b/core/src/core_icu.h index dadf4e8448..45941bc8b8 100644 --- a/core/src/core_icu.h +++ b/core/src/core_icu.h @@ -3,8 +3,6 @@ */ #pragma once -#define KMN_NO_ICU 0 /* Temporary - keep ICU in for now while checking out build issues */ - #ifdef __EMSCRIPTEN__ // define this in tests to keep ICU around # if !defined(KMN_IN_LDML_TESTS) diff --git a/core/src/ldml/ldml_markers.cpp b/core/src/ldml/ldml_markers.cpp index b1d4604dbc..5d4fb4ff46 100644 --- a/core/src/ldml/ldml_markers.cpp +++ b/core/src/ldml/ldml_markers.cpp @@ -268,15 +268,14 @@ add_pending_markers( marker_map *markers, marker_list &last_markers, const std::u32string::const_iterator &last, - const std::u32string::const_iterator &end, - const icu::Normalizer2 *nfd) { + const std::u32string::const_iterator &end) { // quick check to see if there's no work to do. if(markers == nullptr) { return; } /** which character this marker is 'glued' to. */ char32_t marker_ch; - icu::UnicodeString decomposition; + std::u32string decomposition; if (last == end) { // at end of text, so use a special value to indicate 'EOT'. marker_ch = MARKER_BEFORE_EOT; @@ -285,17 +284,13 @@ add_pending_markers( // if the character is composed, we need to use the first decomposed char // as the 'glue'. - if(!nfd->getDecomposition(ch, decomposition)) { + if(!km::core::util::normalize_nfd(ch, decomposition)) { // char does not have a decomposition - so it may be used for the glue - marker_ch = ch; - decomposition.remove(); // no other entries needed - } else { - // 'glue' is the first codepoint of the decomposition. - marker_ch = decomposition.char32At(0); - if (decomposition.countChar32() == 1) { - decomposition.remove(); // no other entries needed - } // else: will add the remainder below + // the 'if' is only for the assertions here. + assert(decomposition.length() == 1); // should be a single UTF-32 char + assert(decomposition.at(0) == ch); // should be the same char } + marker_ch = decomposition.at(0); // always the first char } markers->emplace_back(marker_ch); // now, update the map with these markers (in order) on this character. @@ -304,10 +299,10 @@ add_pending_markers( markers->emplace_back(marker_ch, *i); } // add any further entries due to decomposition - if (!decomposition.isEmpty()) { - // We already added the base char above, add teh rest - for (auto i=1; iemplace_back(decomposition.char32At(i)); + if (decomposition.length() > 1) { + // We already added the base char above, add the rest + for (size_t i=1; iemplace_back(decomposition.at(i)); } } // clear the list @@ -318,10 +313,6 @@ std::u32string remove_markers(const std::u32string &str, marker_map *markers, marker_encoding encoding) { std::u32string out; marker_list last_markers; - UErrorCode status = U_ZERO_ERROR; - const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); - UASSERT_SUCCESS(status); - auto last = str.begin(); // points to the part of the string after the last matched marker for (auto i = str.begin(); i != str.end();) { auto marker_no = parse_next_marker(i, str.end(), encoding); @@ -329,7 +320,7 @@ remove_markers(const std::u32string &str, marker_map *markers, marker_encoding e // add any markers found before this entry, but only if there is intervening // text. This prevents the sentinel or the '\u' from becoming the attachment char. if (i != last) { - add_pending_markers(markers, last_markers, last, str.end(), nfd); + add_pending_markers(markers, last_markers, last, str.end()); out.append(last, i); // append any non-marker text since the end of the last marker last = i; // advance over text we've already appended } @@ -347,7 +338,7 @@ remove_markers(const std::u32string &str, marker_map *markers, marker_encoding e // add any remaining pending markers. // if last == str.end() then this wil be MARKER_BEFORE_EOT // otherwise it will be the glue character - add_pending_markers(markers, last_markers, last, str.end(), nfd); + add_pending_markers(markers, last_markers, last, str.end()); // get the suffix between the last marker and the end (could be nothing) out.append(last, str.end()); return out; diff --git a/core/src/ldml/ldml_transforms.cpp b/core/src/ldml/ldml_transforms.cpp index 7b161ff4f9..42953c604e 100644 --- a/core/src/ldml/ldml_transforms.cpp +++ b/core/src/ldml/ldml_transforms.cpp @@ -1,3 +1,5 @@ +// TEMP +#define KMN_NO_ICU 0 /* Copyright: © SIL International. Description: This is an implementation of the LDML keyboard spec 3.0. diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index c0367b5ef9..98fc3e5fb8 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -13,6 +13,7 @@ #ifdef __EMSCRIPTEN__ #include #include "utfcodec.hpp" +#include // JS implementations EM_JS(char*, NormalizeNFD, (const char* input), { @@ -62,6 +63,10 @@ bool normalize_nfd(std::u16string &str) { * Normalize the input string using ICU, out of place */ bool normalize_nfd(km_core_cu const * src, std::u16string &dst) { +#ifdef __EMSCRIPTEN__ + dst = std::u16string(src); + return normalize_nfd(dst); // vector to above fcn +#else UErrorCode icu_status = U_ZERO_ERROR; const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(icu_status); assert(U_SUCCESS(icu_status)); @@ -80,6 +85,42 @@ bool normalize_nfd(km_core_cu const * src, std::u16string &dst) { dst.assign(udst.getBuffer(), udst.length()); return true; +#endif +} + +bool +normalize_nfd(km_core_usv cp, std::u32string &dst) { + // set the output string to the original string + dst.clear(); + dst.append(1, cp); +#ifdef __EMSCRIPTEN__ + auto str16 = convert(dst); + if (!normalize_nfd(str16)) { + return false; // failed, retain original str + } else { + dst = convert(str16); + return true; + } +#else + UErrorCode icu_status = U_ZERO_ERROR; + const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(icu_status); + assert(U_SUCCESS(icu_status)); + if (!U_SUCCESS(icu_status)) { + // TODO: log the failure code + return false; + } + icu::UnicodeString decomposition; + if (!nfd->getDecomposition(cp, decomposition)) { + return false; // no error, just no decomposition + } else { + dst.clear(); + auto len = decomposition.countChar32(); + for (int i = 0; i < len; i++) { + dst.append(1, decomposition.char32At(i)); + } + return true; + } +#endif } } diff --git a/core/src/util_normalize.hpp b/core/src/util_normalize.hpp index 90ed650b08..d417617565 100644 --- a/core/src/util_normalize.hpp +++ b/core/src/util_normalize.hpp @@ -23,6 +23,9 @@ bool normalize_nfd(std::u16string &str); /** normalize src to dst in NFD. @return false on failure */ bool normalize_nfd(km_core_cu const * src, std::u16string &dst); +/** normalize (decompose) a single cp to string. @return false on failure */ +bool normalize_nfd(km_core_usv cp, std::u32string &dst); + } } } diff --git a/core/tests/unit/kmnkbd/meson.build b/core/tests/unit/kmnkbd/meson.build index 0adda0a35e..7285b9bf11 100644 --- a/core/tests/unit/kmnkbd/meson.build +++ b/core/tests/unit/kmnkbd/meson.build @@ -34,7 +34,7 @@ test_path = join_paths(meson.current_build_dir(), '..', 'kmx') tests_flags = [] if cpp_compiler.get_id() == 'emscripten' - tests_flags += ['-lnodefs.js', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] + tests_flags += ['-lnodefs.js', wasm_exported_runtime_methods] endif foreach t : tests From 5d420248eba9fe12211c1b8127337964a35c3d2f Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 24 May 2024 18:20:30 -0500 Subject: [PATCH 03/14] feat(core): move more normalization logic into JS - major redo of actions_normalize - into UTF-32 and not using ICU directly - add some utilities: u32len, u32dup, context_items_from_utf32 #9467 --- core/src/actions_normalize.cpp | 152 ++++++++++--------------------- core/src/context.hpp | 24 +++++ core/src/core_icu.cpp | 13 ++- core/src/core_icu.h | 1 + core/src/km_core_context_api.cpp | 7 ++ core/src/kmx/kmx_xstring.cpp | 14 +++ core/src/kmx/kmx_xstring.h | 3 + core/src/util_normalize.cpp | 126 ++++++++++++++++++++++--- core/src/util_normalize.hpp | 18 ++++ 9 files changed, 239 insertions(+), 119 deletions(-) diff --git a/core/src/actions_normalize.cpp b/core/src/actions_normalize.cpp index 8a7cf57eaf..78bada7b6f 100644 --- a/core/src/actions_normalize.cpp +++ b/core/src/actions_normalize.cpp @@ -1,5 +1,3 @@ -// TEMP -#define KMN_NO_ICU 0 /* Copyright: © 2024 SIL International. Description: Implementation of the action output normalization. @@ -17,40 +15,13 @@ #include "state.hpp" #include "option.hpp" #include "debuglog.h" -#include "core_icu.h" +#include "util_normalize.hpp" +#include "utfcodec.hpp" +#include "kmx/kmx_xstring.h" -// forward declarations +// forward declaration +bool context_items_to_unicode_string(km::core::context const *context, std::u32string &str); -icu::UnicodeString context_items_to_unicode_string(km::core::context const *context); - - -// TEMP -namespace km { -namespace core { -namespace util { - -/** - * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, - * nul-terminated - */ -inline km_core_usv *unicode_string_to_usv(icu::UnicodeString& src) { - UErrorCode icu_status = U_ZERO_ERROR; - - km_core_usv *dst = new km_core_usv[src.length() + 1]; - - src.toUTF32(reinterpret_cast(dst), src.length(), icu_status); - - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - DebugLog("toUTF32 failed with %x", icu_status); - delete[] dst; - return nullptr; - } - - dst[src.length()] = 0; - return dst; -} -}}} /** * Normalize the output from an action to NFC, across the context | output * boundary, fixing up the app_context and the output actions to take into @@ -94,32 +65,12 @@ bool km::core::actions_normalize( cached_context. */ - /* - Initialization - */ - - UErrorCode icu_status = U_ZERO_ERROR; - const icu::Normalizer2 *nfc = icu::Normalizer2::getNFCInstance(icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - DebugLog("getNFCInstance failed with %x", icu_status); + std::u32string output(actions.output); + std::u32string cached_context_string, app_context_string; + if (!context_items_to_unicode_string(cached_context, cached_context_string)) { return false; } - - const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - DebugLog("getNFDInstance failed with %x", icu_status); - return false; - } - - icu::UnicodeString output = icu::UnicodeString::fromUTF32(reinterpret_cast(actions.output), -1); - icu::UnicodeString cached_context_string = context_items_to_unicode_string(cached_context); - icu::UnicodeString app_context_string = context_items_to_unicode_string(app_context); - assert(!output.isBogus()); - assert(!cached_context_string.isBogus()); - assert(!app_context_string.isBogus()); - if(output.isBogus() || cached_context_string.isBogus() || app_context_string.isBogus()) { + if (!context_items_to_unicode_string(app_context, app_context_string)) { return false; } int nfu_to_delete = 0; @@ -127,20 +78,25 @@ bool km::core::actions_normalize( /* Further debug assertion of inputs */ - - assert(nfd->isNormalized(output, icu_status) && U_SUCCESS(icu_status)); - assert(nfd->isNormalized(cached_context_string, icu_status) && U_SUCCESS(icu_status)); + assert(km::core::util::is_nfd(output)); + assert(km::core::util::is_nfd(cached_context_string)); /* The keyboard processor will have updated the cached_context already, - applying the transform to it, so we need to rewind this. Remove the output - from cached_context_string to start + applying the transform to it, so we need to rewind this. + + Assert that 'cached_context_string' ends with 'output' + + Remove the output + from cached_context_string to start. */ assert(cached_context_string.length() >= output.length()); - int n = cached_context_string.length() - output.length(); + size_t n = cached_context_string.length() - output.length(); + // auto end_cached = cached_context_string.substr(n, output.length()); + // assert(end_cached == output); assert(cached_context_string.compare(n, output.length(), output) == 0); - cached_context_string.remove(n); + cached_context_string.resize(n); /* While cached_context is guaranteed to be normalized, actions->output may not @@ -148,21 +104,22 @@ bool km::core::actions_normalize( normalization in our output, we now need to look for a normalization boundary prior to the intersection of the cached_context and the output. */ - if(!output.isEmpty()) { - while(n > 0 && !nfd->hasBoundaryBefore(output[0])) { + if(!output.empty()) { + while(n > 0 && !km::core::util::has_nfd_boundary_before(output[0])) { // The output may interact with the context further in normalization. We // need to copy characters back further until we reach a normalization // boundary. // Remove last code point from the context ... - n = cached_context_string.moveIndex32(n, -1); - UChar32 chr = cached_context_string.char32At(n); - cached_context_string.remove(n); + auto len = cached_context_string.length(); + assert(len>0); + auto chr = cached_context_string.at(len-1); + cached_context_string.resize(len-1); // And prepend it to the output ... - output.insert(0, chr); + output.insert(0, 1, chr); } } @@ -177,25 +134,20 @@ bool km::core::actions_normalize( its normalized form matches the cached_context normalized form. */ - while(app_context_string.countChar32()) { - icu::UnicodeString app_context_nfd; - nfd->normalize(app_context_string, app_context_nfd, icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - DebugLog("nfd->normalize failed with %x", icu_status); + while(!app_context_string.empty()) { + auto app_context_nfd = app_context_string; + if(!km::core::util::normalize_nfd(app_context_nfd)) { + DebugLog("nfd->normalize failed"); return false; } - if(app_context_nfd.compare(cached_context_string) == 0) { + if(app_context_nfd == cached_context_string) { break; } - // remove the last UChar32 - int32_t lastUChar32 = app_context_string.length()-1; - // adjust pointer to get the entire char (i.e. so we don't slice a non-BMP char) - lastUChar32 = app_context_string.getChar32Start(lastUChar32); - // remove the UChar32 (1 or 2 code units) - app_context_string.remove(lastUChar32); + size_t len = app_context_string.length(); + // remove the cp at end + app_context_string.resize(len-1); nfu_to_delete++; } @@ -203,17 +155,15 @@ bool km::core::actions_normalize( Normalize our output string */ - icu::UnicodeString output_nfc; - nfc->normalize(output, output_nfc, icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - DebugLog("nfc->normalize failed with %x", icu_status); + auto output_nfc = output; + if(!km::core::util::normalize_nfc(output_nfc)) { + DebugLog("nfc->normalize failed"); return false; } - auto new_output = km::core::util::unicode_string_to_usv(output_nfc); - if(!new_output) { - // error logging handled in unicode_string_to_usv + auto new_output = km::core::util::string_to_usv(output_nfc); + assert(new_output != nullptr); + if(new_output == nullptr) { return false; } @@ -226,8 +176,8 @@ bool km::core::actions_normalize( app_context_string.append(output_nfc); km_core_context_item *app_context_items = nullptr; km_core_status status = KM_CORE_STATUS_OK; - if((status = context_items_from_utf16(app_context_string.getTerminatedBuffer(), &app_context_items)) != KM_CORE_STATUS_OK) { - DebugLog("context_items_from_utf16 failed with %x", status); + if((status = context_items_from_utf32(app_context_string.c_str(), &app_context_items)) != KM_CORE_STATUS_OK) { + DebugLog("context_items_from_string failed with %x", status); delete [] new_output; return false; } @@ -253,21 +203,19 @@ bool km::core::actions_normalize( /** * Helper to convert km_core_context list into a icu::UnicodeString */ -icu::UnicodeString context_items_to_unicode_string(km::core::context const *context) { - icu::UnicodeString nullString; - nullString.setToBogus(); +bool context_items_to_unicode_string(km::core::context const *context, std::u32string &str) { km_core_context_item *items = nullptr; km_core_status status; if((status = km_core_context_get(static_cast(context), &items)) != KM_CORE_STATUS_OK) { DebugLog("Failed to retrieve context with %s", status); - return nullString; + return false; } size_t buf_size = 0; if((status = context_items_to_utf32(items, nullptr, &buf_size)) != KM_CORE_STATUS_OK) { DebugLog("Failed to retrieve context size with %s", status); km_core_context_items_dispose(items); - return nullString; + return false; } km_core_usv *buf = new km_core_usv[buf_size]; @@ -275,13 +223,13 @@ icu::UnicodeString context_items_to_unicode_string(km::core::context const *cont DebugLog("Failed to retrieve context with %s", status); km_core_context_items_dispose(items); delete [] buf; - return nullString; + return false; } - auto result = icu::UnicodeString::fromUTF32(reinterpret_cast(buf), -1); + str = std::u32string(buf, buf_size - 1); // don't include terminating null km_core_context_items_dispose(items); delete [] buf; - return result; + return true; } diff --git a/core/src/context.hpp b/core/src/context.hpp index 91cc9688cd..f56d637eff 100644 --- a/core/src/context.hpp +++ b/core/src/context.hpp @@ -87,6 +87,30 @@ km_core_status context_items_from_utf16(km_core_cu const *text, km_core_context_item **out_ptr); +/** + * Convert a UTF32 encoded Unicode string into an array of `km_core_context_item` + * structures. Allocates memory as needed. + * + * @return km_core_status + * * `KM_CORE_STATUS_OK`: On success. + * * `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are + * null. + * * `KM_CORE_STATUS_NO_MEM`: In the event not enough memory can be + * allocated for the output buffer. + * * `KM_CORE_STATUS_INVALID_UTF`: In the event the UTF32 string cannot + * be decoded. + * + * @param text a pointer to a null terminated array of utf32 encoded data. + * @param out_ptr a pointer to the result variable: A pointer to the start of + * the `km_core_context_item` array containing the representation + * of the input string. Terminated with a type of + * `KM_CORE_CT_END`. Must be disposed of with + * `km_core_context_items_dispose`. + */ +km_core_status +context_items_from_utf32(km_core_usv const *text, + km_core_context_item **out_ptr); + /** * Convert a context item array into a UTF-16 encoded string placing it into the * supplied buffer of specified size, and return the number of code units diff --git a/core/src/core_icu.cpp b/core/src/core_icu.cpp index e8b1e4ae6b..cfaac47c50 100644 --- a/core/src/core_icu.cpp +++ b/core/src/core_icu.cpp @@ -24,8 +24,7 @@ km_core_usv *unicode_string_to_usv(icu::UnicodeString& src) { src.toUTF32(reinterpret_cast(dst), src.length(), icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { + if(!UASSERT_SUCCESS(icu_status)) { DebugLog("toUTF32 failed with %x", icu_status); delete[] dst; return nullptr; @@ -49,16 +48,20 @@ km_core_usv *unicode_string_to_usv(icu::UnicodeString& src) { * @return false if failure */ bool normalize(const icu::Normalizer2 *n, std::u16string &str, UErrorCode &status) { - UASSERT_SUCCESS(status); + if(!UASSERT_SUCCESS(status)) { + return false; + } assert(n != nullptr); icu::UnicodeString dest; icu::UnicodeString src = icu::UnicodeString(str.data(), (int32_t)str.length()); n->normalize(src, dest, status); // the next line here will assert - if (UASSERT_SUCCESS(status)) { + if (!UASSERT_SUCCESS(status)) { + return false; + } else { str.assign(dest.getBuffer(), dest.length()); + return true; } - return U_SUCCESS(status); } diff --git a/core/src/core_icu.h b/core/src/core_icu.h index 45941bc8b8..edfc209802 100644 --- a/core/src/core_icu.h +++ b/core/src/core_icu.h @@ -52,6 +52,7 @@ inline bool uassert_success(const char *file, int line, const char *function, UE /** * Assert an ICU4C UErrorCode * the first assert is for debug builds, the second triggers the debuglog and has the return value. + * @returns true on success * */ #define UASSERT_SUCCESS(status) (assert(U_SUCCESS(status)), uassert_success(__FILE__, __LINE__, __FUNCTION__, status)) diff --git a/core/src/km_core_context_api.cpp b/core/src/km_core_context_api.cpp index dd27ab7e5c..594080d8d0 100644 --- a/core/src/km_core_context_api.cpp +++ b/core/src/km_core_context_api.cpp @@ -115,6 +115,13 @@ context_items_from_utf16(km_core_cu const *text, } +km_core_status +context_items_from_utf32(km_core_usv const *text, + km_core_context_item **out_ptr) +{ + return _context_items_from(reinterpret_cast(text), out_ptr); +} + km_core_status context_items_to_utf8(km_core_context_item const *ci, char *buf, size_t * sz_ptr) { diff --git a/core/src/kmx/kmx_xstring.cpp b/core/src/kmx/kmx_xstring.cpp index a1d5ff518a..794ba26889 100644 --- a/core/src/kmx/kmx_xstring.cpp +++ b/core/src/kmx/kmx_xstring.cpp @@ -50,6 +50,15 @@ size_t km::core::kmx::u16len(const km_core_cu *p) { return i; } +size_t km::core::kmx::u32len(const km_core_usv *p) { + int i = 0; + while (*p) { + p++; + i++; + } + return i; +} + int km::core::kmx::u16cmp(const km_core_cu *p, const km_core_cu *q) { while (*p && *q) { if (*p != *q) return *p - *q; @@ -107,6 +116,11 @@ km_core_cu *km::core::kmx::u16dup(km_core_cu *src) { memcpy(dup, src, (u16len(src) + 1) * sizeof(km_core_cu)); return dup; } +km_core_usv *km::core::kmx::u32dup(const km_core_usv *src) { + km_core_usv *dup = new km_core_usv[u32len(src) + 1]; + memcpy(dup, src, (u32len(src) + 1) * sizeof(src[0])); + return dup; +} /* * int xstrlen( PKMX_BYTE p ); diff --git a/core/src/kmx/kmx_xstring.h b/core/src/kmx/kmx_xstring.h index a82e959e56..e17c003091 100644 --- a/core/src/kmx/kmx_xstring.h +++ b/core/src/kmx/kmx_xstring.h @@ -117,6 +117,9 @@ int u16ncmp(const km_core_cu *p, const km_core_cu *q, size_t count); km_core_cu *u16tok(km_core_cu *p, km_core_cu ch, km_core_cu **ctx); km_core_cu *u16dup(km_core_cu *src); +size_t u32len(const km_core_usv *p); +km_core_usv *u32dup(const km_core_usv *src); + //KMX_BOOL MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags); // --- implementation --- diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index 98fc3e5fb8..b5c3b860d1 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -22,12 +22,33 @@ EM_JS(char*, NormalizeNFD, (const char* input), { const nfd = instr.normalize("NFD"); return stringToNewUTF8(nfd); }); + +EM_JS(char*, NormalizeNFC, (const char* input), { + if (!input) return input; // pass through null + const instr = Module.UTF8ToString(input); + const nfd = instr.normalize("NFC"); + return stringToNewUTF8(nfd); +}); + #endif namespace km { namespace core { namespace util { +#ifndef __EMSCRIPTEN__ +inline const icu::Normalizer2 *getNFD(UErrorCode &status) { + const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); + UASSERT_SUCCESS(status); + return nfd; +} +inline const icu::Normalizer2 *getNFC(UErrorCode &status) { + const icu::Normalizer2 *nfc = icu::Normalizer2::getNFCInstance(status); + UASSERT_SUCCESS(status); + return nfc; +} +#endif + bool normalize_nfd(std::u32string &str) { std::u16string rstr = km::core::kmx::u32string_to_u16string(str); if(!km::core::util::normalize_nfd(rstr)) { @@ -38,6 +59,16 @@ bool normalize_nfd(std::u32string &str) { } } +bool normalize_nfc(std::u32string &str) { + std::u16string rstr = km::core::kmx::u32string_to_u16string(str); + if(!km::core::util::normalize_nfc(rstr)) { + return false; + } else { + str = km::core::kmx::u16string_to_u32string(rstr); + return true; + } +} + bool normalize_nfd(std::u16string &str) { #ifdef __EMSCRIPTEN__ std::string instr = convert(str); @@ -53,9 +84,26 @@ bool normalize_nfd(std::u16string &str) { return true; #else UErrorCode status = U_ZERO_ERROR; - const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); - UASSERT_SUCCESS(status); - return normalize(nfd, str, status); + return normalize(getNFD(status), str, status); +#endif +} + +bool normalize_nfc(std::u16string &str) { +#ifdef __EMSCRIPTEN__ + std::string instr = convert(str); + const char *in = instr.c_str(); + char *out = NormalizeNFC(in); + if (out == nullptr) { + assert(out != nullptr); + return false; + } + std::string outstr(out); + str = convert(outstr); + free(out); + return true; +#else + UErrorCode status = U_ZERO_ERROR; + return normalize(getNFC(status), str, status); #endif } @@ -67,19 +115,15 @@ bool normalize_nfd(km_core_cu const * src, std::u16string &dst) { dst = std::u16string(src); return normalize_nfd(dst); // vector to above fcn #else - UErrorCode icu_status = U_ZERO_ERROR; - const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - // TODO: log the failure code + UErrorCode status = U_ZERO_ERROR; + auto nfd = getNFD(status); + if (nfd == nullptr) { return false; } icu::UnicodeString udst; icu::UnicodeString usrc = icu::UnicodeString(src); - nfd->normalize(usrc, udst, icu_status); - assert(U_SUCCESS(icu_status)); - if(!U_SUCCESS(icu_status)) { - // TODO: log the failure code + nfd->normalize(usrc, udst, status); + if(!UASSERT_SUCCESS(status)) { return false; } @@ -123,6 +167,64 @@ normalize_nfd(km_core_usv cp, std::u32string &dst) { #endif } +bool is_nfd(const std::u16string& str) { +#ifdef __EMSCRIPTEN__ + std::u16string o = str; + normalize_nfd(o); + return (o == str); // false if changed +#else + UErrorCode status = U_ZERO_ERROR; + auto nfd = getNFD(status); + if (nfd == nullptr) return false; + auto ustr = icu::UnicodeString(false, str.c_str(), (int)str.length()); + auto result = nfd->isNormalized(ustr, status); + if (!UASSERT_SUCCESS(status)) { + return false; + } else { + return result; + } +#endif +} + +bool is_nfd(const std::u32string& str) { +#ifdef __EMSCRIPTEN__ + std::u32string o = str; + normalize_nfd(o); + return (o == str); // false if changed +#else + UErrorCode status = U_ZERO_ERROR; + auto nfd = getNFD(status); + if (nfd == nullptr) return false; + auto ustr = icu::UnicodeString::fromUTF32(reinterpret_cast(str.c_str()), (int)str.length()); + auto result = nfd->isNormalized(ustr, status); + if (!UASSERT_SUCCESS(status)) { + return false; + } else { + return result; + } +#endif +} + +bool has_nfd_boundary_before(km_core_usv cp) { +#ifdef __EMSCRIPTEN__ +#error TODO +#else + UErrorCode status = U_ZERO_ERROR; + auto nfd = getNFD(status); + if (nfd == nullptr) return false; + return nfd->hasBoundaryBefore(cp); +#endif +} + +/** + * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, + * nul-terminated + */ +km_core_usv *string_to_usv(const std::u32string& src) { + return km::core::kmx::u32dup(src.c_str()); +} + + } } } diff --git a/core/src/util_normalize.hpp b/core/src/util_normalize.hpp index d417617565..6321ffacda 100644 --- a/core/src/util_normalize.hpp +++ b/core/src/util_normalize.hpp @@ -14,6 +14,12 @@ namespace km { namespace core { namespace util { +/** Normalize a u32string inplace to NFC. @return false on failure */ +bool normalize_nfc(std::u32string &str); + +/** Normalize a u16string inplace to NFC. @return false on failure */ +bool normalize_nfc(std::u16string &str); + /** Normalize a u32string inplace to NFD. @return false on failure */ bool normalize_nfd(std::u32string &str); @@ -26,6 +32,18 @@ bool normalize_nfd(km_core_cu const * src, std::u16string &dst); /** normalize (decompose) a single cp to string. @return false on failure */ bool normalize_nfd(km_core_usv cp, std::u32string &dst); +/** @return true if string is already NFD */ +bool is_nfd(const std::u16string& str); + +/** @return true if string is already NFD */ +bool is_nfd(const std::u32string& str); + +/** @return true if cp can interacts with prior chars */ +bool has_nfd_boundary_before(km_core_usv cp); + +/** convenience function, caller owns storage */ +km_core_usv *string_to_usv(const std::u32string& src); + } } } From 53a6638a70c243d6078b83eb51154318bc78bd5e Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Tue, 4 Jun 2024 11:15:17 -0500 Subject: [PATCH 04/14] feat(core): generate and use static table in wasm for NFD boundary - add core/tools build tree with custom targets - add to core/build.sh to generate nfd_table.h - test_unicode to validate Unicode version and compare NFD to actual ICU - currently, linear search of the table. --- core/build.sh | 6 + core/meson.build | 1 + core/src/util_normalize.cpp | 11 +- core/tests/unit/ldml/test_unicode.cpp | 41 + core/tools/meson.build | 40 + core/tools/norm_unicode_update.cpp | 67 ++ .../unicode-character-database/nfd_table.h | 932 ++++++++++++++++++ 7 files changed, 1097 insertions(+), 1 deletion(-) create mode 100644 core/tools/meson.build create mode 100644 core/tools/norm_unicode_update.cpp create mode 100644 resources/standards-data/unicode-character-database/nfd_table.h diff --git a/core/build.sh b/core/build.sh index 168cc1cbcf..016431cfdd 100755 --- a/core/build.sh +++ b/core/build.sh @@ -68,6 +68,7 @@ Libraries will be built in 'build///src'. "uninstall uninstall libraries from current system" \ "${archtargets[@]}" \ "--no-tests do not configure tests (used by other projects)" \ + "--update-unicode rebuild tables if the ICU4C Unicode version changes" \ "--test,-t=opt_tests test[s] to run (space separated)" builder_parse "$@" @@ -177,6 +178,11 @@ if builder_start_action test:mac; then builder_finish_action success test:mac fi +if builder_has_option --update-unicode; then + meson compile -C ${KEYMAN_ROOT}/core/build/mac-x86_64/$BUILDER_CONFIGURATION tools/norm_data && mv -v ${KEYMAN_ROOT}/core/build/mac-x86_64/$BUILDER_CONFIGURATION/tools/nfd_table.h ${KEYMAN_ROOT}/resources/standards-data/unicode-character-database/ +fi + + # ------------------------------------------------------------------------------- do_action install diff --git a/core/meson.build b/core/meson.build index 0049c95741..ff306991e9 100644 --- a/core/meson.build +++ b/core/meson.build @@ -44,3 +44,4 @@ subdir('doc') subdir('include') subdir('src') subdir('tests') +subdir('tools') diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index b5c3b860d1..7678a9d4b0 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -30,6 +30,9 @@ EM_JS(char*, NormalizeNFC, (const char* input), { return stringToNewUTF8(nfd); }); +// pull in the generated table +#include "../../resources/standards-data/unicode-character-database/nfd_table.h" + #endif namespace km { @@ -207,7 +210,13 @@ bool is_nfd(const std::u32string& str) { bool has_nfd_boundary_before(km_core_usv cp) { #ifdef __EMSCRIPTEN__ -#error TODO +// it's a negative table. entries in the table mean returning false. non-entries return true. + for (int i=0;;i++) { + auto t = km_noBoundaryBefore[i]; + if (t == 0) return true; + if (t > cp) return true; + if (t == cp) return false; + } #else UErrorCode status = U_ZERO_ERROR; auto nfd = getNFD(status); diff --git a/core/tests/unit/ldml/test_unicode.cpp b/core/tests/unit/ldml/test_unicode.cpp index ae41853153..ea1d9c6cca 100644 --- a/core/tests/unit/ldml/test_unicode.cpp +++ b/core/tests/unit/ldml/test_unicode.cpp @@ -25,6 +25,8 @@ #include #include #include "json.hpp" +#include "util_normalize.hpp" +#include "kmx/kmx_xstring.h" #include #include @@ -43,6 +45,11 @@ } \ } +#ifdef __EMSCRIPTEN__ +// Pull this in to verify versions +#include "../../../../resources/standards-data/unicode-character-database/nfd_table.h" +#endif + //------------------------------------------------------------------------------------- // Unicode version tests //------------------------------------------------------------------------------------- @@ -149,6 +156,36 @@ const std::string &block_unicode_ver) { std::cout << std::endl; } +#ifdef __EMSCRIPTEN__ +inline const char *boolstr(bool b) { + return b?"T":"f"; +} + +void test_has_boundary_before() { + std::cout << "= " << __FUNCTION__ << std::endl; + std::cout << "(this test only runs under emscripten. congratulations.)" << std::endl; + // static_assert(U_UNICODE_VERSION == KM_HASBOUNDARYBEFORE_UNICODE_VERSION, "nfd_table.h Unicode version does not match ICU's - see nfd_table.h"); + std::cout << U_UNICODE_VERSION << "≈≈" << KM_HASBOUNDARYBEFORE_UNICODE_VERSION << std::endl; + + UErrorCode status = U_ZERO_ERROR; + const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); + UASSERT_SUCCESS(status); + + // now, test that hasBoundaryBefore is the same + for (km_core_usv cp = 0; cp < km::core::kmx::Uni_MAX_CODEPOINT; cp++) { + auto km_hbb = km::core::util::has_nfd_boundary_before(cp); + auto icu_hbb = nfd->hasBoundaryBefore(cp); + + if (km_hbb != icu_hbb) { + std::cerr << "Error: nfd_table.h said " << boolstr(km_hbb) << " but ICU said " << boolstr(icu_hbb) << " for " + << "has_nfd_boundary_before(0x" << std::hex << cp << std::dec << ")" << std::endl; + } + assert(km_hbb == icu_hbb); + } + std::cout << "All OK!" << std::endl; +} +#endif + int test_all(const char *jsonpath, const char *packagepath, const char *blockspath) { std::cout << "= " << __FUNCTION__ << std::endl; @@ -164,6 +201,10 @@ int test_all(const char *jsonpath, const char *packagepath, const char *blockspa test_unicode_versions(versions, package, block_unicode_ver); +#ifdef __EMSCRIPTEN__ + test_has_boundary_before(); +#endif + return EXIT_SUCCESS; } diff --git a/core/tools/meson.build b/core/tools/meson.build new file mode 100644 index 0000000000..31f3a00848 --- /dev/null +++ b/core/tools/meson.build @@ -0,0 +1,40 @@ +# Copyright: © 2024 SIL International. +# Description: Cross platform build script to compile tool(s). +# Create Date: 31 May 2024 +# Authors: Steven R. Loomis (SRL) +# + + +# TODO -- why are these differing from the standard.meson.build flags? +if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' or cpp_compiler.get_id() == 'emscripten' + warns = [ + '-Wno-missing-field-initializers', + '-Wno-unused-parameter' + ] +else + warns = [] +endif + + +if cpp_compiler.get_id() == 'emscripten' + tests_flags += ['-lnodefs.js', + '-sNO_DISABLE_EXCEPTION_CATCHING', # for test exceptions + wasm_exported_runtime_methods] +endif + +norm_unicode_update = executable('norm_unicode_update', + ['norm_unicode_update.cpp'], + cpp_args: defns + warns, + include_directories: [inc, libsrc, '../../developer/src/ext/json'], + link_args: links + tests_flags, + dependencies: [icu_uc, icu_i18n], + # link_with: [lib], + objects: lib.extract_all_objects(recursive: false), + ) + + +# ../../resources/standards-data/unicode-character-database/ +norm_data = custom_target('norm_data', output: 'nfd_table.h', command: [norm_unicode_update, '@OUTPUT@']) + + +# TODO: execute it diff --git a/core/tools/norm_unicode_update.cpp b/core/tools/norm_unicode_update.cpp new file mode 100644 index 0000000000..c2c290ba9b --- /dev/null +++ b/core/tools/norm_unicode_update.cpp @@ -0,0 +1,67 @@ +#include "kmx/kmx_plus.h" +#include "kmx/kmx_xstring.h" +#include "core_icu.h" + +#include +#include +#include + +#include + +#include + +#ifndef __EMSCRIPTEN__ + +int +write_nfd_table(const char *NFD_FILE) { + std::cout << " writing: " << NFD_FILE << std::endl; + auto f = std::ofstream(NFD_FILE); + assert(f.good()); + + // write preamble + f << "//NFD hasBoundaryBefore" << std::endl; + f << "#pragma once" << std::endl; + f << "#define KM_HASBOUNDARYBEFORE_UNICODE_VERSION \"" << U_UNICODE_VERSION << "\"" << std::endl; + f << "#define KM_HASBOUNDARYBEFORE_ICU_VERSION \"" << U_ICU_VERSION << "\"" << std::endl; + f << "static char32_t km_noBoundaryBefore[] = {" << std::endl; + // we're going to need an NFD normalizer + UErrorCode status = U_ZERO_ERROR; + const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); + assert(U_SUCCESS(status)); + + for (km_core_usv ch = 0; ch < 0x10FFFF; ch++) { + bool bb = nfd->hasBoundaryBefore(ch); + assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator + + // TODO: This test may be better in test_unicode + // icu::UnicodeString s; + // s.append((UChar32)ch); + // bool lccc = nfd->isNormalized(s, status) && u_getCombiningClass(ch) == 0; + // assert(U_SUCCESS(status)); + // if (bb != lccc) { + // printf("0x%04x - bb=%s but lccc=%s\n", (unsigned int)ch, bb ? "y" : "n", lccc ? "y" : "n"); + // } + // assert(bb == lccc); + if (bb) continue; //only emit nonboundary + // char key[10]; + // snprintf(key, 10, "%04X", (unsigned int)ch); + f << "\t0x" << std::hex << ch << "," << std::endl; + } + // termination + f << "\t0x" << std::hex << 0 << "," << std::endl; + f << "};" << std::endl; + return 0; +} + +int +main(int argc, const char *argv[]) { + assert(argc == 2); // call with one param: @OUTPUT@ + write_nfd_table(argv[1]); + return 0; +} +#else +int main(int argc, const char *argv[]) { + std::cerr << "Can't run this under Emscripten - run under another platform." << std::endl; + return 1; +} +#endif diff --git a/resources/standards-data/unicode-character-database/nfd_table.h b/resources/standards-data/unicode-character-database/nfd_table.h new file mode 100644 index 0000000000..4e157e5b6b --- /dev/null +++ b/resources/standards-data/unicode-character-database/nfd_table.h @@ -0,0 +1,932 @@ +//NFD hasBoundaryBefore +#pragma once +#define KM_HASBOUNDARYBEFORE_UNICODE_VERSION "15.0" +#define KM_HASBOUNDARYBEFORE_ICU_VERSION "73.1" +static char32_t km_noBoundaryBefore[] = { + 0x300, + 0x301, + 0x302, + 0x303, + 0x304, + 0x305, + 0x306, + 0x307, + 0x308, + 0x309, + 0x30a, + 0x30b, + 0x30c, + 0x30d, + 0x30e, + 0x30f, + 0x310, + 0x311, + 0x312, + 0x313, + 0x314, + 0x315, + 0x316, + 0x317, + 0x318, + 0x319, + 0x31a, + 0x31b, + 0x31c, + 0x31d, + 0x31e, + 0x31f, + 0x320, + 0x321, + 0x322, + 0x323, + 0x324, + 0x325, + 0x326, + 0x327, + 0x328, + 0x329, + 0x32a, + 0x32b, + 0x32c, + 0x32d, + 0x32e, + 0x32f, + 0x330, + 0x331, + 0x332, + 0x333, + 0x334, + 0x335, + 0x336, + 0x337, + 0x338, + 0x339, + 0x33a, + 0x33b, + 0x33c, + 0x33d, + 0x33e, + 0x33f, + 0x340, + 0x341, + 0x342, + 0x343, + 0x344, + 0x345, + 0x346, + 0x347, + 0x348, + 0x349, + 0x34a, + 0x34b, + 0x34c, + 0x34d, + 0x34e, + 0x350, + 0x351, + 0x352, + 0x353, + 0x354, + 0x355, + 0x356, + 0x357, + 0x358, + 0x359, + 0x35a, + 0x35b, + 0x35c, + 0x35d, + 0x35e, + 0x35f, + 0x360, + 0x361, + 0x362, + 0x363, + 0x364, + 0x365, + 0x366, + 0x367, + 0x368, + 0x369, + 0x36a, + 0x36b, + 0x36c, + 0x36d, + 0x36e, + 0x36f, + 0x483, + 0x484, + 0x485, + 0x486, + 0x487, + 0x591, + 0x592, + 0x593, + 0x594, + 0x595, + 0x596, + 0x597, + 0x598, + 0x599, + 0x59a, + 0x59b, + 0x59c, + 0x59d, + 0x59e, + 0x59f, + 0x5a0, + 0x5a1, + 0x5a2, + 0x5a3, + 0x5a4, + 0x5a5, + 0x5a6, + 0x5a7, + 0x5a8, + 0x5a9, + 0x5aa, + 0x5ab, + 0x5ac, + 0x5ad, + 0x5ae, + 0x5af, + 0x5b0, + 0x5b1, + 0x5b2, + 0x5b3, + 0x5b4, + 0x5b5, + 0x5b6, + 0x5b7, + 0x5b8, + 0x5b9, + 0x5ba, + 0x5bb, + 0x5bc, + 0x5bd, + 0x5bf, + 0x5c1, + 0x5c2, + 0x5c4, + 0x5c5, + 0x5c7, + 0x610, + 0x611, + 0x612, + 0x613, + 0x614, + 0x615, + 0x616, + 0x617, + 0x618, + 0x619, + 0x61a, + 0x64b, + 0x64c, + 0x64d, + 0x64e, + 0x64f, + 0x650, + 0x651, + 0x652, + 0x653, + 0x654, + 0x655, + 0x656, + 0x657, + 0x658, + 0x659, + 0x65a, + 0x65b, + 0x65c, + 0x65d, + 0x65e, + 0x65f, + 0x670, + 0x6d6, + 0x6d7, + 0x6d8, + 0x6d9, + 0x6da, + 0x6db, + 0x6dc, + 0x6df, + 0x6e0, + 0x6e1, + 0x6e2, + 0x6e3, + 0x6e4, + 0x6e7, + 0x6e8, + 0x6ea, + 0x6eb, + 0x6ec, + 0x6ed, + 0x711, + 0x730, + 0x731, + 0x732, + 0x733, + 0x734, + 0x735, + 0x736, + 0x737, + 0x738, + 0x739, + 0x73a, + 0x73b, + 0x73c, + 0x73d, + 0x73e, + 0x73f, + 0x740, + 0x741, + 0x742, + 0x743, + 0x744, + 0x745, + 0x746, + 0x747, + 0x748, + 0x749, + 0x74a, + 0x7eb, + 0x7ec, + 0x7ed, + 0x7ee, + 0x7ef, + 0x7f0, + 0x7f1, + 0x7f2, + 0x7f3, + 0x7fd, + 0x816, + 0x817, + 0x818, + 0x819, + 0x81b, + 0x81c, + 0x81d, + 0x81e, + 0x81f, + 0x820, + 0x821, + 0x822, + 0x823, + 0x825, + 0x826, + 0x827, + 0x829, + 0x82a, + 0x82b, + 0x82c, + 0x82d, + 0x859, + 0x85a, + 0x85b, + 0x898, + 0x899, + 0x89a, + 0x89b, + 0x89c, + 0x89d, + 0x89e, + 0x89f, + 0x8ca, + 0x8cb, + 0x8cc, + 0x8cd, + 0x8ce, + 0x8cf, + 0x8d0, + 0x8d1, + 0x8d2, + 0x8d3, + 0x8d4, + 0x8d5, + 0x8d6, + 0x8d7, + 0x8d8, + 0x8d9, + 0x8da, + 0x8db, + 0x8dc, + 0x8dd, + 0x8de, + 0x8df, + 0x8e0, + 0x8e1, + 0x8e3, + 0x8e4, + 0x8e5, + 0x8e6, + 0x8e7, + 0x8e8, + 0x8e9, + 0x8ea, + 0x8eb, + 0x8ec, + 0x8ed, + 0x8ee, + 0x8ef, + 0x8f0, + 0x8f1, + 0x8f2, + 0x8f3, + 0x8f4, + 0x8f5, + 0x8f6, + 0x8f7, + 0x8f8, + 0x8f9, + 0x8fa, + 0x8fb, + 0x8fc, + 0x8fd, + 0x8fe, + 0x8ff, + 0x93c, + 0x94d, + 0x951, + 0x952, + 0x953, + 0x954, + 0x9bc, + 0x9cd, + 0x9fe, + 0xa3c, + 0xa4d, + 0xabc, + 0xacd, + 0xb3c, + 0xb4d, + 0xbcd, + 0xc3c, + 0xc4d, + 0xc55, + 0xc56, + 0xcbc, + 0xccd, + 0xd3b, + 0xd3c, + 0xd4d, + 0xdca, + 0xe38, + 0xe39, + 0xe3a, + 0xe48, + 0xe49, + 0xe4a, + 0xe4b, + 0xeb8, + 0xeb9, + 0xeba, + 0xec8, + 0xec9, + 0xeca, + 0xecb, + 0xf18, + 0xf19, + 0xf35, + 0xf37, + 0xf39, + 0xf71, + 0xf72, + 0xf73, + 0xf74, + 0xf75, + 0xf7a, + 0xf7b, + 0xf7c, + 0xf7d, + 0xf80, + 0xf81, + 0xf82, + 0xf83, + 0xf84, + 0xf86, + 0xf87, + 0xfc6, + 0x1037, + 0x1039, + 0x103a, + 0x108d, + 0x135d, + 0x135e, + 0x135f, + 0x1714, + 0x1715, + 0x1734, + 0x17d2, + 0x17dd, + 0x18a9, + 0x1939, + 0x193a, + 0x193b, + 0x1a17, + 0x1a18, + 0x1a60, + 0x1a75, + 0x1a76, + 0x1a77, + 0x1a78, + 0x1a79, + 0x1a7a, + 0x1a7b, + 0x1a7c, + 0x1a7f, + 0x1ab0, + 0x1ab1, + 0x1ab2, + 0x1ab3, + 0x1ab4, + 0x1ab5, + 0x1ab6, + 0x1ab7, + 0x1ab8, + 0x1ab9, + 0x1aba, + 0x1abb, + 0x1abc, + 0x1abd, + 0x1abf, + 0x1ac0, + 0x1ac1, + 0x1ac2, + 0x1ac3, + 0x1ac4, + 0x1ac5, + 0x1ac6, + 0x1ac7, + 0x1ac8, + 0x1ac9, + 0x1aca, + 0x1acb, + 0x1acc, + 0x1acd, + 0x1ace, + 0x1b34, + 0x1b44, + 0x1b6b, + 0x1b6c, + 0x1b6d, + 0x1b6e, + 0x1b6f, + 0x1b70, + 0x1b71, + 0x1b72, + 0x1b73, + 0x1baa, + 0x1bab, + 0x1be6, + 0x1bf2, + 0x1bf3, + 0x1c37, + 0x1cd0, + 0x1cd1, + 0x1cd2, + 0x1cd4, + 0x1cd5, + 0x1cd6, + 0x1cd7, + 0x1cd8, + 0x1cd9, + 0x1cda, + 0x1cdb, + 0x1cdc, + 0x1cdd, + 0x1cde, + 0x1cdf, + 0x1ce0, + 0x1ce2, + 0x1ce3, + 0x1ce4, + 0x1ce5, + 0x1ce6, + 0x1ce7, + 0x1ce8, + 0x1ced, + 0x1cf4, + 0x1cf8, + 0x1cf9, + 0x1dc0, + 0x1dc1, + 0x1dc2, + 0x1dc3, + 0x1dc4, + 0x1dc5, + 0x1dc6, + 0x1dc7, + 0x1dc8, + 0x1dc9, + 0x1dca, + 0x1dcb, + 0x1dcc, + 0x1dcd, + 0x1dce, + 0x1dcf, + 0x1dd0, + 0x1dd1, + 0x1dd2, + 0x1dd3, + 0x1dd4, + 0x1dd5, + 0x1dd6, + 0x1dd7, + 0x1dd8, + 0x1dd9, + 0x1dda, + 0x1ddb, + 0x1ddc, + 0x1ddd, + 0x1dde, + 0x1ddf, + 0x1de0, + 0x1de1, + 0x1de2, + 0x1de3, + 0x1de4, + 0x1de5, + 0x1de6, + 0x1de7, + 0x1de8, + 0x1de9, + 0x1dea, + 0x1deb, + 0x1dec, + 0x1ded, + 0x1dee, + 0x1def, + 0x1df0, + 0x1df1, + 0x1df2, + 0x1df3, + 0x1df4, + 0x1df5, + 0x1df6, + 0x1df7, + 0x1df8, + 0x1df9, + 0x1dfa, + 0x1dfb, + 0x1dfc, + 0x1dfd, + 0x1dfe, + 0x1dff, + 0x20d0, + 0x20d1, + 0x20d2, + 0x20d3, + 0x20d4, + 0x20d5, + 0x20d6, + 0x20d7, + 0x20d8, + 0x20d9, + 0x20da, + 0x20db, + 0x20dc, + 0x20e1, + 0x20e5, + 0x20e6, + 0x20e7, + 0x20e8, + 0x20e9, + 0x20ea, + 0x20eb, + 0x20ec, + 0x20ed, + 0x20ee, + 0x20ef, + 0x20f0, + 0x2cef, + 0x2cf0, + 0x2cf1, + 0x2d7f, + 0x2de0, + 0x2de1, + 0x2de2, + 0x2de3, + 0x2de4, + 0x2de5, + 0x2de6, + 0x2de7, + 0x2de8, + 0x2de9, + 0x2dea, + 0x2deb, + 0x2dec, + 0x2ded, + 0x2dee, + 0x2def, + 0x2df0, + 0x2df1, + 0x2df2, + 0x2df3, + 0x2df4, + 0x2df5, + 0x2df6, + 0x2df7, + 0x2df8, + 0x2df9, + 0x2dfa, + 0x2dfb, + 0x2dfc, + 0x2dfd, + 0x2dfe, + 0x2dff, + 0x302a, + 0x302b, + 0x302c, + 0x302d, + 0x302e, + 0x302f, + 0x3099, + 0x309a, + 0xa66f, + 0xa674, + 0xa675, + 0xa676, + 0xa677, + 0xa678, + 0xa679, + 0xa67a, + 0xa67b, + 0xa67c, + 0xa67d, + 0xa69e, + 0xa69f, + 0xa6f0, + 0xa6f1, + 0xa806, + 0xa82c, + 0xa8c4, + 0xa8e0, + 0xa8e1, + 0xa8e2, + 0xa8e3, + 0xa8e4, + 0xa8e5, + 0xa8e6, + 0xa8e7, + 0xa8e8, + 0xa8e9, + 0xa8ea, + 0xa8eb, + 0xa8ec, + 0xa8ed, + 0xa8ee, + 0xa8ef, + 0xa8f0, + 0xa8f1, + 0xa92b, + 0xa92c, + 0xa92d, + 0xa953, + 0xa9b3, + 0xa9c0, + 0xaab0, + 0xaab2, + 0xaab3, + 0xaab4, + 0xaab7, + 0xaab8, + 0xaabe, + 0xaabf, + 0xaac1, + 0xaaf6, + 0xabed, + 0xfb1e, + 0xfe20, + 0xfe21, + 0xfe22, + 0xfe23, + 0xfe24, + 0xfe25, + 0xfe26, + 0xfe27, + 0xfe28, + 0xfe29, + 0xfe2a, + 0xfe2b, + 0xfe2c, + 0xfe2d, + 0xfe2e, + 0xfe2f, + 0x101fd, + 0x102e0, + 0x10376, + 0x10377, + 0x10378, + 0x10379, + 0x1037a, + 0x10a0d, + 0x10a0f, + 0x10a38, + 0x10a39, + 0x10a3a, + 0x10a3f, + 0x10ae5, + 0x10ae6, + 0x10d24, + 0x10d25, + 0x10d26, + 0x10d27, + 0x10eab, + 0x10eac, + 0x10efd, + 0x10efe, + 0x10eff, + 0x10f46, + 0x10f47, + 0x10f48, + 0x10f49, + 0x10f4a, + 0x10f4b, + 0x10f4c, + 0x10f4d, + 0x10f4e, + 0x10f4f, + 0x10f50, + 0x10f82, + 0x10f83, + 0x10f84, + 0x10f85, + 0x11046, + 0x11070, + 0x1107f, + 0x110b9, + 0x110ba, + 0x11100, + 0x11101, + 0x11102, + 0x11133, + 0x11134, + 0x11173, + 0x111c0, + 0x111ca, + 0x11235, + 0x11236, + 0x112e9, + 0x112ea, + 0x1133b, + 0x1133c, + 0x1134d, + 0x11366, + 0x11367, + 0x11368, + 0x11369, + 0x1136a, + 0x1136b, + 0x1136c, + 0x11370, + 0x11371, + 0x11372, + 0x11373, + 0x11374, + 0x11442, + 0x11446, + 0x1145e, + 0x114c2, + 0x114c3, + 0x115bf, + 0x115c0, + 0x1163f, + 0x116b6, + 0x116b7, + 0x1172b, + 0x11839, + 0x1183a, + 0x1193d, + 0x1193e, + 0x11943, + 0x119e0, + 0x11a34, + 0x11a47, + 0x11a99, + 0x11c3f, + 0x11d42, + 0x11d44, + 0x11d45, + 0x11d97, + 0x11f41, + 0x11f42, + 0x16af0, + 0x16af1, + 0x16af2, + 0x16af3, + 0x16af4, + 0x16b30, + 0x16b31, + 0x16b32, + 0x16b33, + 0x16b34, + 0x16b35, + 0x16b36, + 0x16ff0, + 0x16ff1, + 0x1bc9e, + 0x1d165, + 0x1d166, + 0x1d167, + 0x1d168, + 0x1d169, + 0x1d16d, + 0x1d16e, + 0x1d16f, + 0x1d170, + 0x1d171, + 0x1d172, + 0x1d17b, + 0x1d17c, + 0x1d17d, + 0x1d17e, + 0x1d17f, + 0x1d180, + 0x1d181, + 0x1d182, + 0x1d185, + 0x1d186, + 0x1d187, + 0x1d188, + 0x1d189, + 0x1d18a, + 0x1d18b, + 0x1d1aa, + 0x1d1ab, + 0x1d1ac, + 0x1d1ad, + 0x1d242, + 0x1d243, + 0x1d244, + 0x1e000, + 0x1e001, + 0x1e002, + 0x1e003, + 0x1e004, + 0x1e005, + 0x1e006, + 0x1e008, + 0x1e009, + 0x1e00a, + 0x1e00b, + 0x1e00c, + 0x1e00d, + 0x1e00e, + 0x1e00f, + 0x1e010, + 0x1e011, + 0x1e012, + 0x1e013, + 0x1e014, + 0x1e015, + 0x1e016, + 0x1e017, + 0x1e018, + 0x1e01b, + 0x1e01c, + 0x1e01d, + 0x1e01e, + 0x1e01f, + 0x1e020, + 0x1e021, + 0x1e023, + 0x1e024, + 0x1e026, + 0x1e027, + 0x1e028, + 0x1e029, + 0x1e02a, + 0x1e08f, + 0x1e130, + 0x1e131, + 0x1e132, + 0x1e133, + 0x1e134, + 0x1e135, + 0x1e136, + 0x1e2ae, + 0x1e2ec, + 0x1e2ed, + 0x1e2ee, + 0x1e2ef, + 0x1e4ec, + 0x1e4ed, + 0x1e4ee, + 0x1e4ef, + 0x1e8d0, + 0x1e8d1, + 0x1e8d2, + 0x1e8d3, + 0x1e8d4, + 0x1e8d5, + 0x1e8d6, + 0x1e944, + 0x1e945, + 0x1e946, + 0x1e947, + 0x1e948, + 0x1e949, + 0x1e94a, + 0x0, +}; From e9673867fa550d68ed7282c291af3198a1f5e603 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Tue, 4 Jun 2024 12:23:16 -0500 Subject: [PATCH 05/14] feat(core): speedup NFD boundary table - use RLE encoding, thanks @mcdurdin - much smaller table and faster lookup Fixes: #9467 --- core/src/util_normalize.cpp | 12 +- core/tools/norm_unicode_update.cpp | 47 +- .../unicode-character-database/nfd_table.h | 1119 +++-------------- 3 files changed, 231 insertions(+), 947 deletions(-) diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index 7678a9d4b0..7d0d76d8ab 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -211,12 +211,14 @@ bool is_nfd(const std::u32string& str) { bool has_nfd_boundary_before(km_core_usv cp) { #ifdef __EMSCRIPTEN__ // it's a negative table. entries in the table mean returning false. non-entries return true. - for (int i=0;;i++) { - auto t = km_noBoundaryBefore[i]; - if (t == 0) return true; - if (t > cp) return true; - if (t == cp) return false; + for (auto i=0;i<(km_noBoundaryBefore_entries*2);i+=2) { + auto start = km_noBoundaryBefore[i+0]; + if (start > cp) return true; + auto count = km_noBoundaryBefore[i+1]; + auto limit = start+count; + if (cp >= start && cp < limit) return false; } + return true; // fallthrough #else UErrorCode status = U_ZERO_ERROR; auto nfd = getNFD(status); diff --git a/core/tools/norm_unicode_update.cpp b/core/tools/norm_unicode_update.cpp index c2c290ba9b..408dbb0c98 100644 --- a/core/tools/norm_unicode_update.cpp +++ b/core/tools/norm_unicode_update.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -23,32 +24,48 @@ write_nfd_table(const char *NFD_FILE) { f << "#pragma once" << std::endl; f << "#define KM_HASBOUNDARYBEFORE_UNICODE_VERSION \"" << U_UNICODE_VERSION << "\"" << std::endl; f << "#define KM_HASBOUNDARYBEFORE_ICU_VERSION \"" << U_ICU_VERSION << "\"" << std::endl; - f << "static char32_t km_noBoundaryBefore[] = {" << std::endl; // we're going to need an NFD normalizer UErrorCode status = U_ZERO_ERROR; const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); assert(U_SUCCESS(status)); + std::vector noBoundary; + for (km_core_usv ch = 0; ch < 0x10FFFF; ch++) { bool bb = nfd->hasBoundaryBefore(ch); assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator - - // TODO: This test may be better in test_unicode - // icu::UnicodeString s; - // s.append((UChar32)ch); - // bool lccc = nfd->isNormalized(s, status) && u_getCombiningClass(ch) == 0; - // assert(U_SUCCESS(status)); - // if (bb != lccc) { - // printf("0x%04x - bb=%s but lccc=%s\n", (unsigned int)ch, bb ? "y" : "n", lccc ? "y" : "n"); - // } - // assert(bb == lccc); if (bb) continue; //only emit nonboundary - // char key[10]; - // snprintf(key, 10, "%04X", (unsigned int)ch); - f << "\t0x" << std::hex << ch << "," << std::endl; + noBoundary.push_back(ch); } + + std::vector> runs; // start,len + + km_core_usv first = 0; + km_core_usv last = 0; + for(auto i = noBoundary.begin(); i <= noBoundary.end(); i++) { + if (first == 0) { + first = last = *i; + } else { + last++; + if(i == noBoundary.end() || *i != last) { + // end of a run + runs.emplace_back(first, last - first); + if (i != noBoundary.end()) { + // setup for next + first = last = *i; + } + } + } + } + f << "#define km_noBoundaryBefore_entries " << runs.size() << "\n"; + + f << "static char32_t km_noBoundaryBefore[km_noBoundaryBefore_entries * 2 ] = {" << std::endl; + + for (auto i = runs.begin(); i < runs.end(); i++) { + f << "\t0x" << std::hex << i->first << std::dec << ",\t " << i->second << ", // ...0x" << std::hex << (i->first+i->second-1) << std::endl; + } + // termination - f << "\t0x" << std::hex << 0 << "," << std::endl; f << "};" << std::endl; return 0; } diff --git a/resources/standards-data/unicode-character-database/nfd_table.h b/resources/standards-data/unicode-character-database/nfd_table.h index 4e157e5b6b..068240fc30 100644 --- a/resources/standards-data/unicode-character-database/nfd_table.h +++ b/resources/standards-data/unicode-character-database/nfd_table.h @@ -2,931 +2,196 @@ #pragma once #define KM_HASBOUNDARYBEFORE_UNICODE_VERSION "15.0" #define KM_HASBOUNDARYBEFORE_ICU_VERSION "73.1" -static char32_t km_noBoundaryBefore[] = { - 0x300, - 0x301, - 0x302, - 0x303, - 0x304, - 0x305, - 0x306, - 0x307, - 0x308, - 0x309, - 0x30a, - 0x30b, - 0x30c, - 0x30d, - 0x30e, - 0x30f, - 0x310, - 0x311, - 0x312, - 0x313, - 0x314, - 0x315, - 0x316, - 0x317, - 0x318, - 0x319, - 0x31a, - 0x31b, - 0x31c, - 0x31d, - 0x31e, - 0x31f, - 0x320, - 0x321, - 0x322, - 0x323, - 0x324, - 0x325, - 0x326, - 0x327, - 0x328, - 0x329, - 0x32a, - 0x32b, - 0x32c, - 0x32d, - 0x32e, - 0x32f, - 0x330, - 0x331, - 0x332, - 0x333, - 0x334, - 0x335, - 0x336, - 0x337, - 0x338, - 0x339, - 0x33a, - 0x33b, - 0x33c, - 0x33d, - 0x33e, - 0x33f, - 0x340, - 0x341, - 0x342, - 0x343, - 0x344, - 0x345, - 0x346, - 0x347, - 0x348, - 0x349, - 0x34a, - 0x34b, - 0x34c, - 0x34d, - 0x34e, - 0x350, - 0x351, - 0x352, - 0x353, - 0x354, - 0x355, - 0x356, - 0x357, - 0x358, - 0x359, - 0x35a, - 0x35b, - 0x35c, - 0x35d, - 0x35e, - 0x35f, - 0x360, - 0x361, - 0x362, - 0x363, - 0x364, - 0x365, - 0x366, - 0x367, - 0x368, - 0x369, - 0x36a, - 0x36b, - 0x36c, - 0x36d, - 0x36e, - 0x36f, - 0x483, - 0x484, - 0x485, - 0x486, - 0x487, - 0x591, - 0x592, - 0x593, - 0x594, - 0x595, - 0x596, - 0x597, - 0x598, - 0x599, - 0x59a, - 0x59b, - 0x59c, - 0x59d, - 0x59e, - 0x59f, - 0x5a0, - 0x5a1, - 0x5a2, - 0x5a3, - 0x5a4, - 0x5a5, - 0x5a6, - 0x5a7, - 0x5a8, - 0x5a9, - 0x5aa, - 0x5ab, - 0x5ac, - 0x5ad, - 0x5ae, - 0x5af, - 0x5b0, - 0x5b1, - 0x5b2, - 0x5b3, - 0x5b4, - 0x5b5, - 0x5b6, - 0x5b7, - 0x5b8, - 0x5b9, - 0x5ba, - 0x5bb, - 0x5bc, - 0x5bd, - 0x5bf, - 0x5c1, - 0x5c2, - 0x5c4, - 0x5c5, - 0x5c7, - 0x610, - 0x611, - 0x612, - 0x613, - 0x614, - 0x615, - 0x616, - 0x617, - 0x618, - 0x619, - 0x61a, - 0x64b, - 0x64c, - 0x64d, - 0x64e, - 0x64f, - 0x650, - 0x651, - 0x652, - 0x653, - 0x654, - 0x655, - 0x656, - 0x657, - 0x658, - 0x659, - 0x65a, - 0x65b, - 0x65c, - 0x65d, - 0x65e, - 0x65f, - 0x670, - 0x6d6, - 0x6d7, - 0x6d8, - 0x6d9, - 0x6da, - 0x6db, - 0x6dc, - 0x6df, - 0x6e0, - 0x6e1, - 0x6e2, - 0x6e3, - 0x6e4, - 0x6e7, - 0x6e8, - 0x6ea, - 0x6eb, - 0x6ec, - 0x6ed, - 0x711, - 0x730, - 0x731, - 0x732, - 0x733, - 0x734, - 0x735, - 0x736, - 0x737, - 0x738, - 0x739, - 0x73a, - 0x73b, - 0x73c, - 0x73d, - 0x73e, - 0x73f, - 0x740, - 0x741, - 0x742, - 0x743, - 0x744, - 0x745, - 0x746, - 0x747, - 0x748, - 0x749, - 0x74a, - 0x7eb, - 0x7ec, - 0x7ed, - 0x7ee, - 0x7ef, - 0x7f0, - 0x7f1, - 0x7f2, - 0x7f3, - 0x7fd, - 0x816, - 0x817, - 0x818, - 0x819, - 0x81b, - 0x81c, - 0x81d, - 0x81e, - 0x81f, - 0x820, - 0x821, - 0x822, - 0x823, - 0x825, - 0x826, - 0x827, - 0x829, - 0x82a, - 0x82b, - 0x82c, - 0x82d, - 0x859, - 0x85a, - 0x85b, - 0x898, - 0x899, - 0x89a, - 0x89b, - 0x89c, - 0x89d, - 0x89e, - 0x89f, - 0x8ca, - 0x8cb, - 0x8cc, - 0x8cd, - 0x8ce, - 0x8cf, - 0x8d0, - 0x8d1, - 0x8d2, - 0x8d3, - 0x8d4, - 0x8d5, - 0x8d6, - 0x8d7, - 0x8d8, - 0x8d9, - 0x8da, - 0x8db, - 0x8dc, - 0x8dd, - 0x8de, - 0x8df, - 0x8e0, - 0x8e1, - 0x8e3, - 0x8e4, - 0x8e5, - 0x8e6, - 0x8e7, - 0x8e8, - 0x8e9, - 0x8ea, - 0x8eb, - 0x8ec, - 0x8ed, - 0x8ee, - 0x8ef, - 0x8f0, - 0x8f1, - 0x8f2, - 0x8f3, - 0x8f4, - 0x8f5, - 0x8f6, - 0x8f7, - 0x8f8, - 0x8f9, - 0x8fa, - 0x8fb, - 0x8fc, - 0x8fd, - 0x8fe, - 0x8ff, - 0x93c, - 0x94d, - 0x951, - 0x952, - 0x953, - 0x954, - 0x9bc, - 0x9cd, - 0x9fe, - 0xa3c, - 0xa4d, - 0xabc, - 0xacd, - 0xb3c, - 0xb4d, - 0xbcd, - 0xc3c, - 0xc4d, - 0xc55, - 0xc56, - 0xcbc, - 0xccd, - 0xd3b, - 0xd3c, - 0xd4d, - 0xdca, - 0xe38, - 0xe39, - 0xe3a, - 0xe48, - 0xe49, - 0xe4a, - 0xe4b, - 0xeb8, - 0xeb9, - 0xeba, - 0xec8, - 0xec9, - 0xeca, - 0xecb, - 0xf18, - 0xf19, - 0xf35, - 0xf37, - 0xf39, - 0xf71, - 0xf72, - 0xf73, - 0xf74, - 0xf75, - 0xf7a, - 0xf7b, - 0xf7c, - 0xf7d, - 0xf80, - 0xf81, - 0xf82, - 0xf83, - 0xf84, - 0xf86, - 0xf87, - 0xfc6, - 0x1037, - 0x1039, - 0x103a, - 0x108d, - 0x135d, - 0x135e, - 0x135f, - 0x1714, - 0x1715, - 0x1734, - 0x17d2, - 0x17dd, - 0x18a9, - 0x1939, - 0x193a, - 0x193b, - 0x1a17, - 0x1a18, - 0x1a60, - 0x1a75, - 0x1a76, - 0x1a77, - 0x1a78, - 0x1a79, - 0x1a7a, - 0x1a7b, - 0x1a7c, - 0x1a7f, - 0x1ab0, - 0x1ab1, - 0x1ab2, - 0x1ab3, - 0x1ab4, - 0x1ab5, - 0x1ab6, - 0x1ab7, - 0x1ab8, - 0x1ab9, - 0x1aba, - 0x1abb, - 0x1abc, - 0x1abd, - 0x1abf, - 0x1ac0, - 0x1ac1, - 0x1ac2, - 0x1ac3, - 0x1ac4, - 0x1ac5, - 0x1ac6, - 0x1ac7, - 0x1ac8, - 0x1ac9, - 0x1aca, - 0x1acb, - 0x1acc, - 0x1acd, - 0x1ace, - 0x1b34, - 0x1b44, - 0x1b6b, - 0x1b6c, - 0x1b6d, - 0x1b6e, - 0x1b6f, - 0x1b70, - 0x1b71, - 0x1b72, - 0x1b73, - 0x1baa, - 0x1bab, - 0x1be6, - 0x1bf2, - 0x1bf3, - 0x1c37, - 0x1cd0, - 0x1cd1, - 0x1cd2, - 0x1cd4, - 0x1cd5, - 0x1cd6, - 0x1cd7, - 0x1cd8, - 0x1cd9, - 0x1cda, - 0x1cdb, - 0x1cdc, - 0x1cdd, - 0x1cde, - 0x1cdf, - 0x1ce0, - 0x1ce2, - 0x1ce3, - 0x1ce4, - 0x1ce5, - 0x1ce6, - 0x1ce7, - 0x1ce8, - 0x1ced, - 0x1cf4, - 0x1cf8, - 0x1cf9, - 0x1dc0, - 0x1dc1, - 0x1dc2, - 0x1dc3, - 0x1dc4, - 0x1dc5, - 0x1dc6, - 0x1dc7, - 0x1dc8, - 0x1dc9, - 0x1dca, - 0x1dcb, - 0x1dcc, - 0x1dcd, - 0x1dce, - 0x1dcf, - 0x1dd0, - 0x1dd1, - 0x1dd2, - 0x1dd3, - 0x1dd4, - 0x1dd5, - 0x1dd6, - 0x1dd7, - 0x1dd8, - 0x1dd9, - 0x1dda, - 0x1ddb, - 0x1ddc, - 0x1ddd, - 0x1dde, - 0x1ddf, - 0x1de0, - 0x1de1, - 0x1de2, - 0x1de3, - 0x1de4, - 0x1de5, - 0x1de6, - 0x1de7, - 0x1de8, - 0x1de9, - 0x1dea, - 0x1deb, - 0x1dec, - 0x1ded, - 0x1dee, - 0x1def, - 0x1df0, - 0x1df1, - 0x1df2, - 0x1df3, - 0x1df4, - 0x1df5, - 0x1df6, - 0x1df7, - 0x1df8, - 0x1df9, - 0x1dfa, - 0x1dfb, - 0x1dfc, - 0x1dfd, - 0x1dfe, - 0x1dff, - 0x20d0, - 0x20d1, - 0x20d2, - 0x20d3, - 0x20d4, - 0x20d5, - 0x20d6, - 0x20d7, - 0x20d8, - 0x20d9, - 0x20da, - 0x20db, - 0x20dc, - 0x20e1, - 0x20e5, - 0x20e6, - 0x20e7, - 0x20e8, - 0x20e9, - 0x20ea, - 0x20eb, - 0x20ec, - 0x20ed, - 0x20ee, - 0x20ef, - 0x20f0, - 0x2cef, - 0x2cf0, - 0x2cf1, - 0x2d7f, - 0x2de0, - 0x2de1, - 0x2de2, - 0x2de3, - 0x2de4, - 0x2de5, - 0x2de6, - 0x2de7, - 0x2de8, - 0x2de9, - 0x2dea, - 0x2deb, - 0x2dec, - 0x2ded, - 0x2dee, - 0x2def, - 0x2df0, - 0x2df1, - 0x2df2, - 0x2df3, - 0x2df4, - 0x2df5, - 0x2df6, - 0x2df7, - 0x2df8, - 0x2df9, - 0x2dfa, - 0x2dfb, - 0x2dfc, - 0x2dfd, - 0x2dfe, - 0x2dff, - 0x302a, - 0x302b, - 0x302c, - 0x302d, - 0x302e, - 0x302f, - 0x3099, - 0x309a, - 0xa66f, - 0xa674, - 0xa675, - 0xa676, - 0xa677, - 0xa678, - 0xa679, - 0xa67a, - 0xa67b, - 0xa67c, - 0xa67d, - 0xa69e, - 0xa69f, - 0xa6f0, - 0xa6f1, - 0xa806, - 0xa82c, - 0xa8c4, - 0xa8e0, - 0xa8e1, - 0xa8e2, - 0xa8e3, - 0xa8e4, - 0xa8e5, - 0xa8e6, - 0xa8e7, - 0xa8e8, - 0xa8e9, - 0xa8ea, - 0xa8eb, - 0xa8ec, - 0xa8ed, - 0xa8ee, - 0xa8ef, - 0xa8f0, - 0xa8f1, - 0xa92b, - 0xa92c, - 0xa92d, - 0xa953, - 0xa9b3, - 0xa9c0, - 0xaab0, - 0xaab2, - 0xaab3, - 0xaab4, - 0xaab7, - 0xaab8, - 0xaabe, - 0xaabf, - 0xaac1, - 0xaaf6, - 0xabed, - 0xfb1e, - 0xfe20, - 0xfe21, - 0xfe22, - 0xfe23, - 0xfe24, - 0xfe25, - 0xfe26, - 0xfe27, - 0xfe28, - 0xfe29, - 0xfe2a, - 0xfe2b, - 0xfe2c, - 0xfe2d, - 0xfe2e, - 0xfe2f, - 0x101fd, - 0x102e0, - 0x10376, - 0x10377, - 0x10378, - 0x10379, - 0x1037a, - 0x10a0d, - 0x10a0f, - 0x10a38, - 0x10a39, - 0x10a3a, - 0x10a3f, - 0x10ae5, - 0x10ae6, - 0x10d24, - 0x10d25, - 0x10d26, - 0x10d27, - 0x10eab, - 0x10eac, - 0x10efd, - 0x10efe, - 0x10eff, - 0x10f46, - 0x10f47, - 0x10f48, - 0x10f49, - 0x10f4a, - 0x10f4b, - 0x10f4c, - 0x10f4d, - 0x10f4e, - 0x10f4f, - 0x10f50, - 0x10f82, - 0x10f83, - 0x10f84, - 0x10f85, - 0x11046, - 0x11070, - 0x1107f, - 0x110b9, - 0x110ba, - 0x11100, - 0x11101, - 0x11102, - 0x11133, - 0x11134, - 0x11173, - 0x111c0, - 0x111ca, - 0x11235, - 0x11236, - 0x112e9, - 0x112ea, - 0x1133b, - 0x1133c, - 0x1134d, - 0x11366, - 0x11367, - 0x11368, - 0x11369, - 0x1136a, - 0x1136b, - 0x1136c, - 0x11370, - 0x11371, - 0x11372, - 0x11373, - 0x11374, - 0x11442, - 0x11446, - 0x1145e, - 0x114c2, - 0x114c3, - 0x115bf, - 0x115c0, - 0x1163f, - 0x116b6, - 0x116b7, - 0x1172b, - 0x11839, - 0x1183a, - 0x1193d, - 0x1193e, - 0x11943, - 0x119e0, - 0x11a34, - 0x11a47, - 0x11a99, - 0x11c3f, - 0x11d42, - 0x11d44, - 0x11d45, - 0x11d97, - 0x11f41, - 0x11f42, - 0x16af0, - 0x16af1, - 0x16af2, - 0x16af3, - 0x16af4, - 0x16b30, - 0x16b31, - 0x16b32, - 0x16b33, - 0x16b34, - 0x16b35, - 0x16b36, - 0x16ff0, - 0x16ff1, - 0x1bc9e, - 0x1d165, - 0x1d166, - 0x1d167, - 0x1d168, - 0x1d169, - 0x1d16d, - 0x1d16e, - 0x1d16f, - 0x1d170, - 0x1d171, - 0x1d172, - 0x1d17b, - 0x1d17c, - 0x1d17d, - 0x1d17e, - 0x1d17f, - 0x1d180, - 0x1d181, - 0x1d182, - 0x1d185, - 0x1d186, - 0x1d187, - 0x1d188, - 0x1d189, - 0x1d18a, - 0x1d18b, - 0x1d1aa, - 0x1d1ab, - 0x1d1ac, - 0x1d1ad, - 0x1d242, - 0x1d243, - 0x1d244, - 0x1e000, - 0x1e001, - 0x1e002, - 0x1e003, - 0x1e004, - 0x1e005, - 0x1e006, - 0x1e008, - 0x1e009, - 0x1e00a, - 0x1e00b, - 0x1e00c, - 0x1e00d, - 0x1e00e, - 0x1e00f, - 0x1e010, - 0x1e011, - 0x1e012, - 0x1e013, - 0x1e014, - 0x1e015, - 0x1e016, - 0x1e017, - 0x1e018, - 0x1e01b, - 0x1e01c, - 0x1e01d, - 0x1e01e, - 0x1e01f, - 0x1e020, - 0x1e021, - 0x1e023, - 0x1e024, - 0x1e026, - 0x1e027, - 0x1e028, - 0x1e029, - 0x1e02a, - 0x1e08f, - 0x1e130, - 0x1e131, - 0x1e132, - 0x1e133, - 0x1e134, - 0x1e135, - 0x1e136, - 0x1e2ae, - 0x1e2ec, - 0x1e2ed, - 0x1e2ee, - 0x1e2ef, - 0x1e4ec, - 0x1e4ed, - 0x1e4ee, - 0x1e4ef, - 0x1e8d0, - 0x1e8d1, - 0x1e8d2, - 0x1e8d3, - 0x1e8d4, - 0x1e8d5, - 0x1e8d6, - 0x1e944, - 0x1e945, - 0x1e946, - 0x1e947, - 0x1e948, - 0x1e949, - 0x1e94a, - 0x0, +#define km_noBoundaryBefore_entries 190 +static char32_t km_noBoundaryBefore[km_noBoundaryBefore_entries * 2 ] = { + 0x300, 79, // ...0x34e + 0x350, 32, // ...0x36f + 0x483, 5, // ...0x487 + 0x591, 45, // ...0x5bd + 0x5bf, 1, // ...0x5bf + 0x5c1, 2, // ...0x5c2 + 0x5c4, 2, // ...0x5c5 + 0x5c7, 1, // ...0x5c7 + 0x610, 11, // ...0x61a + 0x64b, 21, // ...0x65f + 0x670, 1, // ...0x670 + 0x6d6, 7, // ...0x6dc + 0x6df, 6, // ...0x6e4 + 0x6e7, 2, // ...0x6e8 + 0x6ea, 4, // ...0x6ed + 0x711, 1, // ...0x711 + 0x730, 27, // ...0x74a + 0x7eb, 9, // ...0x7f3 + 0x7fd, 1, // ...0x7fd + 0x816, 4, // ...0x819 + 0x81b, 9, // ...0x823 + 0x825, 3, // ...0x827 + 0x829, 5, // ...0x82d + 0x859, 3, // ...0x85b + 0x898, 8, // ...0x89f + 0x8ca, 24, // ...0x8e1 + 0x8e3, 29, // ...0x8ff + 0x93c, 1, // ...0x93c + 0x94d, 1, // ...0x94d + 0x951, 4, // ...0x954 + 0x9bc, 1, // ...0x9bc + 0x9cd, 1, // ...0x9cd + 0x9fe, 1, // ...0x9fe + 0xa3c, 1, // ...0xa3c + 0xa4d, 1, // ...0xa4d + 0xabc, 1, // ...0xabc + 0xacd, 1, // ...0xacd + 0xb3c, 1, // ...0xb3c + 0xb4d, 1, // ...0xb4d + 0xbcd, 1, // ...0xbcd + 0xc3c, 1, // ...0xc3c + 0xc4d, 1, // ...0xc4d + 0xc55, 2, // ...0xc56 + 0xcbc, 1, // ...0xcbc + 0xccd, 1, // ...0xccd + 0xd3b, 2, // ...0xd3c + 0xd4d, 1, // ...0xd4d + 0xdca, 1, // ...0xdca + 0xe38, 3, // ...0xe3a + 0xe48, 4, // ...0xe4b + 0xeb8, 3, // ...0xeba + 0xec8, 4, // ...0xecb + 0xf18, 2, // ...0xf19 + 0xf35, 1, // ...0xf35 + 0xf37, 1, // ...0xf37 + 0xf39, 1, // ...0xf39 + 0xf71, 5, // ...0xf75 + 0xf7a, 4, // ...0xf7d + 0xf80, 5, // ...0xf84 + 0xf86, 2, // ...0xf87 + 0xfc6, 1, // ...0xfc6 + 0x1037, 1, // ...0x1037 + 0x1039, 2, // ...0x103a + 0x108d, 1, // ...0x108d + 0x135d, 3, // ...0x135f + 0x1714, 2, // ...0x1715 + 0x1734, 1, // ...0x1734 + 0x17d2, 1, // ...0x17d2 + 0x17dd, 1, // ...0x17dd + 0x18a9, 1, // ...0x18a9 + 0x1939, 3, // ...0x193b + 0x1a17, 2, // ...0x1a18 + 0x1a60, 1, // ...0x1a60 + 0x1a75, 8, // ...0x1a7c + 0x1a7f, 1, // ...0x1a7f + 0x1ab0, 14, // ...0x1abd + 0x1abf, 16, // ...0x1ace + 0x1b34, 1, // ...0x1b34 + 0x1b44, 1, // ...0x1b44 + 0x1b6b, 9, // ...0x1b73 + 0x1baa, 2, // ...0x1bab + 0x1be6, 1, // ...0x1be6 + 0x1bf2, 2, // ...0x1bf3 + 0x1c37, 1, // ...0x1c37 + 0x1cd0, 3, // ...0x1cd2 + 0x1cd4, 13, // ...0x1ce0 + 0x1ce2, 7, // ...0x1ce8 + 0x1ced, 1, // ...0x1ced + 0x1cf4, 1, // ...0x1cf4 + 0x1cf8, 2, // ...0x1cf9 + 0x1dc0, 64, // ...0x1dff + 0x20d0, 13, // ...0x20dc + 0x20e1, 1, // ...0x20e1 + 0x20e5, 12, // ...0x20f0 + 0x2cef, 3, // ...0x2cf1 + 0x2d7f, 1, // ...0x2d7f + 0x2de0, 32, // ...0x2dff + 0x302a, 6, // ...0x302f + 0x3099, 2, // ...0x309a + 0xa66f, 1, // ...0xa66f + 0xa674, 10, // ...0xa67d + 0xa69e, 2, // ...0xa69f + 0xa6f0, 2, // ...0xa6f1 + 0xa806, 1, // ...0xa806 + 0xa82c, 1, // ...0xa82c + 0xa8c4, 1, // ...0xa8c4 + 0xa8e0, 18, // ...0xa8f1 + 0xa92b, 3, // ...0xa92d + 0xa953, 1, // ...0xa953 + 0xa9b3, 1, // ...0xa9b3 + 0xa9c0, 1, // ...0xa9c0 + 0xaab0, 1, // ...0xaab0 + 0xaab2, 3, // ...0xaab4 + 0xaab7, 2, // ...0xaab8 + 0xaabe, 2, // ...0xaabf + 0xaac1, 1, // ...0xaac1 + 0xaaf6, 1, // ...0xaaf6 + 0xabed, 1, // ...0xabed + 0xfb1e, 1, // ...0xfb1e + 0xfe20, 16, // ...0xfe2f + 0x101fd, 1, // ...0x101fd + 0x102e0, 1, // ...0x102e0 + 0x10376, 5, // ...0x1037a + 0x10a0d, 1, // ...0x10a0d + 0x10a0f, 1, // ...0x10a0f + 0x10a38, 3, // ...0x10a3a + 0x10a3f, 1, // ...0x10a3f + 0x10ae5, 2, // ...0x10ae6 + 0x10d24, 4, // ...0x10d27 + 0x10eab, 2, // ...0x10eac + 0x10efd, 3, // ...0x10eff + 0x10f46, 11, // ...0x10f50 + 0x10f82, 4, // ...0x10f85 + 0x11046, 1, // ...0x11046 + 0x11070, 1, // ...0x11070 + 0x1107f, 1, // ...0x1107f + 0x110b9, 2, // ...0x110ba + 0x11100, 3, // ...0x11102 + 0x11133, 2, // ...0x11134 + 0x11173, 1, // ...0x11173 + 0x111c0, 1, // ...0x111c0 + 0x111ca, 1, // ...0x111ca + 0x11235, 2, // ...0x11236 + 0x112e9, 2, // ...0x112ea + 0x1133b, 2, // ...0x1133c + 0x1134d, 1, // ...0x1134d + 0x11366, 7, // ...0x1136c + 0x11370, 5, // ...0x11374 + 0x11442, 1, // ...0x11442 + 0x11446, 1, // ...0x11446 + 0x1145e, 1, // ...0x1145e + 0x114c2, 2, // ...0x114c3 + 0x115bf, 2, // ...0x115c0 + 0x1163f, 1, // ...0x1163f + 0x116b6, 2, // ...0x116b7 + 0x1172b, 1, // ...0x1172b + 0x11839, 2, // ...0x1183a + 0x1193d, 2, // ...0x1193e + 0x11943, 1, // ...0x11943 + 0x119e0, 1, // ...0x119e0 + 0x11a34, 1, // ...0x11a34 + 0x11a47, 1, // ...0x11a47 + 0x11a99, 1, // ...0x11a99 + 0x11c3f, 1, // ...0x11c3f + 0x11d42, 1, // ...0x11d42 + 0x11d44, 2, // ...0x11d45 + 0x11d97, 1, // ...0x11d97 + 0x11f41, 2, // ...0x11f42 + 0x16af0, 5, // ...0x16af4 + 0x16b30, 7, // ...0x16b36 + 0x16ff0, 2, // ...0x16ff1 + 0x1bc9e, 1, // ...0x1bc9e + 0x1d165, 5, // ...0x1d169 + 0x1d16d, 6, // ...0x1d172 + 0x1d17b, 8, // ...0x1d182 + 0x1d185, 7, // ...0x1d18b + 0x1d1aa, 4, // ...0x1d1ad + 0x1d242, 3, // ...0x1d244 + 0x1e000, 7, // ...0x1e006 + 0x1e008, 17, // ...0x1e018 + 0x1e01b, 7, // ...0x1e021 + 0x1e023, 2, // ...0x1e024 + 0x1e026, 5, // ...0x1e02a + 0x1e08f, 1, // ...0x1e08f + 0x1e130, 7, // ...0x1e136 + 0x1e2ae, 1, // ...0x1e2ae + 0x1e2ec, 4, // ...0x1e2ef + 0x1e4ec, 4, // ...0x1e4ef + 0x1e8d0, 7, // ...0x1e8d6 + 0x1e944, 7, // ...0x1e94a }; From fdb2e95d20429e0f2453a541210bca62fd9ad033 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Tue, 4 Jun 2024 12:26:00 -0500 Subject: [PATCH 06/14] feat(core): build improvements for --update-unicode option Fixes: #9467 --- core/build.sh | 1 + core/tools/meson.build | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/core/build.sh b/core/build.sh index 016431cfdd..ad133ac33a 100755 --- a/core/build.sh +++ b/core/build.sh @@ -179,6 +179,7 @@ if builder_start_action test:mac; then fi if builder_has_option --update-unicode; then + # TODO: only works under mac. What's the right way to get the arch here? meson compile -C ${KEYMAN_ROOT}/core/build/mac-x86_64/$BUILDER_CONFIGURATION tools/norm_data && mv -v ${KEYMAN_ROOT}/core/build/mac-x86_64/$BUILDER_CONFIGURATION/tools/nfd_table.h ${KEYMAN_ROOT}/resources/standards-data/unicode-character-database/ fi diff --git a/core/tools/meson.build b/core/tools/meson.build index 31f3a00848..5221b2a6af 100644 --- a/core/tools/meson.build +++ b/core/tools/meson.build @@ -15,18 +15,11 @@ else warns = [] endif - -if cpp_compiler.get_id() == 'emscripten' - tests_flags += ['-lnodefs.js', - '-sNO_DISABLE_EXCEPTION_CATCHING', # for test exceptions - wasm_exported_runtime_methods] -endif - norm_unicode_update = executable('norm_unicode_update', ['norm_unicode_update.cpp'], cpp_args: defns + warns, include_directories: [inc, libsrc, '../../developer/src/ext/json'], - link_args: links + tests_flags, + link_args: links, dependencies: [icu_uc, icu_i18n], # link_with: [lib], objects: lib.extract_all_objects(recursive: false), From f156a7264dd769f73f6fbc83db4fc0970f888464 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Wed, 5 Jun 2024 15:47:20 -0500 Subject: [PATCH 07/14] feat(core): generator in core/src for util_normalize_table.h - temporary header file generated by wasm during build - built using icu - test_unicode verifies the contents and synchronization with running ICU. - this is used by util_normalize to provide normalization properties under wasm without needing to include ICU. Fixes: #9467 --- core/build.sh | 7 - core/meson.build | 1 - core/src/meson.build | 24 +++ core/src/util_normalize.cpp | 2 +- core/src/util_normalize_table_generator.cpp | 108 ++++++++++ core/tests/unit/ldml/meson.build | 2 +- core/tests/unit/ldml/test_unicode.cpp | 16 +- core/tools/meson.build | 33 --- core/tools/norm_unicode_update.cpp | 84 -------- .../unicode-character-database/nfd_table.h | 197 ------------------ 10 files changed, 145 insertions(+), 329 deletions(-) create mode 100644 core/src/util_normalize_table_generator.cpp delete mode 100644 core/tools/meson.build delete mode 100644 core/tools/norm_unicode_update.cpp delete mode 100644 resources/standards-data/unicode-character-database/nfd_table.h diff --git a/core/build.sh b/core/build.sh index ad133ac33a..168cc1cbcf 100755 --- a/core/build.sh +++ b/core/build.sh @@ -68,7 +68,6 @@ Libraries will be built in 'build///src'. "uninstall uninstall libraries from current system" \ "${archtargets[@]}" \ "--no-tests do not configure tests (used by other projects)" \ - "--update-unicode rebuild tables if the ICU4C Unicode version changes" \ "--test,-t=opt_tests test[s] to run (space separated)" builder_parse "$@" @@ -178,12 +177,6 @@ if builder_start_action test:mac; then builder_finish_action success test:mac fi -if builder_has_option --update-unicode; then - # TODO: only works under mac. What's the right way to get the arch here? - meson compile -C ${KEYMAN_ROOT}/core/build/mac-x86_64/$BUILDER_CONFIGURATION tools/norm_data && mv -v ${KEYMAN_ROOT}/core/build/mac-x86_64/$BUILDER_CONFIGURATION/tools/nfd_table.h ${KEYMAN_ROOT}/resources/standards-data/unicode-character-database/ -fi - - # ------------------------------------------------------------------------------- do_action install diff --git a/core/meson.build b/core/meson.build index ff306991e9..0049c95741 100644 --- a/core/meson.build +++ b/core/meson.build @@ -44,4 +44,3 @@ subdir('doc') subdir('include') subdir('src') subdir('tests') -subdir('tools') diff --git a/core/src/meson.build b/core/src/meson.build index 5632c22257..a4160842eb 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -40,6 +40,29 @@ if icu_uc.found() defns += '-DHAVE_ICU4C' endif +# On wasm, generate util_normalize_table.h automatically from ICU +generated_headers = [] + +if cpp_compiler.get_id() == 'emscripten' + +util_normalize_table_generator = executable('util_normalize_table_generator', + ['util_normalize_table_generator.cpp'], + cpp_args: defns + warns, + include_directories: [inc], + link_args: links, + dependencies: [icu_uc, icu_i18n], + ) + +util_normalize_table_h = custom_target('util_normalize_table.h', + output: 'util_normalize_table.h', + command: [util_normalize_table_generator], + capture:true) + +generated_headers += util_normalize_table_h + + +endif + kmx_files = files( 'actions_normalize.cpp', @@ -111,6 +134,7 @@ lib = library('keymancore', kmx_files, mock_files, version_res, + generated_headers, cpp_args: defns + warns + flags, link_args: links, version: lib_version, diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index 7d0d76d8ab..947dbe0f85 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -31,7 +31,7 @@ EM_JS(char*, NormalizeNFC, (const char* input), { }); // pull in the generated table -#include "../../resources/standards-data/unicode-character-database/nfd_table.h" +#include "util_normalize_table.h" #endif diff --git a/core/src/util_normalize_table_generator.cpp b/core/src/util_normalize_table_generator.cpp new file mode 100644 index 0000000000..b5895a588b --- /dev/null +++ b/core/src/util_normalize_table_generator.cpp @@ -0,0 +1,108 @@ +/* + Copyright: © SIL International. + Description: Generator for util_normalize_table.h + Create Date: 5 Jun 2024 + Authors: Steven R. Loomis + + util_normalize_table.h is used under wasm by utilities in util_normalize.cpp to implement + normalization functions without needing ICU4C linked. + + This generator is invoked automatically by meson as part of the build. +*/ + +#include "kmx/kmx_plus.h" +#include "kmx/kmx_xstring.h" + +#define KMN_NO_ICU 0 // we will need ICU.. + +#include "core_icu.h" + +#include +#include +#include +#include + +#include + +#include + + + +int +write_nfd_table() { +#ifndef __EMSCRIPTEN__ + std::cerr << "Note: This is unusual - this generator is usually only run under emscripten!" << std::endl; +#endif + + // We write to stdout instead of to a file to avoid dealing with the filesystem under emscripten. + + std::cerr << "Writing to stdout." << std::endl; + + // write preamble + std::cout << "// GENERATED FILE: DO NOT EDIT" << std::endl; + std::cout << "//" << std::endl; + std::cout << "// util_normalize_table.h is generated by util_normalize_table_generator.cpp" << std::endl; + std::cout << "// and used by util_normalize.cpp" << std::endl; + std::cout << std::endl; + std::cout << "#pragma once" << std::endl; + std::cout << "#define KM_HASBOUNDARYBEFORE_UNICODE_VERSION \"" << U_UNICODE_VERSION << "\"" << std::endl; + std::cout << "#define KM_HASBOUNDARYBEFORE_ICU_VERSION \"" << U_ICU_VERSION << "\"" << std::endl; + std::cout << std::endl; + // we're going to need an NFD normalizer + UErrorCode status = U_ZERO_ERROR; + const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); + assert(U_SUCCESS(status)); + + // collect the raw list of chars that do NOT have a boundary before them. + std::vector noBoundary; + for (km_core_usv ch = 0; ch < 0x10FFFF; ch++) { + bool bb = nfd->hasBoundaryBefore(ch); + assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator + if (bb) continue; //only emit nonboundary + noBoundary.push_back(ch); + } + + // now, compress these into runs + std::vector> runs; // start,len + + km_core_usv first = 0; + km_core_usv last = 0; + for(auto i = noBoundary.begin(); i <= noBoundary.end(); i++) { + if (first == 0) { + first = last = *i; + } else { + last++; + if(i == noBoundary.end() || *i != last) { + // end of a run + runs.emplace_back(first, last - first); + if (i != noBoundary.end()) { + // setup for next + first = last = *i; + } + } + } + } + + // finally, write out metadata and the runs themselves. + std::cout << "#define km_noBoundaryBefore_entries " << runs.size() << "\n"; + + std::cout << "static char32_t km_noBoundaryBefore[km_noBoundaryBefore_entries * 2 ] = {" << std::endl; + + std::cout << "/* start codepoint, count (inclusive), ...range end */" << std::endl; + + for (auto i = runs.begin(); i < runs.end(); i++) { + std::cout << "\t0x" << std::hex << i->first << std::dec << ",\t " << i->second << ", // ...0x" << std::hex << (i->first+i->second-1) << std::endl; + } + + // termination + std::cout << "};" << std::endl; + std::cout << "// end" << std::endl; + std::cerr << "Wrote " << runs.size() << " runs representing " << noBoundary.size() << " entries." << std::endl; + return 0; +} + +int +main(int /*argc*/, const char * /*argv*/[]) { + write_nfd_table(); + return 0; +} diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build index da60757750..42f2d7778e 100644 --- a/core/tests/unit/ldml/meson.build +++ b/core/tests/unit/ldml/meson.build @@ -132,7 +132,7 @@ test('test_context_normalization', test_context_normalization, suite: 'ldml') # Build and run additional test_unicode test test_unicode = executable('test_unicode', 'test_unicode.cpp', - ['test_unicode.cpp', common_test_files], + ['test_unicode.cpp', common_test_files, generated_headers], cpp_args: defns + warns, include_directories: [inc, libsrc, '../../../../developer/src/ext/json'], link_args: links + tests_flags, diff --git a/core/tests/unit/ldml/test_unicode.cpp b/core/tests/unit/ldml/test_unicode.cpp index 50844d635b..b36f1a3e38 100644 --- a/core/tests/unit/ldml/test_unicode.cpp +++ b/core/tests/unit/ldml/test_unicode.cpp @@ -47,7 +47,7 @@ #ifdef __EMSCRIPTEN__ // Pull this in to verify versions -#include "../../../../resources/standards-data/unicode-character-database/nfd_table.h" +#include "util_normalize_table.h" #endif //------------------------------------------------------------------------------------- @@ -189,9 +189,15 @@ inline const char *boolstr(bool b) { void test_has_boundary_before() { std::cout << "= " << __FUNCTION__ << std::endl; - std::cout << "(this test only runs under emscripten. congratulations.)" << std::endl; - // static_assert(U_UNICODE_VERSION == KM_HASBOUNDARYBEFORE_UNICODE_VERSION, "nfd_table.h Unicode version does not match ICU's - see nfd_table.h"); - std::cout << U_UNICODE_VERSION << "≈≈" << KM_HASBOUNDARYBEFORE_UNICODE_VERSION << std::endl; + std::cout << "I see we are on Emscripten / wasm! Now we will do some additional tests." << std::endl; + std::string icu4c_unicode(U_UNICODE_VERSION), header_unicode(KM_HASBOUNDARYBEFORE_UNICODE_VERSION), + icu4c_icu(U_ICU_VERSION), header_icu(KM_HASBOUNDARYBEFORE_ICU_VERSION); + std::cout << "Unicode: " << U_UNICODE_VERSION << ", and from the table file: " << KM_HASBOUNDARYBEFORE_UNICODE_VERSION << std::endl; + std::cout << "It would be very strange for these versions to be out of sync. Some sort of build or tool problem." << std::endl; + assert_basic_equal(icu4c_unicode, header_unicode); + assert_basic_equal(icu4c_icu, header_icu); + + std::cout << std::endl << "Now, let's make sure has_nfd_boundary_before() matches ICU." << std::endl; UErrorCode status = U_ZERO_ERROR; const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); @@ -203,7 +209,7 @@ void test_has_boundary_before() { auto icu_hbb = nfd->hasBoundaryBefore(cp); if (km_hbb != icu_hbb) { - std::cerr << "Error: nfd_table.h said " << boolstr(km_hbb) << " but ICU said " << boolstr(icu_hbb) << " for " + std::cerr << "Error: util_normalize_table.h said " << boolstr(km_hbb) << " but ICU said " << boolstr(icu_hbb) << " for " << "has_nfd_boundary_before(0x" << std::hex << cp << std::dec << ")" << std::endl; } assert(km_hbb == icu_hbb); diff --git a/core/tools/meson.build b/core/tools/meson.build deleted file mode 100644 index 5221b2a6af..0000000000 --- a/core/tools/meson.build +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright: © 2024 SIL International. -# Description: Cross platform build script to compile tool(s). -# Create Date: 31 May 2024 -# Authors: Steven R. Loomis (SRL) -# - - -# TODO -- why are these differing from the standard.meson.build flags? -if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' or cpp_compiler.get_id() == 'emscripten' - warns = [ - '-Wno-missing-field-initializers', - '-Wno-unused-parameter' - ] -else - warns = [] -endif - -norm_unicode_update = executable('norm_unicode_update', - ['norm_unicode_update.cpp'], - cpp_args: defns + warns, - include_directories: [inc, libsrc, '../../developer/src/ext/json'], - link_args: links, - dependencies: [icu_uc, icu_i18n], - # link_with: [lib], - objects: lib.extract_all_objects(recursive: false), - ) - - -# ../../resources/standards-data/unicode-character-database/ -norm_data = custom_target('norm_data', output: 'nfd_table.h', command: [norm_unicode_update, '@OUTPUT@']) - - -# TODO: execute it diff --git a/core/tools/norm_unicode_update.cpp b/core/tools/norm_unicode_update.cpp deleted file mode 100644 index 408dbb0c98..0000000000 --- a/core/tools/norm_unicode_update.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "kmx/kmx_plus.h" -#include "kmx/kmx_xstring.h" -#include "core_icu.h" - -#include -#include -#include -#include - -#include - -#include - -#ifndef __EMSCRIPTEN__ - -int -write_nfd_table(const char *NFD_FILE) { - std::cout << " writing: " << NFD_FILE << std::endl; - auto f = std::ofstream(NFD_FILE); - assert(f.good()); - - // write preamble - f << "//NFD hasBoundaryBefore" << std::endl; - f << "#pragma once" << std::endl; - f << "#define KM_HASBOUNDARYBEFORE_UNICODE_VERSION \"" << U_UNICODE_VERSION << "\"" << std::endl; - f << "#define KM_HASBOUNDARYBEFORE_ICU_VERSION \"" << U_ICU_VERSION << "\"" << std::endl; - // we're going to need an NFD normalizer - UErrorCode status = U_ZERO_ERROR; - const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status); - assert(U_SUCCESS(status)); - - std::vector noBoundary; - - for (km_core_usv ch = 0; ch < 0x10FFFF; ch++) { - bool bb = nfd->hasBoundaryBefore(ch); - assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator - if (bb) continue; //only emit nonboundary - noBoundary.push_back(ch); - } - - std::vector> runs; // start,len - - km_core_usv first = 0; - km_core_usv last = 0; - for(auto i = noBoundary.begin(); i <= noBoundary.end(); i++) { - if (first == 0) { - first = last = *i; - } else { - last++; - if(i == noBoundary.end() || *i != last) { - // end of a run - runs.emplace_back(first, last - first); - if (i != noBoundary.end()) { - // setup for next - first = last = *i; - } - } - } - } - f << "#define km_noBoundaryBefore_entries " << runs.size() << "\n"; - - f << "static char32_t km_noBoundaryBefore[km_noBoundaryBefore_entries * 2 ] = {" << std::endl; - - for (auto i = runs.begin(); i < runs.end(); i++) { - f << "\t0x" << std::hex << i->first << std::dec << ",\t " << i->second << ", // ...0x" << std::hex << (i->first+i->second-1) << std::endl; - } - - // termination - f << "};" << std::endl; - return 0; -} - -int -main(int argc, const char *argv[]) { - assert(argc == 2); // call with one param: @OUTPUT@ - write_nfd_table(argv[1]); - return 0; -} -#else -int main(int argc, const char *argv[]) { - std::cerr << "Can't run this under Emscripten - run under another platform." << std::endl; - return 1; -} -#endif diff --git a/resources/standards-data/unicode-character-database/nfd_table.h b/resources/standards-data/unicode-character-database/nfd_table.h deleted file mode 100644 index 068240fc30..0000000000 --- a/resources/standards-data/unicode-character-database/nfd_table.h +++ /dev/null @@ -1,197 +0,0 @@ -//NFD hasBoundaryBefore -#pragma once -#define KM_HASBOUNDARYBEFORE_UNICODE_VERSION "15.0" -#define KM_HASBOUNDARYBEFORE_ICU_VERSION "73.1" -#define km_noBoundaryBefore_entries 190 -static char32_t km_noBoundaryBefore[km_noBoundaryBefore_entries * 2 ] = { - 0x300, 79, // ...0x34e - 0x350, 32, // ...0x36f - 0x483, 5, // ...0x487 - 0x591, 45, // ...0x5bd - 0x5bf, 1, // ...0x5bf - 0x5c1, 2, // ...0x5c2 - 0x5c4, 2, // ...0x5c5 - 0x5c7, 1, // ...0x5c7 - 0x610, 11, // ...0x61a - 0x64b, 21, // ...0x65f - 0x670, 1, // ...0x670 - 0x6d6, 7, // ...0x6dc - 0x6df, 6, // ...0x6e4 - 0x6e7, 2, // ...0x6e8 - 0x6ea, 4, // ...0x6ed - 0x711, 1, // ...0x711 - 0x730, 27, // ...0x74a - 0x7eb, 9, // ...0x7f3 - 0x7fd, 1, // ...0x7fd - 0x816, 4, // ...0x819 - 0x81b, 9, // ...0x823 - 0x825, 3, // ...0x827 - 0x829, 5, // ...0x82d - 0x859, 3, // ...0x85b - 0x898, 8, // ...0x89f - 0x8ca, 24, // ...0x8e1 - 0x8e3, 29, // ...0x8ff - 0x93c, 1, // ...0x93c - 0x94d, 1, // ...0x94d - 0x951, 4, // ...0x954 - 0x9bc, 1, // ...0x9bc - 0x9cd, 1, // ...0x9cd - 0x9fe, 1, // ...0x9fe - 0xa3c, 1, // ...0xa3c - 0xa4d, 1, // ...0xa4d - 0xabc, 1, // ...0xabc - 0xacd, 1, // ...0xacd - 0xb3c, 1, // ...0xb3c - 0xb4d, 1, // ...0xb4d - 0xbcd, 1, // ...0xbcd - 0xc3c, 1, // ...0xc3c - 0xc4d, 1, // ...0xc4d - 0xc55, 2, // ...0xc56 - 0xcbc, 1, // ...0xcbc - 0xccd, 1, // ...0xccd - 0xd3b, 2, // ...0xd3c - 0xd4d, 1, // ...0xd4d - 0xdca, 1, // ...0xdca - 0xe38, 3, // ...0xe3a - 0xe48, 4, // ...0xe4b - 0xeb8, 3, // ...0xeba - 0xec8, 4, // ...0xecb - 0xf18, 2, // ...0xf19 - 0xf35, 1, // ...0xf35 - 0xf37, 1, // ...0xf37 - 0xf39, 1, // ...0xf39 - 0xf71, 5, // ...0xf75 - 0xf7a, 4, // ...0xf7d - 0xf80, 5, // ...0xf84 - 0xf86, 2, // ...0xf87 - 0xfc6, 1, // ...0xfc6 - 0x1037, 1, // ...0x1037 - 0x1039, 2, // ...0x103a - 0x108d, 1, // ...0x108d - 0x135d, 3, // ...0x135f - 0x1714, 2, // ...0x1715 - 0x1734, 1, // ...0x1734 - 0x17d2, 1, // ...0x17d2 - 0x17dd, 1, // ...0x17dd - 0x18a9, 1, // ...0x18a9 - 0x1939, 3, // ...0x193b - 0x1a17, 2, // ...0x1a18 - 0x1a60, 1, // ...0x1a60 - 0x1a75, 8, // ...0x1a7c - 0x1a7f, 1, // ...0x1a7f - 0x1ab0, 14, // ...0x1abd - 0x1abf, 16, // ...0x1ace - 0x1b34, 1, // ...0x1b34 - 0x1b44, 1, // ...0x1b44 - 0x1b6b, 9, // ...0x1b73 - 0x1baa, 2, // ...0x1bab - 0x1be6, 1, // ...0x1be6 - 0x1bf2, 2, // ...0x1bf3 - 0x1c37, 1, // ...0x1c37 - 0x1cd0, 3, // ...0x1cd2 - 0x1cd4, 13, // ...0x1ce0 - 0x1ce2, 7, // ...0x1ce8 - 0x1ced, 1, // ...0x1ced - 0x1cf4, 1, // ...0x1cf4 - 0x1cf8, 2, // ...0x1cf9 - 0x1dc0, 64, // ...0x1dff - 0x20d0, 13, // ...0x20dc - 0x20e1, 1, // ...0x20e1 - 0x20e5, 12, // ...0x20f0 - 0x2cef, 3, // ...0x2cf1 - 0x2d7f, 1, // ...0x2d7f - 0x2de0, 32, // ...0x2dff - 0x302a, 6, // ...0x302f - 0x3099, 2, // ...0x309a - 0xa66f, 1, // ...0xa66f - 0xa674, 10, // ...0xa67d - 0xa69e, 2, // ...0xa69f - 0xa6f0, 2, // ...0xa6f1 - 0xa806, 1, // ...0xa806 - 0xa82c, 1, // ...0xa82c - 0xa8c4, 1, // ...0xa8c4 - 0xa8e0, 18, // ...0xa8f1 - 0xa92b, 3, // ...0xa92d - 0xa953, 1, // ...0xa953 - 0xa9b3, 1, // ...0xa9b3 - 0xa9c0, 1, // ...0xa9c0 - 0xaab0, 1, // ...0xaab0 - 0xaab2, 3, // ...0xaab4 - 0xaab7, 2, // ...0xaab8 - 0xaabe, 2, // ...0xaabf - 0xaac1, 1, // ...0xaac1 - 0xaaf6, 1, // ...0xaaf6 - 0xabed, 1, // ...0xabed - 0xfb1e, 1, // ...0xfb1e - 0xfe20, 16, // ...0xfe2f - 0x101fd, 1, // ...0x101fd - 0x102e0, 1, // ...0x102e0 - 0x10376, 5, // ...0x1037a - 0x10a0d, 1, // ...0x10a0d - 0x10a0f, 1, // ...0x10a0f - 0x10a38, 3, // ...0x10a3a - 0x10a3f, 1, // ...0x10a3f - 0x10ae5, 2, // ...0x10ae6 - 0x10d24, 4, // ...0x10d27 - 0x10eab, 2, // ...0x10eac - 0x10efd, 3, // ...0x10eff - 0x10f46, 11, // ...0x10f50 - 0x10f82, 4, // ...0x10f85 - 0x11046, 1, // ...0x11046 - 0x11070, 1, // ...0x11070 - 0x1107f, 1, // ...0x1107f - 0x110b9, 2, // ...0x110ba - 0x11100, 3, // ...0x11102 - 0x11133, 2, // ...0x11134 - 0x11173, 1, // ...0x11173 - 0x111c0, 1, // ...0x111c0 - 0x111ca, 1, // ...0x111ca - 0x11235, 2, // ...0x11236 - 0x112e9, 2, // ...0x112ea - 0x1133b, 2, // ...0x1133c - 0x1134d, 1, // ...0x1134d - 0x11366, 7, // ...0x1136c - 0x11370, 5, // ...0x11374 - 0x11442, 1, // ...0x11442 - 0x11446, 1, // ...0x11446 - 0x1145e, 1, // ...0x1145e - 0x114c2, 2, // ...0x114c3 - 0x115bf, 2, // ...0x115c0 - 0x1163f, 1, // ...0x1163f - 0x116b6, 2, // ...0x116b7 - 0x1172b, 1, // ...0x1172b - 0x11839, 2, // ...0x1183a - 0x1193d, 2, // ...0x1193e - 0x11943, 1, // ...0x11943 - 0x119e0, 1, // ...0x119e0 - 0x11a34, 1, // ...0x11a34 - 0x11a47, 1, // ...0x11a47 - 0x11a99, 1, // ...0x11a99 - 0x11c3f, 1, // ...0x11c3f - 0x11d42, 1, // ...0x11d42 - 0x11d44, 2, // ...0x11d45 - 0x11d97, 1, // ...0x11d97 - 0x11f41, 2, // ...0x11f42 - 0x16af0, 5, // ...0x16af4 - 0x16b30, 7, // ...0x16b36 - 0x16ff0, 2, // ...0x16ff1 - 0x1bc9e, 1, // ...0x1bc9e - 0x1d165, 5, // ...0x1d169 - 0x1d16d, 6, // ...0x1d172 - 0x1d17b, 8, // ...0x1d182 - 0x1d185, 7, // ...0x1d18b - 0x1d1aa, 4, // ...0x1d1ad - 0x1d242, 3, // ...0x1d244 - 0x1e000, 7, // ...0x1e006 - 0x1e008, 17, // ...0x1e018 - 0x1e01b, 7, // ...0x1e021 - 0x1e023, 2, // ...0x1e024 - 0x1e026, 5, // ...0x1e02a - 0x1e08f, 1, // ...0x1e08f - 0x1e130, 7, // ...0x1e136 - 0x1e2ae, 1, // ...0x1e2ae - 0x1e2ec, 4, // ...0x1e2ef - 0x1e4ec, 4, // ...0x1e4ef - 0x1e8d0, 7, // ...0x1e8d6 - 0x1e944, 7, // ...0x1e94a -}; From be78b58df148ceeb5a6dbbada1ef04beb507d3ef Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 11 Jun 2024 14:36:19 +0700 Subject: [PATCH 08/14] docs(developer): context help in keyboard-editor section Fixes: #2131 --- developer/src/tike/xml/help/contexthelp.xml | 97 +++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index 62bd8677ae..134fb619a4 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -102,6 +102,18 @@ ======================================================================== -->
+ +

Keyboard Editor page consists of: +

    +
  • Details
  • +
  • Layout
  • +
  • Icon
  • +
  • On-Screen
  • +
  • Touch Layout
  • +
  • Build
  • +
+

+
@@ -139,6 +151,24 @@

This corresponds to the following source line:

store(&message) 'Here is a message about a keyboard'
+ + +

The Keyboard Version documents the version of the keyboard.

+

A keyboard version should be updated whenever there are changes to a keyboard. The good principles to follow are: +

    +
  • Increment the major version number for a + keyboard that has significant new functionality.
  • +
  • Increment the minor version number for changes that impact functionality but not + in a significant manner.
  • +
  • Optionally, use a third number for bug fixes.
  • +
+

+

This corresponds to the following source line:

+
store(&keyboardversion) '1.1.2'
+

Note: there is a difference between &keyboardversion, which documents the keyboard version, and &version, + which determines which version of Keyman a keyboard will run with.

+
+

In this field, enter information about the keyboard for your own reference. These comments will only be visible in the source file, and not to users of your keyboard.

@@ -183,6 +213,41 @@

Tests your KeymanWeb keyboard in an Internet Explorer embedded window

+ +

The Features grid controls which additional file components are included in + the keyboard. Each of the features relates to a system store. Here are the file + components: +

    +
  • Embedded JavaScript
  • +
  • Embedded CSS
  • +
  • Web Help
  • +
  • Include Codes
  • +
  • Desktop On-Screen Keyboard (auto-included if Targets is any)
  • +
  • Touch-Optimised Keyboard (auto-included if Targets is any)
  • +
+ Icon will be automatically included when a new keyboard project is created. +

+
+ + +

This will open a selection dialog allowing you to choose a feature to add to + the keyboard project. Adding a feature will add an extra tab to the editor, + and add the corresponding store to the keyboard source

+
+ + +

Depending on what is included in the Feature Grid, you can select a feature from + the grid then click on Edit... This will take you to the corresponding tab and + let you make changes. +

+
+ + +

Removing a feature will not delete the component file, + but will just remove the store from the keyboard source. +

+
+ @@ -197,8 +262,40 @@

The toolbox allows for the changing of the colours, addition of text and shapes. It also allows for the moving of the icon around the canvas and also a preview of the icon is displayed.

+ + +

Click on a colour from the box to apply the colour onto the Keyboard's + icon. You can see the Foreground Color displays the colour you chose. To deselect + the colour, click on the X mark on the corner left of the colour box, or choose + another colour. +

+
+ +

This tab allows you to edit the visual representation of your keyboard + layout. The content on this tab is stored in the .kvks file associated with + your keyboard. The visual representation is used only in desktop and desktop + web; however if no touch layout is defined, this layout will be synthesized + into a touch layout automatically. +

+

An On-Screen keyboard is optional but in most keyboards is recommended. + The On-Screen keyboard may not always match the actual layout identically, + because you may choose to hide some of the details of encoding from the + interface presented to the user. +

+

+ This keyboard layout can also be printed or included in HTML or other documentation. + The editor allows you to export the file to HTML, PNG or BMP formats. +

+
+ + +

If this option is checked, when the Fill from layout button is clicked, + then keys without corresponding rules in the Layout will be filled with the + base layout character. +

+
From 4116f73ecccf61ffa649a12e812d3a4409f29eaf Mon Sep 17 00:00:00 2001 From: MengHeng <90595388+Meng-Heng@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:50:07 +0700 Subject: [PATCH 09/14] Apply suggestions from code review Co-authored-by: Marc Durdin --- developer/src/tike/xml/help/contexthelp.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index 134fb619a4..9c7750f7b7 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -102,7 +102,7 @@ ======================================================================== --> - +

Keyboard Editor page consists of:

  • Details
  • From 074c8f7dac9f865ec4ec4b825206ef7479a09b88 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 12 Jun 2024 14:52:38 +0700 Subject: [PATCH 10/14] fix(developer): show message if no more platforms to add to touch layout editor Fixes: #11646 Fixes: KEYMAN-DEVELOPER-1X2 --- .../src/tike/xml/layoutbuilder/builder.xsl | 4 ++++ .../xml/layoutbuilder/platform-controls.js | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/developer/src/tike/xml/layoutbuilder/builder.xsl b/developer/src/tike/xml/layoutbuilder/builder.xsl index 3b75cd81a3..d43d90c574 100644 --- a/developer/src/tike/xml/layoutbuilder/builder.xsl +++ b/developer/src/tike/xml/layoutbuilder/builder.xsl @@ -271,6 +271,10 @@ +
    +

    All available platforms have already been added.

    +
    +
    diff --git a/developer/src/tike/xml/layoutbuilder/platform-controls.js b/developer/src/tike/xml/layoutbuilder/platform-controls.js index 965513e69c..bccb99717c 100644 --- a/developer/src/tike/xml/layoutbuilder/platform-controls.js +++ b/developer/src/tike/xml/layoutbuilder/platform-controls.js @@ -5,14 +5,20 @@ $(function() { for (var platform in KVKL) { platforms[platform] = 0; } + let nPlatforms = 0; for (platform in platforms) { if (platforms[platform]) { var opt = document.createElement('option'); $(opt).text(platform); $('#selAddPlatform').append(opt); + nPlatforms++; } } - $('#addPlatformDialog').dialog('open') + if(nPlatforms == 0) { + $('#addPlatformDialogNoPlatformsToAdd').dialog('open') + } else { + $('#addPlatformDialog').dialog('open') + } }); $('#btnDelPlatform').click(function () { @@ -69,6 +75,22 @@ $(function() { } }); + // + // Platform dialog -- no platforms to add + // + + $('#addPlatformDialogNoPlatformsToAdd').dialog({ + autoOpen: false, + height: 150, + width: 350, + modal: true, + buttons: { + "OK": function () { + $(this).dialog('close'); + } + } + }); + // // Platform Properties Dialog // From 2d6d007f74fc70c25a3b0ea4a42760b2c5884f45 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Wed, 12 Jun 2024 15:35:17 +0700 Subject: [PATCH 11/14] docs(developer): context help in package-editor and put the context help in their tabs Fixes a portion of #2131: --- developer/src/tike/xml/help/contexthelp.xml | 217 ++++++++++++++++---- 1 file changed, 173 insertions(+), 44 deletions(-) diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index 62bd8677ae..cd9d37f76a 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -331,33 +331,185 @@

    The debugger can be used without the debug information by clicking on Test without debugger.

    - + +

    Enter the name for your new keyboard. Click on the Browse button to change the location of the new keyboard.

    - + +
    + +

    A keyboard package is most likely to have 7 tabs: +

      +
    • Files
    • +
    • Keyboards
    • +
    • Lexical Models
    • +
    • Details
    • +
    • Shortcuts
    • +
    • Source
    • +
    • Build
    • +
    +

    +
    + + + +

    Usually, there is only one keyboard listed in the box, and by clicking on it + will show the keyboard information.

    +
    + +

    When font files are added to the package, this dropdown tells the Keyman + apps which font to use when rendering the On Screen Keyboard touch keyboard. +

    +
    + +

    When font files are added to the package, this dropdown tells the Keyman + apps for iOS and Android which font to use in edit fields. It only applies + within the Keyman app and apps that support this functionality.

    +
    + +

    Each language listed here is a BCP 47 language tag and every keyboard must have a + minimum of one language. When Keyman installs the keyboard package, it will associate the + keyboard with the language(s) you select.

    +
    + +

    This button will open up the BCP 47 Tag window to add the keyboard's language tag, script tag, + region tag, and language name.

    +
    + +

    Select on a language then click on Remove to delete the language tag.

    +
    + +

    Click on Edit... then the BCP 47 Tag window will pop up once again with the language information.

    +
    + + + +

    This is the location of the Lexical Model (Predictive text) for the keyboard in the keyboard + package.

    +
    + +

    Add button brings up the “Select BCP 47 Tag” + window, thus allowing the input of a language for the Lexical Model (Predictive text).

    +
    + +

    By ticking this, it will set the text direction of the Lexical Model (Predictive text) to Right-to-left.

    +
    + +

    A short or long text related to the Lexical Model (Predictive text) is encouraged to be added here but it is optional.

    +
    + + + +

    Choose from the list to specifies which welcome file is suitable to display when they install the keyboard package. + However, this is optional, most keyboard package uses the default option which is (none).

    +
    + +

    Choose from the list if there is a License file to specified to the keyboard package. + However, this is optional, most keyboard package uses the default option which is (none) and we + only accept an open-license keyboard package.

    +
    + +

    Enter the name of the author or authors of the keyboard package.

    +
    + +

    Enter the copyright details of the keyboard package This information will be displayed with the version, author and message information when the package is installed.

    +
    + +

    Enter the contact email address for the keyboard package.

    +
    + +

    Package Name is the name that will be displayed when the package is installed. It should be a descriptive name, in any language, but remember that some applications may use a font that does not include the language you are writing the keyboard name in. Don't include a version number, help information or hotkey in the name.

    +
    + +

    The version number allows the user to check whether they have the latest version of the keyboard. The format should be 'major.minor[.subversion]'. Each number should be an integer, and you should avoid non-integer version strings. See help for more details.

    +
    + +

    The website details for the keyboard package if available.

    +
    + +

    Specifies an independant version for the Lexical Model.

    +
    + +

    By ticking this, the Lexical Model (Predictive text) version will receive a version bump alongside + the keyboard, even when the Model does not receive an update.

    +
    + +

    A keyboard package's description about the language, wonderful community, script, or any related information + that would help users once they install the package.

    +
    + +

    If a keyboard package is intended to replace an existing keyboard, or if there are related packages, + then the identifiers for these packages should be listed here.

    +
    + +

    Add the Package ID and specify Deprecated or Non-deprecated if a keyboard package is intended to + replace an existing keyboard, or if there are related packages.

    +
    + +

    Selects the package and click Edit... to add any changes to the current information.

    +
    + +

    A keyboard package's description about the language, community, script, or any related information + that would help users when they see once installing the package.

    +
    + + + +

    Enter the path of the start menu to be displayed when the keyboard package is installed.

    +
    + +

    This is a list of Start menu entries for the keyboard package.

    +
    + +

    The uninstall shortcut will be added automatically to the shortcut menu list when the package is installed.

    +
    + +

    This option will allow you to create a folder on the Start menu when the keyboard package is installed.

    +
    + +

    To add a new menu item to the list of shortcuts to be displayed in the Start menu folder created when the keyboard package is installed.

    +
    + +

    To delete the selected shortcut menu item from the Start menu folder.

    +
    + +

    The text to be displayed for the selected file as a menu item in the Start up folder that is created when the package is installed.

    +
    + + + + + + +

    Starts the Keyman Developer Web Server for the keyboard package. + This will list the various IP addresses and hostnames that Keyman Developer is listening on.

    +
    + +

    A list of available servers to test the keyboard package.

    +
    + +

    Starts your default browser with the selected address to allow testing of the keyboard package + directly.

    +
    + +

    Specifies the location of the Keyman MSI file. As of Keyman Developer 17, bundled executable package + installers for Keyman for Windows can be created using kmc, but cannot be created within the IDE.

    +
    +

    You can include an image file that will be displayed to the left of the install details when the package is installed. This image should be 140 pixels wide and 250 pixels high.

    The readme file can be displayed after the installation of the keyboard package, but can also be accessed from the keyboard folder at a later time. The file must be loaded under the Files tab, add option before being able to be selected from the drop down list.

    - - -

    This option will allow you to create a folder on the Start menu when the keyboard package is installed.

    -
    - -

    The uninstall shortcut will be added automatically to the shortcut menu list when the package is installed.

    -
    +

    This option allows the user to add new files to the package.

    - -

    To delete the selected shortcut menu item from the Start menu folder.

    -

    This allows for the insertion of a copyright symbol if needed.

    @@ -375,8 +527,6 @@ installed correctly. If you can, try installing your package on several different machines.

    - -

    Displays the output path and filename of the file when the package is compiled.

    @@ -404,9 +554,6 @@

    This option will install the package on the computer. A message will be displayed as to the success of the install.

    - -

    To add a new menu item to the list of shortcuts to be displayed in the Start menu folder created when the keyboard package is installed.

    -

    Opens the source folder of the selected file.

    @@ -432,33 +579,6 @@

    File Type

    Enter the details of the file type being added to the keyboard package.

    - -

    Enter the name of the author or authors of the keyboard package.

    -
    - -

    Enter the copyright details of the keyboard package This information will be displayed with the version, author and message information when the package is installed.

    -
    - -

    Enter the contact email address for the keyboard package.

    -
    - -

    Package Name is the name that will be displayed when the package is installed. It should be a descriptive name, in any language, but remember that some applications may use a font that does not include the language you are writing the keyboard name in. Don't include a version number, help information or hotkey in the name.

    -
    - -

    The version number allows the user to check whether they have the latest version of the keyboard. The format should be 'major.minor[.subversion]'. Each number should be an integer, and you should avoid non-integer version strings. See help for more details.

    -
    - -

    The website details for the keyboard package if available.

    -
    - - -

    The text to be displayed for the selected file as a menu item in the Start up folder that is created when the package is installed.

    -
    - - -

    Start Menu Path

    -

    Enter the path of the start menu to be displayed when the keyboard package is installed.

    -

    This will display all the files that have been added to the keyboard package. It allows for the addition and removal of files, the entering of file details and the editing of any of the files listed if the appropriate editor is available.

    @@ -467,12 +587,14 @@
    +

    The Project Manager allows you to manage all the files related to a keyboard layout in a single location.

    +

    The name of the developer of the keyboard. This is either your full name or @@ -560,6 +682,7 @@

    +

    Wordlist tabs have two views: Design, and Code. Changes to one view are reflected @@ -584,6 +707,7 @@

    +

    Editor windows in Keyman Developer supports standard Windows editing keystrokes. @@ -593,6 +717,7 @@

    +

    Attempt to identify the fonts on your system that will support the @@ -605,6 +730,7 @@

    +

    The message window appears at the bottom of the screen, or floating in a toolbar @@ -613,6 +739,7 @@

    +

    The debugger input window is used for typing input to test the keyboard. @@ -689,6 +816,7 @@

    +

    The About dialog displays copyright and registration information for Keyman Developer, @@ -696,6 +824,7 @@

    +

    This dialog lets you check the virtual key code for any key combination (except Window reserved key combinations such as Alt + Tab). You can then insert the virtual key code into the last active edit window at the current cursor position.



    From 9d2d790fa9afd92d4ec934e957604f195e45be90 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Thu, 13 Jun 2024 12:19:56 +0700 Subject: [PATCH 12/14] docs(developer): context help in new-project section Fixes: #2131 --- developer/src/tike/xml/help/contexthelp.xml | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index 62bd8677ae..a7f66ed12b 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -710,4 +710,27 @@

    To close the dialog, click the Close button or press Shift + Esc.

    - \ No newline at end of file + + +
    + +

    + Creates a new Keyman Keyboard, LDML Keyboard, or a Wordlist Lexical Model, + or by importing from another source. Details are written on individual icons; clicking on + them shows a detailed explanation of each keyboard project. +

    +
    + +

    + Once decided on a project to create, click OK will take you to the next step. +

    +
    + +

    + Cancels creating a project. +

    +
    +
    + + + From 263d0e32f2d43ebe2f551fd3deea4e04bbc8aae3 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 13 Jun 2024 12:17:55 -0500 Subject: [PATCH 13/14] chore(core): update comments and remove a raw numeric literal - per review comments Fixes: #9467 Co-authored-by: rc-swag <58423624+rc-swag@users.noreply.github.com> --- core/src/util_normalize.cpp | 6 ++++-- core/src/util_normalize_table_generator.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index 947dbe0f85..bcdfc1bb3f 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -228,8 +228,10 @@ bool has_nfd_boundary_before(km_core_usv cp) { } /** - * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, - * nul-terminated + * Helper to convert std::u32string to a UTF-32 km_core_usv buffer, + * nul-terminated. + * Parallel to unicode_string_to_usv() + * @returns new buffer, caller owns storage */ km_core_usv *string_to_usv(const std::u32string& src) { return km::core::kmx::u32dup(src.c_str()); diff --git a/core/src/util_normalize_table_generator.cpp b/core/src/util_normalize_table_generator.cpp index b5895a588b..994f16d8b3 100644 --- a/core/src/util_normalize_table_generator.cpp +++ b/core/src/util_normalize_table_generator.cpp @@ -55,7 +55,7 @@ write_nfd_table() { // collect the raw list of chars that do NOT have a boundary before them. std::vector noBoundary; - for (km_core_usv ch = 0; ch < 0x10FFFF; ch++) { + for (km_core_usv ch = 0; ch < km::core::kmx::Uni_MAX_CODEPOINT; ch++) { bool bb = nfd->hasBoundaryBefore(ch); assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator if (bb) continue; //only emit nonboundary From 781a46341dacc4093191a368b57a49af712046f2 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 13 Jun 2024 14:09:31 -0400 Subject: [PATCH 14/14] auto: increment master version to 18.0.56 --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index b5d6ea2caa..c5e1f2e780 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 18.0.55 alpha 2024-06-13 + +* fix(developer): handle missing OSK when importing a Windows keyboard into a touch-only project (#11720) +* fix(developer): verify email addresses in .kps and .keyboard_info (#11735) +* change(web): prep for better asynchronous prediction handling (#10343) + ## 18.0.54 alpha 2024-06-12 * fix(common): remove subpackage entries for older TS version (#11745) diff --git a/VERSION.md b/VERSION.md index 1d6c319a44..b38400af1f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.55 \ No newline at end of file +18.0.56 \ No newline at end of file