Fix for multi-language support.
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / multi-language-support-impl.cpp
index 4143b03..c596045 100644 (file)
 // CLASS HEADER
 #include <dali-toolkit/internal/text/multi-language-support-impl.h>
 
-// INTERNAL INCLUDES
-#include <dali/public-api/adaptor-framework/singleton-service.h>
-#include <dali-toolkit/public-api/text/logical-model.h>
-#include <dali-toolkit/public-api/text/font-run.h>
-#include <dali-toolkit/public-api/text/script.h>
-#include <dali-toolkit/public-api/text/script-run.h>
+// EXTERNAL INCLUDES
+#include <memory.h>
 #include <dali/integration-api/debug.h>
+#include <dali/public-api/adaptor-framework/singleton-service.h>
+#include <dali/public-api/text-abstraction/font-client.h>
+#include <dali/public-api/text-abstraction/script.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/logical-model-impl.h>
+#include <dali-toolkit/internal/text/font-run.h>
+#include <dali-toolkit/internal/text/script-run.h>
+#include <dali-toolkit/internal/text/text-io.h>
 
 namespace Dali
 {
@@ -32,6 +37,15 @@ namespace Dali
 namespace Toolkit
 {
 
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_MULTI_LANGUAGE_SUPPORT");
+#endif
+
+const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
+}
+
 namespace Text
 {
 
@@ -65,7 +79,7 @@ FontId GetFontId( Length index,
       fontId = fontRun.fontId;
     }
 
-    if( index == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters )
+    if( index + 1u == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters )
     {
       // All the characters of the current run have been traversed. Get the next one for the next iteration.
       ++fontRunIt;
@@ -102,7 +116,7 @@ Script GetScript( Length index,
       script = scriptRun.script;
     }
 
-    if( index == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters )
+    if( index + 1u == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters )
     {
       // All the characters of the current run have been traversed. Get the next one for the next iteration.
       ++scriptRunIt;
@@ -112,6 +126,24 @@ Script GetScript( Length index,
   return script;
 }
 
+/**
+ * @brief Whether the character is valid for all scripts. i.e. the white space.
+ *
+ * @param[in] character The character.
+ *
+ * @return @e true if the character is valid for all scripts.
+ */
+bool IsValidForAllScripts( Character character )
+{
+  return ( TextAbstraction::IsWhiteSpace( character )         ||
+           TextAbstraction::IsZeroWidthNonJoiner( character ) ||
+           TextAbstraction::IsZeroWidthJoiner( character )    ||
+           TextAbstraction::IsZeroWidthSpace( character )     ||
+           TextAbstraction::IsLeftToRightMark( character )    ||
+           TextAbstraction::IsRightToLeftMark( character )    ||
+           TextAbstraction::IsThinSpace( character ) );
+}
+
 bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
 {
   for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
@@ -179,10 +211,11 @@ Text::MultilanguageSupport MultilanguageSupport::Get()
   return multilanguageSupportHandle;
 }
 
-void MultilanguageSupport::SetScripts( LogicalModel& model )
+void MultilanguageSupport::SetScripts( const Vector<Character>& text,
+                                       const Vector<LineBreakInfo>& lineBreakInfo,
+                                       Vector<ScriptRun>& scripts )
 {
-  // 1) Retrieve the text from the model.
-  const Length numberOfCharacters = model.GetNumberOfCharacters();
+  const Length numberOfCharacters = text.Count();
 
   if( 0u == numberOfCharacters )
   {
@@ -190,76 +223,158 @@ void MultilanguageSupport::SetScripts( LogicalModel& model )
     return;
   }
 
-  Vector<Character> text;
-  text.Resize( numberOfCharacters );
-
-  model.GetText( 0u,
-                 text.Begin(),
-                 numberOfCharacters );
-
-  // 2) Traverse all characters and set the scripts.
-
   // Stores the current script run.
   ScriptRun currentScriptRun;
   currentScriptRun.characterRun.characterIndex = 0u;
   currentScriptRun.characterRun.numberOfCharacters = 0u;
   currentScriptRun.script = TextAbstraction::UNKNOWN;
 
-  // Temporary stores the script runs.
-  std::vector<ScriptRun> scriptRuns;
-  scriptRuns.reserve( numberOfCharacters << 2u ); // To reduce the number of reallocations.
+  // Reserve some space to reduce the number of reallocations.
+  scripts.Reserve( numberOfCharacters << 2u );
 
-  for( Vector<Character>::ConstIterator it = text.Begin(),
-         endIt = text.End();
-       it != endIt;
-       ++it )
+  // Whether the first valid script need to be set.
+  bool firstValidScript = true;
+
+  // Whether the first valid script is a right to left script.
+  bool isParagraphRTL = false;
+
+  // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
+  Length numberOfAllScriptCharacters = 0u;
+
+  // Pointers to the text and break info buffers.
+  const Character* textBuffer = text.Begin();
+  const LineBreakInfo* breakInfoBuffer = lineBreakInfo.Begin();
+
+  // Traverse all characters and set the scripts.
+  for( Length index = 0u; index < numberOfCharacters; ++index )
   {
-    const Character character = *it;
+    Character character = *( textBuffer + index );
+    LineBreakInfo breakInfo = *( breakInfoBuffer + index );
+
+    // Some characters (like white spaces) are valid for many scripts. The rules to set a script
+    // for them are:
+    // - If they are at the begining of a paragraph they get the script of the first character with
+    //   a defined script. If they are at the end, they get the script of the last one.
+    // - If they are between two scripts with the same direction, they get the script of the previous
+    //   character with a defined script. If the two scripts have different directions, they get the
+    //   script of the first character of the paragraph with a defined script.
+
+    // Skip those characters valid for many scripts like white spaces or '\n'.
+    bool endOfText = index == numberOfCharacters;
+    while( !endOfText &&
+           IsValidForAllScripts( character ) )
+    {
+      // Count all these characters to be added into a script.
+      ++numberOfAllScriptCharacters;
 
-    Script script = GetCharacterScript( character );
+      if( TextAbstraction::LINE_MUST_BREAK == breakInfo )
+      {
+        // The next character is a new paragraph.
+        // Know when there is a new paragraph is needed because if there is a white space
+        // between two scripts with different directions, it is added to the script with
+        // the same direction than the first script of the paragraph.
+        firstValidScript = true;
+        isParagraphRTL = false;
+      }
 
-    if( TextAbstraction::UNKNOWN == script )
+      // Get the next character.
+      ++index;
+      endOfText = index == numberOfCharacters;
+      if( !endOfText )
+      {
+        character = *( textBuffer + index );
+        breakInfo = *( breakInfoBuffer + index );
+      }
+    }
+
+    if( endOfText )
     {
-      script = TextAbstraction::LATIN;
-      DALI_ASSERT_DEBUG( !"MultilanguageSupport::SetScripts. Unkown script!" );
+      // Last characters of the text are 'white spaces'.
+      // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
+      break;
+    }
+
+    // Get the script of the character.
+    Script script = TextAbstraction::GetCharacterScript( character );
+
+    // Check if it is the first character of a paragraph.
+    if( firstValidScript &&
+        ( TextAbstraction::UNKNOWN != script ) )
+    {
+      // Sets the direction of the first valid script.
+      isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
+      firstValidScript = false;
     }
 
     if( script != currentScriptRun.script )
     {
       // Current run needs to be stored and a new one initialized.
 
+      if( isParagraphRTL != TextAbstraction::IsRightToLeftScript( script ) )
+      {
+        // Current script has different direction than the first script of the paragraph.
+        // All the previously skipped characters need to be added to the previous script before it's stored.
+        currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
+        numberOfAllScriptCharacters = 0u;
+      }
+
       if( 0u != currentScriptRun.characterRun.numberOfCharacters )
       {
         // Store the script run.
-        scriptRuns.push_back( currentScriptRun );
+        scripts.PushBack( currentScriptRun );
       }
 
       // Initialize the new one.
       currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-      currentScriptRun.characterRun.numberOfCharacters = 0u;
+      currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters; // Adds the white spaces which are at the begining of the script.
       currentScriptRun.script = script;
+      numberOfAllScriptCharacters = 0u;
+    }
+    else
+    {
+      // Adds white spaces between characters.
+      currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
+      numberOfAllScriptCharacters = 0u;
+    }
+
+    if( TextAbstraction::LINE_MUST_BREAK == breakInfo )
+    {
+      // The next character is a new paragraph.
+      firstValidScript = true;
+      isParagraphRTL = false;
     }
 
     // Add one more character to the run.
     ++currentScriptRun.characterRun.numberOfCharacters;
   }
 
+  // Add remaining characters into the last script.
+  currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
   if( 0u != currentScriptRun.characterRun.numberOfCharacters )
   {
+    if( TextAbstraction::UNKNOWN == currentScriptRun.script )
+    {
+      // There are only white spaces in the last script. Set the latin script.
+      currentScriptRun.script = TextAbstraction::LATIN;
+    }
+
     // Store the last run.
-    scriptRuns.push_back( currentScriptRun );
+    scripts.PushBack( currentScriptRun );
   }
+}
 
-  // 3) Set the script runs into the model.
-
-  model.SetScripts( &scriptRuns[0u],
-                    scriptRuns.size() );
+void MultilanguageSupport::ReplaceScripts( LogicalModel& model,
+                                           CharacterIndex characterIndex,
+                                           Length numberOfCharactersToRemove,
+                                           Length numberOfCharactersToInsert )
+{
 }
 
-void MultilanguageSupport::ValidateFonts( LogicalModel& model )
+void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
+                                          const Vector<ScriptRun>& scripts,
+                                          Vector<FontRun>& fonts )
 {
-  // 1) Retrieve the text from the model.
-  const Length numberOfCharacters = model.GetNumberOfCharacters();
+  const Length numberOfCharacters = text.Count();
 
   if( 0u == numberOfCharacters )
   {
@@ -267,47 +382,19 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
     return;
   }
 
-  Vector<Character> text;
-  text.Resize( numberOfCharacters );
-
-  Character* textBuffer = text.Begin();
-  model.GetText( 0u,
-                 textBuffer,
-                 numberOfCharacters );
-
-  // 2) Retrieve any font previously set.
+  // Copy the fonts set by application developers.
+  const Length numberOfFontRuns = fonts.Count();
+  const Vector<FontRun> definedFonts = fonts;
+  fonts.Clear();
 
-  const Length numberOfFontRuns = model.GetNumberOfFontRuns( 0u, numberOfCharacters );
-
-  Vector<FontRun> fontRuns;
-  fontRuns.Reserve( numberOfFontRuns );
-
-  FontRun* fontRunsBuffer = fontRuns.Begin();
-  model.GetFontRuns( fontRunsBuffer,
-                     0u,
-                     numberOfCharacters );
-
-  // 3) Retrieve the scripts from the model.
-
-  const Length numberOfScriptRuns = model.GetNumberOfScriptRuns( 0u, numberOfCharacters );
-
-  Vector<ScriptRun> scriptRuns;
-  scriptRuns.Reserve( numberOfScriptRuns );
-
-  ScriptRun* scriptRunsBuffer = scriptRuns.Begin();
-  model.GetScriptRuns( scriptRunsBuffer,
-                       0u,
-                       numberOfCharacters );
-
-  // 4) Traverse the characters and validate/set the fonts.
+  // Traverse the characters and validate/set the fonts.
 
   // Get the caches.
   FontId* defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
   ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
 
   // Stores the validated font runs.
-  Vector<FontRun> validatedFontRuns;
-  validatedFontRuns.Reserve( numberOfFontRuns );
+  fonts.Reserve( numberOfFontRuns );
 
   // Initializes a validated font run.
   FontRun currentFontRun;
@@ -320,15 +407,15 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
   TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
 
   // Iterators of the font and script runs.
-  Vector<FontRun>::ConstIterator fontRunIt = fontRuns.Begin();
-  Vector<FontRun>::ConstIterator fontRunEndIt = fontRuns.End();
-  Vector<ScriptRun>::ConstIterator scriptRunIt = scriptRuns.Begin();
-  Vector<ScriptRun>::ConstIterator scriptRunEndIt = scriptRuns.End();
+  Vector<FontRun>::ConstIterator fontRunIt = definedFonts.Begin();
+  Vector<FontRun>::ConstIterator fontRunEndIt = definedFonts.End();
+  Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
+  Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
 
   for( Length index = 0u; index < numberOfCharacters; ++index )
   {
     // Get the character.
-    const Character character = *( textBuffer + index );
+    const Character character = *( text.Begin() + index );
 
     // Get the font for the character.
     FontId fontId = GetFontId( index,
@@ -363,12 +450,33 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
       {
         // Check in the valid fonts cache.
         ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
+
+        if( NULL == validateFontsPerScript )
+        {
+          validateFontsPerScript = new ValidateFontsPerScript();
+
+          mValidFontsPerScriptCache.PushBack( validateFontsPerScript );
+          validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
+        }
+
         if( NULL != validateFontsPerScript )
         {
           if( !validateFontsPerScript->FindValidFont( fontId ) )
           {
             // Use the font client to validate the font.
-            const GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
+            GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
+
+            // Emojis are present in many monochrome fonts; prefer color by default.
+            if( TextAbstraction::EMOJI == script &&
+                0u != glyphIndex )
+            {
+              BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
+              if( bitmap &&
+                  Pixel::BGRA8888 != bitmap.GetPixelFormat() )
+              {
+                glyphIndex = 0;
+              }
+            }
 
             if( 0u == glyphIndex )
             {
@@ -385,30 +493,8 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
             }
           }
         }
-        else
-        {
-          // Use the font client to validate the font.
-          const GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
-
-          if( 0u == glyphIndex )
-          {
-            // Get the point size of the current font. It will be used to get a default font id.
-            pointSize = fontClient.GetPointSize( fontId );
-
-            // The font is not valid. Set to zero and a default one will be set.
-            fontId = 0u;
-          }
-          else
-          {
-            // Add the font to the valid font cache.
-            validateFontsPerScript = new ValidateFontsPerScript();
-            *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
-
-            validateFontsPerScript->mValidFonts.PushBack( fontId );
-          }
-        }
       }
-    }
+    } // !isDefault
 
     // The font has not been validated. Find a default one.
     if( 0u == fontId )
@@ -419,9 +505,27 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
       // If the cache has not a default font, get one from the font client.
       if( 0u == fontId )
       {
+        // Emojis are present in many monochrome fonts; prefer color by default.
+        bool preferColor = ( TextAbstraction::EMOJI == script );
+
         // Find a default font.
-        fontId = fontClient.FindDefaultFont( character, pointSize );
+        fontId = fontClient.FindDefaultFont( character, pointSize, preferColor );
 
+        // If the system does not support a suitable font, fallback to Latin
+        if( 0u == fontId )
+        {
+          fontId = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
+        }
+        if( 0u == fontId )
+        {
+          fontId = fontClient.FindDefaultFont( UTF32_A, pointSize );
+        }
+
+#ifdef DEBUG_ENABLED
+        Dali::TextAbstraction::FontDescription description;
+        fontClient.GetDescription( fontId, description );
+        DALI_LOG_INFO( gLogFilter, Debug::Concise, "Script: %s; Selected font: %s\n", Dali::TextAbstraction::ScriptName[script], description.path.c_str() );
+#endif
         // Cache the font.
         *( defaultFontPerScriptCacheBuffer + script ) = fontId;
       }
@@ -437,7 +541,7 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
       if( 0u != currentFontRun.characterRun.numberOfCharacters )
       {
         // Store the font run.
-        validatedFontRuns.PushBack( currentFontRun );
+        fonts.PushBack( currentFontRun );
       }
 
       // Initialize the new one.
@@ -454,12 +558,15 @@ void MultilanguageSupport::ValidateFonts( LogicalModel& model )
   if( 0u != currentFontRun.characterRun.numberOfCharacters )
   {
     // Store the last run.
-    validatedFontRuns.PushBack( currentFontRun );
+    fonts.PushBack( currentFontRun );
   }
+}
 
-  // 5) Sets the validated font runs to the model.
-  model.SetFonts( validatedFontRuns.Begin(),
-                  validatedFontRuns.Count() );
+void MultilanguageSupport::ValidateFonts( LogicalModel& model,
+                                          CharacterIndex characterIndex,
+                                          Length numberOfCharactersToRemove,
+                                          Length numberOfCharactersToInsert )
+{
 }
 
 } // namespace Internal