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 @@
======================================================================== -->
+
@@ -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 @@
======================================================================== -->
+
+
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
+
+
+
+
+
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