[dali_2.3.22] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / multi-language-support-impl.cpp
index 96210ca..41ba5ac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
@@ -21,7 +21,9 @@
 // EXTERNAL INCLUDES
 #include <dali/devel-api/common/singleton-service.h>
 #include <dali/devel-api/text-abstraction/font-client.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <dali/integration-api/debug.h>
+#include <dali/integration-api/trace.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/emoji-helper.h>
@@ -37,13 +39,150 @@ namespace
 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
 #endif
 
+DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false);
+
 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
+
+// TODO : Customization required for these values.
+constexpr std::size_t MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE = 63;
+constexpr std::size_t MAX_DEFAULT_FONTS_CACHE_SIZE             = 15;
+
+constexpr int VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT = 8;
+constexpr int DEFAULT_FONTS_REMAIN_COUNT             = 2;
 } // namespace
 
 namespace Text
 {
 namespace Internal
 {
+
+namespace
+{
+void CheckFontSupportsCharacter(
+  bool& isValidFont,
+  bool& isCommonScript,
+  const Character& character,
+  ValidateFontsPerScript**& validFontsPerScriptCacheBuffer,
+  const Script& script,
+  FontId& fontId,
+  TextAbstraction::FontClient& fontClient,
+  const bool isValidCachedDefaultFont,
+  const FontId& cachedDefaultFontId,
+  const TextAbstraction::FontDescription& currentFontDescription,
+  const TextAbstraction::PointSize26Dot6& currentFontPointSize,
+  DefaultFonts**& defaultFontPerScriptCacheBuffer)
+{
+  // Need to check if the given font supports the current character.
+  if(!isValidFont) // (1)
+  {
+    // Whether the current character is common for all scripts (i.e. white spaces, ...)
+
+    // Is not desirable to cache fonts for the common script.
+    //
+    // i.e. Consider the text " हिंदी", the 'white space' has assigned the DEVANAGARI script.
+    //      The user may have set a font or the platform's default is used.
+    //
+    //      As the 'white space' is the first character, no font is cached so the font validation
+    //      retrieves a glyph from the given font.
+    //
+    //      Many fonts support 'white spaces' so probably the font set by the user or the platform's default
+    //      supports the 'white space'. However, that font may not support the DEVANAGARI script.
+    isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
+
+    // Check in the valid fonts cache.
+    ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
+
+    if(NULL != validateFontsPerScript)
+    {
+      // This cache stores valid fonts set by the user.
+      isValidFont = validateFontsPerScript->IsValidFont(fontId);
+
+      // It may happen that a validated font for a script doesn't have all the glyphs for that script.
+      // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
+      if(isValidFont)
+      {
+        // Checks if the current character is supported by the font is needed.
+        isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
+      }
+    }
+
+    if(!isValidFont) // (2)
+    {
+      // The selected font is not stored in any cache.
+
+      // Checks if the current character is supported by the selected font.
+      isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
+
+      // If there is a valid font, cache it.
+      if(isValidFont && !isCommonScript)
+      {
+        if(NULL == validateFontsPerScript)
+        {
+          validateFontsPerScript = new ValidateFontsPerScript();
+
+          *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
+        }
+
+        validateFontsPerScript->Cache(fontId);
+      }
+
+      if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
+      {
+        // The selected font by the user or the platform's default font has failed to validate the character.
+
+        // Checks if the previously discarted cached default font supports the character.
+        bool isValidCachedFont = false;
+        if(isValidCachedDefaultFont)
+        {
+          isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
+        }
+
+        if(isValidCachedFont)
+        {
+          // Use the cached default font for the script if there is one.
+          fontId      = cachedDefaultFontId;
+          isValidFont = true;
+        }
+        else
+        {
+          // There is no valid cached default font for the script.
+
+          DefaultFonts* defaultFontsPerScript = NULL;
+
+          // Find a fallback-font.
+          fontId = fontClient.FindFallbackFont(character,
+                                               currentFontDescription,
+                                               currentFontPointSize,
+                                               false);
+
+          if(0u == fontId)
+          {
+            fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
+          }
+
+          if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
+          {
+            // Cache the font if it is not an unknown script
+            if(NULL == defaultFontsPerScript)
+            {
+              defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
+
+              if(NULL == defaultFontsPerScript)
+              {
+                defaultFontsPerScript                       = new DefaultFonts();
+                *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
+              }
+            }
+            defaultFontsPerScript->Cache(currentFontDescription, fontId);
+            isValidFont = true;
+          }
+        }
+      } // !isValidFont (3)
+    }   // !isValidFont (2)
+  }     // !isValidFont (1)
+}
+} // unnamed namespace
+
 bool ValidateFontsPerScript::IsValidFont(FontId fontId) const
 {
   for(Vector<FontId>::ConstIterator it    = mValidFonts.Begin(),
@@ -59,6 +198,20 @@ bool ValidateFontsPerScript::IsValidFont(FontId fontId) const
 
   return false;
 }
+void ValidateFontsPerScript::Cache(FontId fontId)
+{
+  mValidFonts.PushBack(fontId);
+  if(MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE < mValidFonts.Count())
+  {
+    // Clear cache but remaind some last items.
+    const auto offset = mValidFonts.Count() - VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT;
+    for(int i = 0; i < VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT; ++i)
+    {
+      mValidFonts[i] = std::move(mValidFonts[offset + i]);
+    }
+    mValidFonts.Resize(VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT);
+  }
+}
 
 FontId DefaultFonts::FindFont(TextAbstraction::FontClient&            fontClient,
                               const TextAbstraction::FontDescription& description,
@@ -90,11 +243,22 @@ void DefaultFonts::Cache(const TextAbstraction::FontDescription& description, Fo
   item.description = description;
   item.fontId      = fontId;
   mFonts.push_back(item);
+  if(MAX_DEFAULT_FONTS_CACHE_SIZE < mFonts.size())
+  {
+    // Clear cache but remaind some last items.
+    const auto offset = mFonts.size() - DEFAULT_FONTS_REMAIN_COUNT;
+    for(int i = 0; i < DEFAULT_FONTS_REMAIN_COUNT; ++i)
+    {
+      mFonts[i] = std::move(mFonts[offset + i]);
+    }
+    mFonts.resize(DEFAULT_FONTS_REMAIN_COUNT);
+  }
 }
 
 MultilanguageSupport::MultilanguageSupport()
 : mDefaultFontPerScriptCache(),
-  mValidFontsPerScriptCache()
+  mValidFontsPerScriptCache(),
+  mLocale(std::string())
 {
   // 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.
@@ -103,6 +267,11 @@ MultilanguageSupport::MultilanguageSupport()
   // Initializes the valid fonts cache to NULL (no valid fonts).
   // Reserves space to cache the valid fonts and access them with the script as an index.
   mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
+
+  if(Dali::Adaptor::IsAvailable())
+  {
+    Dali::Adaptor::Get().LocaleChangedSignal().Connect(this, &MultilanguageSupport::OnLocaleChanged);
+  }
 }
 
 MultilanguageSupport::~MultilanguageSupport()
@@ -126,6 +295,29 @@ MultilanguageSupport::~MultilanguageSupport()
   }
 }
 
+void MultilanguageSupport::OnLocaleChanged(std::string locale)
+{
+  if(mLocale != locale)
+  {
+    mLocale = locale;
+    ClearCache();
+  }
+}
+
+void MultilanguageSupport::ClearCache()
+{
+  mDefaultFontPerScriptCache.Clear();
+  mValidFontsPerScriptCache.Clear();
+
+  mDefaultFontPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
+  mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
+}
+
+std::string MultilanguageSupport::GetLocale()
+{
+  return mLocale;
+}
+
 Text::MultilanguageSupport MultilanguageSupport::Get()
 {
   Text::MultilanguageSupport multilanguageSupportHandle;
@@ -410,6 +602,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
                                          const Vector<FontDescriptionRun>&       fontDescriptions,
                                          const TextAbstraction::FontDescription& defaultFontDescription,
                                          TextAbstraction::PointSize26Dot6        defaultFontPointSize,
+                                         float                                   fontSizeScale,
                                          CharacterIndex                          startIndex,
                                          Length                                  numberOfCharacters,
                                          Vector<FontRun>&                        fonts)
@@ -423,6 +616,8 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
     return;
   }
 
+  DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FONTS_VALIDATE");
+
   // Find the first index where to insert the font run.
   FontRunIndex fontIndex = 0u;
   if(0u != startIndex)
@@ -468,7 +663,8 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
   Vector<ScriptRun>::ConstIterator scriptRunEndIt          = scripts.End();
   bool                             isNewParagraphCharacter = false;
 
-  FontId                  previousEmojiFontId = 0u;
+  FontId                  currentFontId       = 0u;
+  FontId                  previousFontId      = 0u;
   TextAbstraction::Script previousScript      = TextAbstraction::UNKNOWN;
 
   CharacterIndex lastCharacter = startIndex + numberOfCharacters - 1u;
@@ -486,6 +682,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
     MergeFontDescriptions(fontDescriptions,
                           defaultFontDescription,
                           defaultFontPointSize,
+                          fontSizeScale,
                           index,
                           currentFontDescription,
                           currentFontPointSize,
@@ -493,6 +690,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
 
     // Get the font for the current character.
     FontId fontId = fontClient.GetFontId(currentFontDescription, currentFontPointSize);
+    currentFontId = fontId;
 
     // Get the script for the current character.
     Script script = GetScript(index,
@@ -541,130 +739,40 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
     }
 
-    bool isCommonScript = false;
-    bool isEmojiScript  = TextAbstraction::IsEmojiScript(script) || TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script);
+    bool isEmojiScript = IsEmojiColorScript(script) || IsEmojiTextScript(script);
+    bool isZWJ         = TextAbstraction::IsZeroWidthJoiner(character);
 
-    if(isEmojiScript && (previousScript == script))
+    if((previousScript == script) &&
+       (isEmojiScript || isZWJ))
     {
-      // Emoji sequence should use the previous emoji font.
-      if(0u != previousEmojiFontId)
+      // This sequence should use the previous font.
+      if(0u != previousFontId)
       {
-        fontId      = previousEmojiFontId;
+        fontId      = previousFontId;
         isValidFont = true;
       }
     }
 
+    if(TextAbstraction::IsSpace(character) &&
+       TextAbstraction::HasLigatureMustBreak(script) &&
+       isValidCachedDefaultFont &&
+       (isDefaultFont || (currentFontId == previousFontId)))
+    {
+      fontId      = cachedDefaultFontId;
+      isValidFont = true;
+    }
+
+    // This is valid after CheckFontSupportsCharacter();
+    bool isCommonScript = false;
+
     // If the given font is not valid, it means either:
     // - there is no cached font for the current script yet or,
     // - the user has set a different font than the default one for the current script or,
     // - the platform default font is different than the default font for the current script.
 
     // Need to check if the given font supports the current character.
-    if(!isValidFont) // (1)
-    {
-      // Whether the current character is common for all scripts (i.e. white spaces, ...)
-
-      // Is not desirable to cache fonts for the common script.
-      //
-      // i.e. Consider the text " हिंदी", the 'white space' has assigned the DEVANAGARI script.
-      //      The user may have set a font or the platform's default is used.
-      //
-      //      As the 'white space' is the first character, no font is cached so the font validation
-      //      retrieves a glyph from the given font.
-      //
-      //      Many fonts support 'white spaces' so probably the font set by the user or the platform's default
-      //      supports the 'white space'. However, that font may not support the DEVANAGARI script.
-      isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
-
-      // Check in the valid fonts cache.
-      ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
-
-      if(NULL != validateFontsPerScript)
-      {
-        // This cache stores valid fonts set by the user.
-        isValidFont = validateFontsPerScript->IsValidFont(fontId);
-
-        // It may happen that a validated font for a script doesn't have all the glyphs for that script.
-        // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
-        if(isValidFont)
-        {
-          // Checks if the current character is supported by the font is needed.
-          isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
-        }
-      }
-
-      if(!isValidFont) // (2)
-      {
-        // The selected font is not stored in any cache.
-
-        // Checks if the current character is supported by the selected font.
-        isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
-
-        // If there is a valid font, cache it.
-        if(isValidFont && !isCommonScript)
-        {
-          if(NULL == validateFontsPerScript)
-          {
-            validateFontsPerScript = new ValidateFontsPerScript();
-
-            *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
-          }
-
-          validateFontsPerScript->mValidFonts.PushBack(fontId);
-        }
-
-        if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
-        {
-          // The selected font by the user or the platform's default font has failed to validate the character.
-
-          // Checks if the previously discarted cached default font supports the character.
-          bool isValidCachedFont = false;
-          if(isValidCachedDefaultFont)
-          {
-            isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
-          }
-
-          if(isValidCachedFont)
-          {
-            // Use the cached default font for the script if there is one.
-            fontId = cachedDefaultFontId;
-          }
-          else
-          {
-            // There is no valid cached default font for the script.
-
-            DefaultFonts* defaultFontsPerScript = NULL;
-
-            // Find a fallback-font.
-            fontId = fontClient.FindFallbackFont(character,
-                                                 currentFontDescription,
-                                                 currentFontPointSize,
-                                                 false);
-
-            if(0u == fontId)
-            {
-              fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
-            }
-
-            if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
-            {
-              // Cache the font if it is not an unknown script
-              if(NULL == defaultFontsPerScript)
-              {
-                defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
-
-                if(NULL == defaultFontsPerScript)
-                {
-                  defaultFontsPerScript                       = new DefaultFonts();
-                  *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
-                }
-              }
-              defaultFontsPerScript->Cache(currentFontDescription, fontId);
-            }
-          }
-        } // !isValidFont (3)
-      }   // !isValidFont (2)
-    }     // !isValidFont (1)
+    CheckFontSupportsCharacter(isValidFont, isCommonScript, character, validFontsPerScriptCacheBuffer, script, fontId, fontClient,
+                               isValidCachedDefaultFont, cachedDefaultFontId, currentFontDescription, currentFontPointSize, defaultFontPerScriptCacheBuffer);
 
     if(isEmojiScript && (previousScript != script))
     {
@@ -703,19 +811,6 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       }
     }
 
-    // Store the font id when the first character is an emoji.
-    if(isEmojiScript)
-    {
-      if(0u != fontId && previousScript != script)
-      {
-        previousEmojiFontId = fontId;
-      }
-    }
-    else
-    {
-      previousEmojiFontId = 0u;
-    }
-
 #ifdef DEBUG_ENABLED
     if(gLogFilter->IsEnabledFor(Debug::Verbose))
     {
@@ -729,6 +824,15 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
                     description.path.c_str());
     }
 #endif
+    if(!isValidFont && !isCommonScript)
+    {
+      Dali::TextAbstraction::FontDescription descriptionForLog;
+      fontClient.GetDescription(fontId, descriptionForLog);
+      DALI_LOG_RELEASE_INFO("Validated font set fail : Character : %x, Script : %s, Font : %s \n",
+                            character,
+                            Dali::TextAbstraction::ScriptName[script],
+                            descriptionForLog.path.c_str());
+    }
 
     // Whether bols style is required.
     isBoldRequired = (currentFontDescription.weight >= TextAbstraction::FontWeight::BOLD);
@@ -765,6 +869,8 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
     // Whether the current character is a new paragraph character.
     isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
     previousScript          = script;
+    currentFontId           = fontId;
+    previousFontId          = currentFontId;
   } // end traverse characters.
 
   if(0u != currentFontRun.characterRun.numberOfCharacters)