Multi-language support interface
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / multi-language-support-impl.cpp
index 4143b03..27d0ee2 100644 (file)
 // CLASS HEADER
 #include <dali-toolkit/internal/text/multi-language-support-impl.h>
 
-// INTERNAL INCLUDES
+// 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>
+
+// INTERNAL INCLUDES
 #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>
-#include <dali/integration-api/debug.h>
 
 namespace Dali
 {
@@ -65,7 +69,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 +106,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 +116,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 ( IsWhiteSpace( character )         ||
+           IsZeroWidthNonJoiner( character ) ||
+           IsZeroWidthJoiner( character )    ||
+           IsZeroWidthSpace( character )     ||
+           IsLeftToRightMark( character )    ||
+           IsRightToLeftMark( character )    ||
+           IsThinSpace( character ) );
+}
+
 bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
 {
   for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
@@ -179,10 +201,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 +213,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;
+
+      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;
+      }
+
+      // Get the next character.
+      ++index;
+      endOfText = index == numberOfCharacters;
+      if( !endOfText )
+      {
+        character = *( textBuffer + index );
+        breakInfo = *( breakInfoBuffer + index );
+      }
+    }
 
+    if( endOfText )
+    {
+      // 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 = GetCharacterScript( character );
 
-    if( TextAbstraction::UNKNOWN == script )
+    // Check if it is the first character of a paragraph.
+    if( firstValidScript &&
+        ( TextAbstraction::UNKNOWN != script ) )
     {
-      script = TextAbstraction::LATIN;
-      DALI_ASSERT_DEBUG( !"MultilanguageSupport::SetScripts. Unkown script!" );
+      // Sets the direction of the first valid script.
+      isParagraphRTL = ( TextAbstraction::ARABIC == script );
+      firstValidScript = false;
     }
 
     if( script != currentScriptRun.script )
     {
       // Current run needs to be stored and a new one initialized.
 
+      if( isParagraphRTL != ( TextAbstraction::ARABIC == 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 +372,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.
-
-  const Length numberOfFontRuns = model.GetNumberOfFontRuns( 0u, numberOfCharacters );
-
-  Vector<FontRun> fontRuns;
-  fontRuns.Reserve( numberOfFontRuns );
+  // Copy the fonts set by application developers.
+  const Length numberOfFontRuns = fonts.Count();
+  const Vector<FontRun> definedFonts = fonts;
+  fonts.Clear();
 
-  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 +397,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,
@@ -437,7 +514,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 +531,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