diff --git a/core/include/keyman/keyman_core_api_context.h b/core/include/keyman/keyman_core_api_context.h index 26bd2f25bc..02bd751150 100644 --- a/core/include/keyman/keyman_core_api_context.h +++ b/core/include/keyman/keyman_core_api_context.h @@ -116,7 +116,7 @@ km_core_context_items_dispose(km_core_context_item *context_items); */ KMN_API km_core_context * -km_core_state_context(km_core_state *state); +km_core_state_context(km_core_state const *state); /** * Get access to the state object's application context. @@ -127,7 +127,7 @@ km_core_state_context(km_core_state *state); */ KMN_API km_core_context * -km_core_state_app_context(km_core_state *state); +km_core_state_app_context(km_core_state const *state); /* ``` diff --git a/core/src/action.cpp b/core/src/action.cpp index d2e0613b14..f958ca3dbe 100644 --- a/core/src/action.cpp +++ b/core/src/action.cpp @@ -16,7 +16,7 @@ #include "state.hpp" #include "option.hpp" -km_core_actions const * km::core::action_item_list_to_actions_object( +km_core_actions * km::core::action_item_list_to_actions_object( km_core_action_item const *action_items ) { assert(action_items != nullptr); diff --git a/core/src/action.hpp b/core/src/action.hpp index d8ee401e97..0d6844474f 100644 --- a/core/src/action.hpp +++ b/core/src/action.hpp @@ -14,8 +14,14 @@ namespace km { namespace core { - km_core_actions const *action_item_list_to_actions_object( + km_core_actions *action_item_list_to_actions_object( km_core_action_item const *action_items ); + + bool actions_normalize( + km_core_context const *cached_context, + km_core_context const *app_context, + km_core_actions *actions + ); } // namespace core } // namespace km diff --git a/core/src/actions_normalize.cpp b/core/src/actions_normalize.cpp new file mode 100644 index 0000000000..896350fc3a --- /dev/null +++ b/core/src/actions_normalize.cpp @@ -0,0 +1,34 @@ +/* + Copyright: © 2024 SIL International. + Description: Implementation of the action output normalization. + Create Date: 16 Jan 2024 + Authors: Marc Durdin (MCD) + History: 16 Jan 2024 - MCD - Initial implementation from #9999 +*/ +#include +#include +#include +#include + +#include "action.hpp" +#include "state.hpp" +#include "option.hpp" + +bool km::core::actions_normalize( + km_core_context const *cached_context, + km_core_context const *app_context, + km_core_actions *actions +) { + assert(actions != nullptr); + assert(cached_context != nullptr); + assert(app_context != nullptr); + if(actions == nullptr || cached_context == nullptr || app_context == nullptr) { + return false; + } + + // Normalize output to NFC + //TODO + // actions->code_points_to_delete++; + + return true; +} diff --git a/core/src/km_core_action_api.cpp b/core/src/km_core_action_api.cpp index db8a5c8820..4d9a57c371 100644 --- a/core/src/km_core_action_api.cpp +++ b/core/src/km_core_action_api.cpp @@ -33,7 +33,16 @@ km_core_actions const * km_core_state_get_actions( return nullptr; } - return action_item_list_to_actions_object(action_items); + km_core_actions * result = action_item_list_to_actions_object(action_items); + + if(state->processor().supports_normalization()) { + // Normalize to NFC for those keyboard processors that support it + if(!actions_normalize(km_core_state_context(state), km_core_state_app_context(state), result)) { + km_core_actions_dispose(result); + return nullptr; + } + } + return result; } km_core_status km_core_actions_dispose( diff --git a/core/src/km_core_state_api.cpp b/core/src/km_core_state_api.cpp index 7b7e74f634..03e041322d 100644 --- a/core/src/km_core_state_api.cpp +++ b/core/src/km_core_state_api.cpp @@ -65,20 +65,20 @@ void km_core_state_dispose(km_core_state *state) } -km_core_context *km_core_state_context(km_core_state *state) +km_core_context *km_core_state_context(km_core_state const *state) { assert(state); if (!state) return nullptr; - return static_cast(&state->context()); + return static_cast(&(const_cast(state)->context())); } -km_core_context *km_core_state_app_context(km_core_state *state) +km_core_context *km_core_state_app_context(km_core_state const *state) { assert(state); if (!state) return nullptr; - return static_cast(&state->app_context()); + return static_cast(&(const_cast(state)->app_context())); } km_core_status km_core_state_get_intermediate_context( diff --git a/core/src/meson.build b/core/src/meson.build index a80eaaa94a..6f96041dac 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -42,6 +42,7 @@ endif kmx_files = files( + 'actions_normalize.cpp', 'action.cpp', 'option.cpp', 'keyboard.cpp', diff --git a/core/tests/unit/kmnkbd/meson.build b/core/tests/unit/kmnkbd/meson.build index 703374bdf6..16f0c87e8d 100644 --- a/core/tests/unit/kmnkbd/meson.build +++ b/core/tests/unit/kmnkbd/meson.build @@ -26,6 +26,7 @@ tests = [ ['debug-api', 'debug_api.cpp'], ['kmx_xstring', 'test_kmx_xstring.cpp'], ['kmx_context', 'test_kmx_context.cpp'], + ['test_actions_normalize', 'test_actions_normalize.cpp'], ] test_path = join_paths(meson.current_build_dir(), '..', 'kmx') diff --git a/core/tests/unit/kmnkbd/test_actions_normalize.cpp b/core/tests/unit/kmnkbd/test_actions_normalize.cpp new file mode 100644 index 0000000000..95d300ef0c --- /dev/null +++ b/core/tests/unit/kmnkbd/test_actions_normalize.cpp @@ -0,0 +1,112 @@ +/* + Copyright: © 2018 SIL International. + Description: Tests for the context API family of functions. + Create Date: 23 Oct 2023 + Authors: Marc Durdin + History: 23 Oct 2023 - MCD - Initial implementation. +*/ +#include +#include "keyman_core.h" + +#include "path.hpp" +#include "action.hpp" + +#include +#include "../emscripten_filesystem.h" + +km_core_option_item test_env_opts[] = +{ + KM_CORE_OPTIONS_END +}; + +km_core_keyboard * test_kb = nullptr; +km_core_state * test_state = nullptr; +km_core_actions * test_actions = nullptr; +std::string arg_path; + +void teardown() { + if(test_state) { + km_core_state_dispose(test_state); + test_state = nullptr; + } + if(test_kb) { + km_core_keyboard_dispose(test_kb); + test_kb = nullptr; + } + if(test_actions) { + delete [] test_actions->output; + delete test_actions; + test_actions = nullptr; + } +} + +void setup(const km_core_cp *context, int actions_code_points_to_delete, const std::u32string actions_output) { + teardown(); + + km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx"); + try_status(km_core_keyboard_load(path.native().c_str(), &test_kb)); + try_status(km_core_state_create(test_kb, test_env_opts, &test_state)); + + assert(km_core_state_context_set_if_needed(test_state, context) == KM_CORE_CONTEXT_STATUS_UPDATED); + + test_actions = new km_core_actions; + test_actions->code_points_to_delete = actions_code_points_to_delete; + test_actions->output = new km_core_usv[actions_output.length() + 1]; + actions_output.copy(test_actions->output, actions_output.length()); + test_actions->output[actions_output.length()] = 0; +} + +//------------------------------------------------------------------------------------- + +void test_no_normalization() { + setup(u"abc", 0, U"def"); + const int expected_delete = 0; + const std::u32string expected_output = U"def"; + + assert(km::core::actions_normalize(km_core_state_context(test_state), km_core_state_app_context(test_state), test_actions)); + + assert(expected_delete == test_actions->code_points_to_delete); + assert(expected_output == test_actions->output); + + teardown(); +} + +//------------------------------------------------------------------------------------- +// Launcher +//------------------------------------------------------------------------------------- + +constexpr const auto help_str = "\ +test_actions_normalize [--color] \n\ +\n\ + --color Force color output\n\ + BUILD_PATH Path where test_actions_normalize.exe is found; kmx files are\n\ + located relative to this path.\n"; + +int error_args() { + std::cerr << "test_actions_normalize: Invalid arguments." << std::endl; + std::cout << help_str; + return 1; +} + +int main(int argc, char *argv []) { + + if(argc < 2) { + return error_args(); + } + + auto arg_color = std::string(argv[1]) == "--color"; + if(arg_color && argc < 3) { + return error_args(); + } + console_color::enabled = console_color::isaterminal() || arg_color; + +#ifdef __EMSCRIPTEN__ + arg_path = get_wasm_file_path(argv[arg_color ? 2 : 1]); +#else + arg_path = argv[arg_color ? 2 : 1]; +#endif + + // actions + test_no_normalization(); +} +