From: Bowon Ryu Date: Wed, 15 Jan 2025 11:43:45 +0000 (+0900) Subject: Add Dali ICU to Multilanguage support X-Git-Tag: dali_2.4.9~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0bfe1abc5ac44034b6dd526be124f8dc919d8d00;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git Add Dali ICU to Multilanguage support Supports Thai word line breaks using ICU dictionary. Environment variables have been added. The default is 0. export DALI_TEXT_ENABLE_ICU=1 It is currently only used for Thai language, but will be used in many cases in the future. Change-Id: I93ced35d8d8e9c3f69a076920165dd4670c3e3db Signed-off-by: Bowon Ryu --- diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index fda111bc4a..98a0a671a8 100755 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -46,6 +46,7 @@ SET(TC_SOURCES utc-Dali-Text-CharacterSpacingSpan.cpp utc-Dali-Text-FontSpan.cpp utc-Dali-Text-BoldSpan.cpp + utc-Dali-Text-ICU.cpp utc-Dali-Text-ItalicSpan.cpp utc-Dali-Text-BackgroundColorSpan.cpp utc-Dali-Text-Range.cpp @@ -108,6 +109,7 @@ SET(TEST_HARNESS_SOURCES dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp dali-toolkit-test-utils/toolkit-event-thread-callback.cpp dali-toolkit-test-utils/toolkit-environment-variable.cpp + dali-toolkit-test-utils/toolkit-icu.cpp dali-toolkit-test-utils/toolkit-input-method-context.cpp dali-toolkit-test-utils/toolkit-input-method-options.cpp dali-toolkit-test-utils/toolkit-lifecycle-controller.cpp diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.cpp new file mode 100755 index 0000000000..5139cfa954 --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Dali +{ +namespace TextAbstraction +{ +namespace Internal +{ +namespace +{ +Dali::Vector gICUs; +uint32_t gInitializedCount = 0u; +} + +class ICU: public Dali::BaseObject +{ +public: + +ICU() +: mInitialized(false) +{ +} + +~ICU() +{ +} + +void Initialize() +{ + // Try once. + mInitialized = true; + tet_printf("ICU::Initialize:%d\n", mInitialized); +} + +void UpdateLineBreakInfoByLocale(const std::string& text, + Length numberOfCharacters, + const char* locale, + LineBreakInfo* breakInfo) +{ + tet_printf("ICU::UpdateLineBreakInfoByLocale\n"); + if(!mInitialized) + { + Initialize(); + } +} + +// Test method +void ResetInitialize() +{ + mInitialized = false; +} + +// Test method +bool IsInitialized() +{ + return mInitialized; +} + +private: + bool mInitialized : 1; ///< Whether Initialize() has been called, prevents dlopen from being attempted again. +}; + +inline ICU& GetImplementation(TextAbstraction::ICU& icu) +{ + DALI_ASSERT_ALWAYS(icu && "icu handle is empty"); + BaseObject& handle = icu.GetBaseObject(); + return static_cast(handle); +} + +inline const ICU& GetImplementation(const TextAbstraction::ICU& icu) +{ + DALI_ASSERT_ALWAYS(icu && "icu handle is empty"); + const BaseObject& handle = icu.GetBaseObject(); + return static_cast(handle); +} + +} // namespace Internal + +/********************************************************************************/ +/********************************* PUBLIC CLASS *******************************/ +/********************************************************************************/ + +ICU::ICU() +{ +} + +ICU::~ICU() +{ +} + +ICU::ICU(Internal::ICU* impl) +: BaseHandle(impl) +{ +} + +ICU ICU::New() +{ + auto icuImpl = new Internal::ICU(); + Dali::TextAbstraction::Internal::gICUs.PushBack(icuImpl); + return ICU(icuImpl); +} + +void ICU::UpdateLineBreakInfoByLocale(const std::string& text, + Length numberOfCharacters, + const char* locale, + LineBreakInfo* breakInfo) +{ + Internal::GetImplementation(*this).UpdateLineBreakInfoByLocale(text, numberOfCharacters, locale, breakInfo); +} + +} // namespace TextAbstraction +} // namespace Dali; + +namespace Test +{ +namespace ICU +{ +void ResetInitializeFlag() +{ + for(auto& icu : Dali::TextAbstraction::Internal::gICUs) + { + icu->ResetInitialize(); + } + Dali::TextAbstraction::Internal::gInitializedCount = 0u; +} + +uint32_t GetInitializedCount() +{ + Dali::TextAbstraction::Internal::gInitializedCount = 0u; + for(auto& icu : Dali::TextAbstraction::Internal::gICUs) + { + if(icu->IsInitialized()) + { + Dali::TextAbstraction::Internal::gInitializedCount++; + } + } + return Dali::TextAbstraction::Internal::gInitializedCount; +} + +} // namespace ICU +} // namespace Test diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.h b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.h new file mode 100644 index 0000000000..4aa37f01f8 --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.h @@ -0,0 +1,33 @@ +#ifndef DALI_TOOLKIT_TEST_ICU_H +#define DALI_TOOLKIT_TEST_ICU_H + +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +namespace Test +{ +namespace ICU +{ +void ResetInitializeFlag(); +uint32_t GetInitializedCount(); + +} // namespace ICU +} // namespace Test + +#endif // DALI_TOOLKIT_TEST_ICU_H diff --git a/automated-tests/src/dali-toolkit/utc-Dali-Text-ICU.cpp b/automated-tests/src/dali-toolkit/utc-Dali-Text-ICU.cpp new file mode 100644 index 0000000000..c07c0ad40c --- /dev/null +++ b/automated-tests/src/dali-toolkit/utc-Dali-Text-ICU.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Dali; +using namespace Toolkit; + +namespace +{ +static int ASYNC_TEXT_THREAD_TIMEOUT = 5; +static bool gAsyncTextRenderedCalled; +static float gAsyncTextRenderedWidth; +static float gAsyncTextRenderedHeight; + +struct CallbackFunctor +{ + CallbackFunctor(bool* callbackFlag) + : mCallbackFlag(callbackFlag) + { + } + + void operator()() + { + *mCallbackFlag = true; + } + bool* mCallbackFlag; +}; + +static void TestAsyncTextRendered(TextLabel control, float width, float height) +{ + tet_infoline(" TestAsyncTextRendered"); + gAsyncTextRenderedCalled = true; + gAsyncTextRenderedWidth = width; + gAsyncTextRenderedHeight = height; +} + +} // namespace + +void dali_text_icu_startup(void) +{ + EnvironmentVariable::SetTestEnvironmentVariable("DALI_TEXT_ENABLE_ICU", "1"); + + test_return_value = TET_UNDEF; +} + +void dali_text_icu_cleanup(void) +{ + EnvironmentVariable::SetTestEnvironmentVariable("DALI_TEXT_ENABLE_ICU", "0"); + + test_return_value = TET_PASS; +} + +int UtcDaliToolkitTextICU(void) +{ + ToolkitTestApplication application; + tet_infoline(" UtcDaliToolkitTextICU"); + + // Avoid a crash when core load gl resources. + application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE); + + // Create MultilanguageSupport & AsyncTextManager to get LocaleChangedSignal. + Text::MultilanguageSupport::Get(); + Text::AsyncTextManager::Get(); + + Adaptor &adaptor = application.GetAdaptor(); + std::string locale = "en_US"; + adaptor.LocaleChangedSignal().Emit(locale); + + TextLabel label = TextLabel::New(); + DALI_TEST_CHECK(label); + + float expectedWidth = 300.0f; + float expectedHeight = 300.0f; + + // Sync test. + label.SetProperty(TextLabel::Property::TEXT, "ฉันเป็นเอาต์บอกความร้อนของสิ่งที่ถูกหล่อหลอม ฉันไม่สามารถพูดภาษาไทยได้จริงๆ แต่ฉันสามารถช่วยเหลือคุณในการสร้างคำบรรยายสำหรับภาษาไทยได้ แต่ฉันจะต้องพึ่งความช่วยเหลือของ"); + label.SetProperty(Actor::Property::SIZE, Vector2(expectedWidth, expectedHeight)); + label.SetProperty(TextLabel::Property::POINT_SIZE, 12); + label.SetProperty(TextLabel::Property::MULTI_LINE, true); + application.GetScene().Add(label); + + label.SetProperty(TextLabel::Property::LINE_WRAP_MODE, "WORD"); + DALI_TEST_EQUALS(static_cast(Text::LineWrap::WORD), label.GetProperty(TextLabel::Property::LINE_WRAP_MODE), TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(0u, Test::ICU::GetInitializedCount(), TEST_LOCATION); + + // Locale changed + std::string newLocale = "th_TH"; + adaptor.LocaleChangedSignal().Emit(newLocale); + + label.SetProperty(TextLabel::Property::TEXT, "ฉันเป็นเอาต์บอกความร้อนของสิ่งที่ถูกหล่อหลอม ฉันไม่สามารถพูดภาษาไทยได้จริงๆ แต่ฉันสามารถช่วยเหลือคุณในการสร้างคำบรรยายสำหรับภาษาไทยได้ แต่ฉันจะต้องพึ่งความช่วยเหลือของ"); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(1u, Test::ICU::GetInitializedCount(), TEST_LOCATION); + + // Async test. + label.SetProperty(DevelTextLabel::Property::RENDER_MODE, DevelTextLabel::Render::ASYNC_AUTO); + + // Connect to the async text rendered signal. + ConnectionTracker* testTracker = new ConnectionTracker(); + DevelTextLabel::AsyncTextRenderedSignal(label).Connect(&TestAsyncTextRendered); + + bool asyncTextRendered = false; + label.ConnectSignal(testTracker, "asyncTextRendered", CallbackFunctor(&asyncTextRendered)); + + gAsyncTextRenderedCalled = false; + gAsyncTextRenderedWidth = 0.0f; + gAsyncTextRenderedHeight = 0.0f; + + // Request render automatically. + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, ASYNC_TEXT_THREAD_TIMEOUT), true, TEST_LOCATION); + + DALI_TEST_CHECK(gAsyncTextRenderedCalled); + DALI_TEST_CHECK(asyncTextRendered); + + DALI_TEST_EQUALS(expectedWidth, gAsyncTextRenderedWidth, Math::MACHINE_EPSILON_1000, TEST_LOCATION); + DALI_TEST_EQUALS(expectedHeight, gAsyncTextRenderedHeight, Math::MACHINE_EPSILON_1000, TEST_LOCATION); + DALI_TEST_EQUALS(false, label.GetProperty(DevelTextLabel::Property::MANUAL_RENDERED), TEST_LOCATION); + DALI_TEST_EQUALS(2u, Test::ICU::GetInitializedCount(), TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + Test::ICU::ResetInitializeFlag(); + DALI_TEST_EQUALS(0u, Test::ICU::GetInitializedCount(), TEST_LOCATION); + + tet_result(TET_PASS); + END_TEST; +} diff --git a/dali-toolkit/internal/text/async-text/async-text-loader-impl.cpp b/dali-toolkit/internal/text/async-text/async-text-loader-impl.cpp index a83aef6af1..d3c68b53fd 100644 --- a/dali-toolkit/internal/text/async-text/async-text-loader-impl.cpp +++ b/dali-toolkit/internal/text/async-text/async-text-loader-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,11 +61,13 @@ AsyncTextLoader::AsyncTextLoader() : mModule(), mTextModel(), mMetrics(), + mLocale(), mNumberOfCharacters(0u), mFitActualEllipsis(true), mIsTextDirectionRTL(false), mIsTextMirrored(false), mModuleClearNeeded(false), + mLocaleUpdateNeeded(false), mMutex() { mModule = Dali::Toolkit::Text::AsyncTextModule::New(); @@ -77,12 +79,31 @@ AsyncTextLoader::AsyncTextLoader() // Use this to access FontClient i.e. to get down-scaled Emoji metrics. mMetrics = Metrics::New(mModule.GetFontClient()); mLayoutEngine.SetMetrics(mMetrics); + + mLocale = TextAbstraction::GetLocaleFull(); } AsyncTextLoader::~AsyncTextLoader() { } +void AsyncTextLoader::SetLocale(const std::string& locale) +{ + mLocale = locale; + mModule.GetMultilanguageSupport().SetLocale(mLocale); +} + +void AsyncTextLoader::SetLocaleUpdateNeeded(bool update) +{ + Dali::Mutex::ScopedLock lock(mMutex); + mLocaleUpdateNeeded = update; +} + +bool AsyncTextLoader::IsLocaleUpdateNeeded() +{ + return mLocaleUpdateNeeded; +} + void AsyncTextLoader::ClearModule() { mModule.ClearCache(); @@ -318,6 +339,14 @@ void AsyncTextLoader::Update(AsyncTextParameters& parameters) lineBreakInfo.Resize(numberOfCharacters, TextAbstraction::LINE_NO_BREAK); SetLineBreakInfo(mModule.GetSegmentation(), utf32Characters, 0u, numberOfCharacters, lineBreakInfo); + // Check if an ICU-based line break update is required. + if(mModule.GetMultilanguageSupport().IsICULineBreakNeeded()) + { + std::string currentText; + Utf32ToUtf8(mTextModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText); + mModule.GetMultilanguageSupport().UpdateICULineBreak(currentText, numberOfCharacters, lineBreakInfo.Begin()); + } + // Hyphenation if(parameters.lineWrapMode == ((Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || parameters.lineWrapMode == ((Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) diff --git a/dali-toolkit/internal/text/async-text/async-text-loader-impl.h b/dali-toolkit/internal/text/async-text/async-text-loader-impl.h index 11015a8ccc..371fb2cd12 100644 --- a/dali-toolkit/internal/text/async-text/async-text-loader-impl.h +++ b/dali-toolkit/internal/text/async-text/async-text-loader-impl.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_ASYNC_TEXT_LOADER_IMPL_H /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,21 @@ public: */ ~AsyncTextLoader(); + /** + * @copydoc Dali::AsyncTextLoader::SetLocale() + */ + void SetLocale(const std::string& locale); + + /** + * @copydoc Dali::AsyncTextLoader::SetLocaleUpdateNeeded() + */ + void SetLocaleUpdateNeeded(bool update); + + /** + * @copydoc Dali::AsyncTextLoader::IsLocaleUpdateNeeded() + */ + bool IsLocaleUpdateNeeded(); + /** * @copydoc Dali::AsyncTextLoader::ClearModule() */ @@ -195,12 +210,14 @@ private: MetricsPtr mMetrics; Text::Layout::Engine mLayoutEngine; Text::TypesetterPtr mTypesetter; + std::string mLocale; Length mNumberOfCharacters; - bool mFitActualEllipsis : 1; // Used to store actual ellipses during TextFit calculations. Do not use it in other sections. - bool mIsTextDirectionRTL : 1; // The direction of the first line after layout completion. - bool mIsTextMirrored : 1; - bool mModuleClearNeeded : 1; + bool mFitActualEllipsis : 1; // Used to store actual ellipses during TextFit calculations. Do not use it in other sections. + bool mIsTextDirectionRTL : 1; // The direction of the first line after layout completion. + bool mIsTextMirrored : 1; + bool mModuleClearNeeded : 1; + bool mLocaleUpdateNeeded : 1; Mutex mMutex; }; // class AsyncTextLoader diff --git a/dali-toolkit/internal/text/async-text/async-text-loader.cpp b/dali-toolkit/internal/text/async-text/async-text-loader.cpp index 460dc62fb6..3298d8427f 100644 --- a/dali-toolkit/internal/text/async-text/async-text-loader.cpp +++ b/dali-toolkit/internal/text/async-text/async-text-loader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,21 @@ AsyncTextLoader::AsyncTextLoader(Internal::AsyncTextLoader* implementation) { } +void AsyncTextLoader::SetLocale(const std::string& locale) +{ + GetImplementation(*this).SetLocale(locale); +} + +void AsyncTextLoader::SetLocaleUpdateNeeded(bool update) +{ + GetImplementation(*this).SetLocaleUpdateNeeded(update); +} + +bool AsyncTextLoader::IsLocaleUpdateNeeded() +{ + return GetImplementation(*this).IsLocaleUpdateNeeded(); +} + void AsyncTextLoader::ClearModule() { GetImplementation(*this).ClearModule(); diff --git a/dali-toolkit/internal/text/async-text/async-text-loader.h b/dali-toolkit/internal/text/async-text/async-text-loader.h index 79d3a9aea8..22b35615cd 100644 --- a/dali-toolkit/internal/text/async-text/async-text-loader.h +++ b/dali-toolkit/internal/text/async-text/async-text-loader.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_ASYNC_TEXT_LOADER_H /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -296,6 +296,29 @@ public: */ static AsyncTextLoader New(); + /** + * @brief Sets the locale. + * + * @param[in] locale The locale. + */ + void SetLocale(const std::string& locale); + + /** + * @brief Sets a flag indicating that module's locale updating is needed. + * + * When the async text loader is available, update is processed on the main thread. + * + * @param[in] update Whether to update the locale or not. + */ + void SetLocaleUpdateNeeded(bool update); + + /** + * @brief Whether module's locale updating is needed. + * + * @return A flag that indicates whether the locale should be updated or not. + */ + bool IsLocaleUpdateNeeded(); + /** * @brief Clear the cache of the async text module. */ diff --git a/dali-toolkit/internal/text/async-text/async-text-manager-impl.cpp b/dali-toolkit/internal/text/async-text/async-text-manager-impl.cpp index 12a31c4c46..3198da9527 100644 --- a/dali-toolkit/internal/text/async-text/async-text-manager-impl.cpp +++ b/dali-toolkit/internal/text/async-text/async-text-manager-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,12 +116,14 @@ void AsyncTextManager::OnLocaleChanged(std::string locale) for(auto& loader : mAvailableLoaders) { loader.ClearModule(); + loader.SetLocale(mLocale); } // When the Loader is in running state, just set the flag and clear it when it becomes available. for(auto& loader : mRunningLoaders) { loader.SetModuleClearNeeded(true); + loader.SetLocaleUpdateNeeded(true); } } } @@ -139,6 +141,11 @@ Text::AsyncTextLoader AsyncTextManager::GetAvailableLoader() loader.ClearModule(); loader.SetModuleClearNeeded(false); } + if(loader.IsLocaleUpdateNeeded()) + { + loader.SetLocale(mLocale); + loader.SetLocaleUpdateNeeded(false); + } mAvailableLoaders.pop_back(); mRunningLoaders.push_back(loader); diff --git a/dali-toolkit/internal/text/controller/text-controller-impl-model-updater.cpp b/dali-toolkit/internal/text/controller/text-controller-impl-model-updater.cpp index 9e710d9b29..dc674ba1f3 100644 --- a/dali-toolkit/internal/text/controller/text-controller-impl-model-updater.cpp +++ b/dali-toolkit/internal/text/controller/text-controller-impl-model-updater.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -174,6 +174,16 @@ bool ControllerImplModelUpdater::Update(Controller::Impl& impl, OperationsMask o TextAbstraction::Segmentation segmentation = TextAbstraction::Segmentation::Get(); SetLineBreakInfo(segmentation, utf32Characters, startIndex, requestedNumberOfCharacters, lineBreakInfo); + MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get(); + + // Check if an ICU-based line break update is required. + if(multilanguageSupport.IsICULineBreakNeeded()) + { + std::string currentText; + Utf32ToUtf8(impl.mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText); + multilanguageSupport.UpdateICULineBreak(currentText, numberOfCharacters, lineBreakInfo.Begin()); + } + if(impl.mModel->mLineWrapMode == ((Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || impl.mModel->mLineWrapMode == ((Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) { diff --git a/dali-toolkit/internal/text/multi-language-support-impl.cpp b/dali-toolkit/internal/text/multi-language-support-impl.cpp index af89803871..f725d437fd 100644 --- a/dali-toolkit/internal/text/multi-language-support-impl.cpp +++ b/dali-toolkit/internal/text/multi-language-support-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include // EXTERNAL INCLUDES +#include #include #include #include @@ -42,6 +43,11 @@ DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false); const Dali::Toolkit::Text::Character UTF32_A = 0x0041; const Dali::Toolkit::Text::Character UTF32_COLON = 0x3A; + +const char* DALI_TEXT_ENABLE_ICU("DALI_TEXT_ENABLE_ICU"); +const int DEFAULT_ENABLE_ICU = 0; +const char* THAILAND_LOCALE_CODE = "th_TH"; + } // namespace namespace Text @@ -255,9 +261,12 @@ void DefaultFonts::Cache(const TextAbstraction::FontDescription& description, Fo } MultilanguageSupport::MultilanguageSupport(bool connectLocaleChangedSignal) -: mDefaultFontPerScriptCache(), +: mICU(), + mDefaultFontPerScriptCache(), mValidFontsPerScriptCache(), - mLocale() + mLocale(), + mIsICUEnabled(false), + mIsICULineBreakNeededForLocale(false) { // Initializes the default font cache to zero (invalid font). // Reserves space to cache the default fonts and access them with the script as an index. @@ -269,6 +278,16 @@ MultilanguageSupport::MultilanguageSupport(bool connectLocaleChangedSignal) mLocale = TextAbstraction::GetLocaleFull(); + // Check environment variable for DALI_TEXT_ENABLE_ICU. + auto enableICUstring = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_TEXT_ENABLE_ICU); + int enableICU = enableICUstring ? std::atoi(enableICUstring) : DEFAULT_ENABLE_ICU; + if(enableICU == 1) + { + mIsICUEnabled = true; + mIsICULineBreakNeededForLocale = IsICULineBreakNeededForLocale(); + mICU = TextAbstraction::ICU::New(); + } + if(connectLocaleChangedSignal && Dali::Adaptor::IsAvailable()) { Dali::Adaptor::Get().LocaleChangedSignal().Connect(this, &MultilanguageSupport::OnLocaleChanged); @@ -300,7 +319,7 @@ void MultilanguageSupport::OnLocaleChanged(std::string locale) { if(mLocale != locale) { - mLocale = locale; + SetLocale(locale); ClearCache(); } } @@ -314,11 +333,32 @@ void MultilanguageSupport::ClearCache() mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL); } -std::string MultilanguageSupport::GetLocale() +const std::string& MultilanguageSupport::GetLocale() { return mLocale; } +void MultilanguageSupport::SetLocale(const std::string& locale) +{ + mLocale = locale; + mIsICULineBreakNeededForLocale = IsICULineBreakNeededForLocale(); +} + +bool MultilanguageSupport::IsICULineBreakNeeded() +{ + return mIsICUEnabled && mIsICULineBreakNeededForLocale; +} + +void MultilanguageSupport::UpdateICULineBreak(const std::string& text, + TextAbstraction::Length numberOfCharacters, + TextAbstraction::LineBreakInfo* breakInfo) +{ + if(mIsICUEnabled && mICU) + { + mICU.UpdateLineBreakInfoByLocale(text, numberOfCharacters, mLocale.c_str(), breakInfo); + } +} + Text::MultilanguageSupport MultilanguageSupport::Get() { Text::MultilanguageSupport multilanguageSupportHandle; @@ -994,6 +1034,11 @@ void MultilanguageSupport::AddCurrentScriptAndCreatNewScript(const Script currentScriptRun.isRightToLeft = isRightToLeft; } +bool MultilanguageSupport::IsICULineBreakNeededForLocale() +{ + return mLocale == THAILAND_LOCALE_CODE; +} + } // namespace Internal } // namespace Text diff --git a/dali-toolkit/internal/text/multi-language-support-impl.h b/dali-toolkit/internal/text/multi-language-support-impl.h index d33f78538e..4863271682 100644 --- a/dali-toolkit/internal/text/multi-language-support-impl.h +++ b/dali-toolkit/internal/text/multi-language-support-impl.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_MULTI_LANGUAGE_SUPPORT_IMPL_H /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ */ // EXTERNAL INCLUDES +#include #include #include @@ -182,36 +183,56 @@ public: */ void OnLocaleChanged(std::string locale); + /** + * @copydoc Dali::MultilanguageSupport::GetLocale() + */ + const std::string& GetLocale(); + + /** + * @copydoc Dali::MultilanguageSupport::SetLocale() + */ + void SetLocale(const std::string& locale); + /** * @copydoc Dali::MultilanguageSupport::ClearCache() */ void ClearCache(); /** - * @brief Gets the locale. + * @copydoc Dali::MultilanguageSupport::IsICULineBreakNeeded() */ - std::string GetLocale(); + bool IsICULineBreakNeeded(); + + /** + * @copydoc Dali::MultilanguageSupport::UpdateICULineBreak() + */ + void UpdateICULineBreak(const std::string& text, + TextAbstraction::Length numberOfCharacters, + TextAbstraction::LineBreakInfo* breakInfo); private: + TextAbstraction::ICU mICU; ///< Handle to the dali ICU. Vector mDefaultFontPerScriptCache; ///< Caches default fonts for a script. Vector mValidFontsPerScriptCache; ///< Caches valid fonts for a script. std::string mLocale; + bool mIsICUEnabled : 1; + bool mIsICULineBreakNeededForLocale : 1; //Methods /** - * @brief Add the current script to scripts and create new script. - * - * @param[in] requestedScript The script of the new script run. - * @param[in] isRightToLeft The direction of the new script run. - * @param[in] addScriptCharactersToNewScript Whether to add the pending characters to the new script run or to the current script run. - * @param[inout] currentScriptRun The current character script run and it will be updated it to the new script run. - * @param[inout] numberOfAllScriptCharacters The pending characters. - * @param[inout] scripts The list of scripts. - * @param[inout] scriptIndex The current index of scripts. - * - */ + * @brief Add the current script to scripts and create new script. + * + * @param[in] requestedScript The script of the new script run. + * @param[in] isRightToLeft The direction of the new script run. + * @param[in] addScriptCharactersToNewScript Whether to add the pending characters to the new script run or to the current script run. + * @param[inout] currentScriptRun The current character script run and it will be updated it to the new script run. + * @param[inout] numberOfAllScriptCharacters The pending characters. + * @param[inout] scripts The list of scripts. + * @param[inout] scriptIndex The current index of scripts. + * + */ void AddCurrentScriptAndCreatNewScript(const Script requestedScript, const bool isRightToLeft, const bool addScriptCharactersToNewScript, @@ -219,6 +240,14 @@ private: Length& numberOfAllScriptCharacters, Vector& scripts, ScriptRunIndex& scriptIndex); + + /** + * @brief Checks if an ICU-based line break update is required for the current locale. + * @note Use only when there is a change in locale to avoid useless calculations. + * @return If true, icu-based line breaks are required or possible. + */ + bool IsICULineBreakNeededForLocale(); + }; } // namespace Internal diff --git a/dali-toolkit/internal/text/multi-language-support.cpp b/dali-toolkit/internal/text/multi-language-support.cpp index e81d7d88a3..00668d0a56 100644 --- a/dali-toolkit/internal/text/multi-language-support.cpp +++ b/dali-toolkit/internal/text/multi-language-support.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,11 +86,33 @@ void MultilanguageSupport::ValidateFonts(TextAbstraction::FontClient& fonts); } +const std::string& MultilanguageSupport::GetLocale() +{ + return GetImplementation(*this).GetLocale(); +} + +void MultilanguageSupport::SetLocale(const std::string& locale) +{ + GetImplementation(*this).SetLocale(locale); +} + void MultilanguageSupport::ClearCache() { GetImplementation(*this).ClearCache(); } +bool MultilanguageSupport::IsICULineBreakNeeded() +{ + return GetImplementation(*this).IsICULineBreakNeeded(); +} + +void MultilanguageSupport::UpdateICULineBreak(const std::string& text, + TextAbstraction::Length numberOfCharacters, + TextAbstraction::LineBreakInfo* breakInfo) +{ + GetImplementation(*this).UpdateICULineBreak(text, numberOfCharacters, breakInfo); +} + } // namespace Text } // namespace Toolkit diff --git a/dali-toolkit/internal/text/multi-language-support.h b/dali-toolkit/internal/text/multi-language-support.h index 22c3c66a92..223a52ce32 100644 --- a/dali-toolkit/internal/text/multi-language-support.h +++ b/dali-toolkit/internal/text/multi-language-support.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_MULTI_LANGUAGE_SUPPORT_H /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,11 +136,39 @@ public: Length numberOfCharacters, Vector& fonts); + /** + * @brief Gets the locale. + */ + const std::string& GetLocale(); + + /** + * @brief Sets the locale. + * @param[in] locale The locale. + */ + void SetLocale(const std::string& locale); + /** * @brief Clear font caches when locale changed. */ void ClearCache(); + /** + * @brief Checks if an ICU-based line break update is required for the current locale. + * @return If true, icu-based line breaks are required or possible. + */ + bool IsICULineBreakNeeded(); + + /** + * @brief Update line break information by ICU. + * @remark Updates given line break information with ICU dictionary-based word wrap information that unibreak does not support. + * @param[in] text A string of UTF-8 characters. + * @param[in] numberOfCharacters The number of characters. + * @param[out] breakInfo The unibreak line break information buffer. + */ + void UpdateICULineBreak(const std::string& text, + TextAbstraction::Length numberOfCharacters, + TextAbstraction::LineBreakInfo* breakInfo); + public: // Default copy and move operator MultilanguageSupport(const MultilanguageSupport& rhs) = default;