Add Dali ICU to Multilanguage support 32/318332/8
authorBowon Ryu <bowon.ryu@samsung.com>
Wed, 15 Jan 2025 11:43:45 +0000 (20:43 +0900)
committerBowon Ryu <bowon.ryu@samsung.com>
Thu, 6 Mar 2025 06:17:24 +0000 (15:17 +0900)
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 <bowon.ryu@samsung.com>
14 files changed:
automated-tests/src/dali-toolkit/CMakeLists.txt
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.cpp [new file with mode: 0755]
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-icu.h [new file with mode: 0644]
automated-tests/src/dali-toolkit/utc-Dali-Text-ICU.cpp [new file with mode: 0644]
dali-toolkit/internal/text/async-text/async-text-loader-impl.cpp
dali-toolkit/internal/text/async-text/async-text-loader-impl.h
dali-toolkit/internal/text/async-text/async-text-loader.cpp
dali-toolkit/internal/text/async-text/async-text-loader.h
dali-toolkit/internal/text/async-text/async-text-manager-impl.cpp
dali-toolkit/internal/text/controller/text-controller-impl-model-updater.cpp
dali-toolkit/internal/text/multi-language-support-impl.cpp
dali-toolkit/internal/text/multi-language-support-impl.h
dali-toolkit/internal/text/multi-language-support.cpp
dali-toolkit/internal/text/multi-language-support.h

index fda111bc4a52f7958ac7fa302e472fd8215ae759..98a0a671a888adc548a2e137eb4ee753e53449f7 100755 (executable)
@@ -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 (executable)
index 0000000..5139cfa
--- /dev/null
@@ -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 <dali/devel-api/text-abstraction/icu.h>
+#include <dali/devel-api/text-abstraction/text-abstraction-definitions.h>
+#include <dali/public-api/dali-adaptor-common.h>
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/object/base-object.h>
+#include <toolkit-application.h>
+#include <toolkit-icu.h>
+#include <dali-toolkit-test-suite-utils.h>
+
+namespace Dali
+{
+namespace TextAbstraction
+{
+namespace Internal
+{
+namespace
+{
+Dali::Vector<Dali::TextAbstraction::Internal::ICU*> 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<Internal::ICU&>(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<const Internal::ICU&>(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 (file)
index 0000000..4aa37f0
--- /dev/null
@@ -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 <cstdint>
+
+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 (file)
index 0000000..c07c0ad
--- /dev/null
@@ -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 <toolkit-environment-variable.h>
+#include <toolkit-event-thread-callback.h>
+#include <toolkit-icu.h>
+
+#include <dali-toolkit-test-suite-utils.h>
+#include <dali-toolkit/dali-toolkit.h>
+#include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
+#include <dali-toolkit/internal/text/async-text/async-text-manager.h>
+#include <dali-toolkit/internal/text/multi-language-support.h>
+#include <locale.h>
+#include <iostream>
+
+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<int>(Text::LineWrap::WORD), label.GetProperty<int>(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<bool>(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;
+}
index a83aef6af1bdcc7fd98a9d9841018d155bf8c045..d3c68b53fdf7dbfb7cc2c3d487a2aa785afb2191 100644 (file)
@@ -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))
index 11015a8ccc441606e6352efacc139ea2f3813900..371fb2cd125d104b1c3784ce38f3ef1baf2753de 100644 (file)
@@ -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
index 460dc62fb63a58333aaf137bf3ae1cb0706ef0f7..3298d8427ffd1a2ecff935cbca710fbc83e29f77 100644 (file)
@@ -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();
index 79d3a9aea82b6c1cd8f47b0756ecc676074df2c1..22b35615cd673c7c0b5ce678d7c5a86e9ed2f627 100644 (file)
@@ -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.
    */
index 12a31c4c461f4a9487e9ba678d89bb4be0db8225..3198da952794a33526457940951fb66beb3d810d 100644 (file)
@@ -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);
index 9e710d9b293288dccb58d7e4aade0904d2e6ba05..dc674ba1f3f6e3292c8c71a4ffd26b10e62fa66e 100644 (file)
@@ -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))
     {
index af898038719062d6ee7b1e0f34ca55cba2c1c9ea..f725d437fddec0fdaabb7014b0d139bafe22b59e 100644 (file)
@@ -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 <dali-toolkit/internal/text/multi-language-support-impl.h>
 
 // EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/environment-variable.h>
 #include <dali/devel-api/common/singleton-service.h>
 #include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <dali/integration-api/debug.h>
@@ -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
index d33f78538e0d797b72e970276845573ea1979480..486327168233a9499b7c8e9b99cbf5c79e8b312f 100644 (file)
@@ -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 <dali/devel-api/text-abstraction/icu.h>
 #include <dali/public-api/object/base-object.h>
 #include <dali/public-api/signals/connection-tracker.h>
 
@@ -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<DefaultFonts*>           mDefaultFontPerScriptCache; ///< Caches default fonts for a script.
   Vector<ValidateFontsPerScript*> 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<ScriptRun>& 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
index e81d7d88a30d3bff6175d77ced2f1a5086f5b60c..00668d0a562c4f9dfebbee0d8d097075a20d3eb5 100644 (file)
@@ -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
index 22c3c66a92df1a3f186d208b328e10293a7dc70a..223a52ce3226851ee5b90329109b1f7f30def5b7 100644 (file)
@@ -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<FontRun>&                        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;