Support Emoji sequences 47/266047/17
authorShrouq Sabah <s.sabah@samsung.com>
Wed, 3 Nov 2021 07:43:33 +0000 (09:43 +0200)
committerShrouq Sabah <s.sabah@samsung.com>
Wed, 17 Nov 2021 10:10:59 +0000 (12:10 +0200)
This is a common solution that handles emoji sequences:
  :: text presentation sequence and selector
  :: emoji presentation sequence and selector
  :: emoji modifier sequence like skin tone
  :: emoji keycap sequence
  :: emoji flag sequence
  :: emoji tag sequence like England flag
  :: more cases in emoji zwj sequence like Rainbow Flag

This patch handle display cases.
The editing cases will be handled in another patch.

This patch depends on the dali-adaptor patch:
https://review.tizen.org/gerrit/c/platform/core/uifw/dali-adaptor/+/266046

Change-Id: I1bc7aed8a73b1d396019b2ef0a7fab5ea187d39f

automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp
automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.h
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp
dali-toolkit/internal/file.list
dali-toolkit/internal/text/emoji-helper.cpp [new file with mode: 0644]
dali-toolkit/internal/text/emoji-helper.h [new file with mode: 0644]
dali-toolkit/internal/text/multi-language-support-impl.cpp
dali-toolkit/internal/text/multi-language-support-impl.h

index 64a8473..701977d 100755 (executable)
@@ -460,6 +460,47 @@ void ConfigureTextEditor( ControllerPtr controller )
   controller->SetMatchLayoutDirection(DevelText::MatchLayoutDirection::CONTENTS);
 }
 
+
+Vector<FontDescriptionRun> CreateSingleFontDescription(
+                    const CharacterRun&         characterRun,
+                    const std::string           fontFamilyName,
+                    const FontWeight            weight,
+                    const FontWidth             width,
+                    const FontSlant             slant,
+                    const PointSize26Dot6       size,
+                    const bool                  familyDefined,
+                    const bool                  weightDefined,
+                    const bool                  widthDefined,
+                    const bool                  slantDefined,
+                    const bool                  sizeDefined)
+{
+
+  FontDescriptionRun fontDescriptionRun =
+  {
+    characterRun,
+    nullptr,
+    0u,
+    weight,
+    width,
+    slant,
+    size,
+    familyDefined,
+    weightDefined,
+    widthDefined,
+    slantDefined,
+    sizeDefined
+  };
+
+  fontDescriptionRun.familyLength = fontFamilyName.size();
+  fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
+  memcpy( fontDescriptionRun.familyName, fontFamilyName.c_str(), fontDescriptionRun.familyLength );
+
+  Vector<FontDescriptionRun> fontDescriptionRuns;
+  fontDescriptionRuns.PushBack(fontDescriptionRun);
+
+  return fontDescriptionRuns;
+}
+
 } // namespace Text
 
 } // namespace Toolkit
index ccfbe98..0b9f592 100644 (file)
@@ -93,6 +93,37 @@ void ConfigureTextField( ControllerPtr controller );
  */
 void ConfigureTextEditor( ControllerPtr controller );
 
+
+/**
+ * @brief Creates one FontDescriptionRun then add it to FontDescription list.
+ *
+ * @param[in] characterRun The initial character index and the number of characters of the run.
+ * @param[in] fontFamilyName The font's family name.
+ * @param[in] weight The font's weight.
+ * @param[in] width The font's width.
+ * @param[in] slant The font's slant.
+ * @param[in] size Whether the font's family is defined.
+ * @param[in] familyDefined Whether the font's weight is defined.
+ * @param[in] weightDefined Whether the font's width is defined.
+ * @param[in] widthDefined Whether the ellipsis layout option is enabled.
+ * @param[in] slantDefined Whether the font's slant is defined.
+ * @param[in] sizeDefined Whether the font's size is defined.
+
+* @return vector contains one FontDescriptionRun.
+ */
+Vector<FontDescriptionRun> CreateSingleFontDescription(
+                    const CharacterRun&         characterRun,
+                    const std::string           fontFamilyName,
+                    const FontWeight            weight,
+                    const FontWidth             width,
+                    const FontSlant             slant,
+                    const PointSize26Dot6       size,
+                    const bool                  familyDefined,
+                    const bool                  weightDefined,
+                    const bool                  widthDefined,
+                    const bool                  slantDefined,
+                    const bool                  sizeDefined);
+
 } // namespace Text
 
 } // namespace Toolkit
index 8c056dd..272db31 100755 (executable)
@@ -119,6 +119,7 @@ struct ShapeInfoData
   Length*         charactersPerGlyph;                 ///< The characters per glyph.
   uint32_t        expectedNumberOfNewParagraphGlyphs; ///< The expected number of glyphs.
   GlyphIndex*     newParagraphGlyphs;                 ///< Indices to the new paragraphs glyphs.
+  bool            markupProcessorEnabled;             //< Enable markup processor to use markup text.
   Vector<FontDescriptionRun> fontDescriptions;        ///< Fonts which is used for text.
 };
 
@@ -140,7 +141,7 @@ bool ShapeInfoTest( const ShapeInfoData& data )
                    layoutSize,
                    textModel,
                    metrics,
-                   false,
+                   data.markupProcessorEnabled,
                    LineWrap::WORD,
                    false,
                    Toolkit::DevelText::EllipsisPosition::END );
@@ -337,6 +338,34 @@ void LoadSoftwareStylingFonts()
   fontClient.GetFontId( pathName + DEFAULT_FONT_DIR + "/roboto/Roboto-BoldItalic.ttf" );
 }
 
+void LoadEmojiFonts()
+{
+  TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+  fontClient.ClearCache();
+  fontClient.SetDpi( 96u, 96u );
+
+  char* pathNamePtr = get_current_dir_name();
+  const std::string pathName( pathNamePtr );
+  free( pathNamePtr );
+
+
+  TextAbstraction::FontDescription fontDescriptionText;
+  fontDescriptionText.path   = "";
+  fontDescriptionText.family = "DejaVuSans";
+  fontDescriptionText.width  = TextAbstraction::FontWidth::NONE;
+  fontDescriptionText.weight = TextAbstraction::FontWeight::NORMAL;
+  fontDescriptionText.slant  = TextAbstraction::FontSlant::NONE;
+  fontClient.GetFontId(fontDescriptionText, TextAbstraction::FontClient::DEFAULT_POINT_SIZE);
+
+  TextAbstraction::FontDescription fontDescriptionEmoji;
+  fontDescriptionEmoji.path   = "";
+  fontDescriptionEmoji.family = "NotoColorEmoji";
+  fontDescriptionEmoji.width  = TextAbstraction::FontWidth::NONE;
+  fontDescriptionEmoji.weight = TextAbstraction::FontWeight::NORMAL;
+  fontDescriptionEmoji.slant  = TextAbstraction::FontSlant::NONE;
+  fontClient.GetFontId(fontDescriptionEmoji, TextAbstraction::FontClient::DEFAULT_POINT_SIZE);
+}
+
 } // namespace
 
 //////////////////////////////////////////////////////////
@@ -671,7 +700,8 @@ int UtcDaliTextShape(void)
       nullptr,
       nullptr,
       0u,
-      nullptr
+      nullptr,
+      false,
     },
     {
       "Latin script",
@@ -684,6 +714,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph02,
       0u,
       nullptr,
+      false,
       fontDescriptions01
     },
     {
@@ -697,6 +728,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph03,
       2u,
       newParagraphGlyphs03,
+      false,
       fontDescriptions02
     },
     {
@@ -710,6 +742,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph04,
       0u,
       nullptr,
+      false,
       fontDescriptions03
     },
     {
@@ -723,7 +756,8 @@ int UtcDaliTextShape(void)
       charactersPerGlyph05,
       1u,
       newParagraphGlyphs05,
-      fontDescriptions04
+      false,
+      fontDescriptions04,
     },
     {
       "Latin script with some paragraphs. Update mid paragraph.",
@@ -736,6 +770,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph05,
       1u,
       newParagraphGlyphs06,
+      false,
       fontDescriptions05
     },
     {
@@ -749,6 +784,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph05,
       1u,
       newParagraphGlyphs07,
+      false,
       fontDescriptions06
     },
   };
@@ -927,6 +963,7 @@ int UtcDaliTextSoftwareStyling(void)
       charactersPerGlyph,
       0u,
       nullptr,
+      false,
       fontDescriptions01
     },
     {
@@ -940,6 +977,7 @@ int UtcDaliTextSoftwareStyling(void)
       charactersPerGlyph,
       0u,
       nullptr,
+      false,
       fontDescriptions02
     }
   };
@@ -960,3 +998,142 @@ int UtcDaliTextSoftwareStyling(void)
   tet_result(TET_PASS);
   END_TEST;
 }
+
+
+int UtcDaliTextShapeEmojiSequences(void)
+{
+
+  ToolkitTestApplication application;
+
+  tet_infoline(" UtcDaliTextShapeEmojiSequences");
+
+  const std::string colorFontFamily( "NotoColorEmoji" );
+  const std::string textFontFamily( "DejaVuSans" );
+
+  LoadEmojiFonts();
+
+  //Common attributes for font Descriptions
+  CharacterRun    characterRun = {0u, 2u};
+  FontWeight      weight       = TextAbstraction::FontWeight::NORMAL;
+  FontWidth       width        = TextAbstraction::FontWidth::NORMAL;
+  FontSlant       slant        = TextAbstraction::FontSlant::ITALIC;
+  PointSize26Dot6 size         = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
+
+  bool familyDefined = true;
+  bool weightDefined = false;
+  bool widthDefined  = false;
+  bool slantDefined  = false;
+  bool sizeDefined   = false;
+
+
+  // variation selector 16 (Emoji)
+  struct GlyphInfoData glyphsVS16[] =
+  {
+    { 2u, 74u, 0.f, 0.f, 0.f, 0.f, 39.0f, 0.f, false, false },
+  };
+  CharacterIndex characterIndicesVS16[] = { 0u, 1u};
+  Length charactersPerGlyphVS16[] = { 2u };
+
+
+
+  // variation selector 15 (Text)
+  struct GlyphInfoData glyphsVS15[] =
+  {
+    { 1u, 3842u, 0.f, 0.f, 0.f, 0.f, 14.0f, 0.f, false, false },
+    { 1u, 8203u, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, false, false },
+  };
+  CharacterIndex characterIndicesVS15[] = { 0u, 0u};
+  Length charactersPerGlyphVS15[] = { 0u, 2u };
+
+  //Font Descriptions
+  Vector<FontDescriptionRun> fontDescriptionsColorVS16 =
+                    CreateSingleFontDescription (characterRun, colorFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+  Vector<FontDescriptionRun> fontDescriptionsColorVS15 =
+                    CreateSingleFontDescription (characterRun, colorFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+  Vector<FontDescriptionRun> fontDescriptionsTextVS16 =
+                    CreateSingleFontDescription (characterRun, textFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+  Vector<FontDescriptionRun> fontDescriptionsTextVS15 =
+                    CreateSingleFontDescription (characterRun, textFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+
+  struct ShapeInfoData data[] =
+  {
+     {
+      "EMOJI Sequence: Color Font with VS16",
+      "&#x262a;&#xfe0f;",
+      0u,
+      2u,
+      1u,
+      glyphsVS16,
+      characterIndicesVS16,
+      charactersPerGlyphVS16,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsColorVS16
+    },
+    {
+      "EMOJI Sequence: Color Font with VS15",
+      "&#x262a;&#xfe0e;",
+      0u,
+      2u,
+      2u,
+      glyphsVS15,
+      characterIndicesVS15,
+      charactersPerGlyphVS15,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsColorVS15
+    },
+    {
+      "EMOJI Sequence: Text Font with VS16",
+      "&#x262a;&#xfe0f;",
+      0u,
+      2u,
+      1u,
+      glyphsVS16,
+      characterIndicesVS16,
+      charactersPerGlyphVS16,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsTextVS16
+    },
+    {
+      "EMOJI Sequence: Text Font with VS15",
+      "&#x262a;&#xfe0e;",
+      0u,
+      2u,
+      2u,
+      glyphsVS15,
+      characterIndicesVS15,
+      charactersPerGlyphVS15,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsTextVS15
+    },
+  };
+
+  const unsigned int numberOfTests = 4u;
+
+  for( unsigned int index = 0u; index < numberOfTests; ++index )
+  {
+     tet_infoline( data[index].description.c_str());
+    if( !ShapeInfoTest( data[index] ) )
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  tet_result(TET_PASS);
+  END_TEST;
+}
index c9453e1..be0e5b1 100644 (file)
@@ -843,6 +843,37 @@ int UtcDaliToolkitTextLabelEmojisP(void)
   application.SendNotification();
   application.Render();
 
+  // EMOJI Sequences case for coverage.
+  std::string emojiSequences =
+       "Text VS15 &#x262a;&#xfe0e;\n"                                                         //text presentation sequence and selector
+      "Color VS16 &#x262a;&#xfe0f;\n"                                                        //emoji presentation sequence and selector
+      "Default &#x262a; \n"                                                                  //default presentation
+      "FamilyManWomanGirlBoy &#x1F468;&#x200D;&#x1F469;&#x200D;&#x1F467;&#x200D;&#x1F466;\n" // emoji multi zwj sequence
+      "WomanScientist &#x1f469;&#x200d;&#x1f52c;\n"                                          // emoji zwj sequence
+      "WomanScientistLightSkinTone&#x1F469;&#x1F3FB;&#x200D;&#x1F52C; \n"                    //emoji modifier sequence: skin tone & JWZ
+      "LeftRightArrowText&#x2194;&#xfe0e;\n"                                                 //text presentation sequence and selector
+      "LeftRightArrowEmoji&#x2194;&#xfe0f;\n"                                                //emoji presentation sequence and selector
+      "SouthKoreaFlag&#x1f1f0;&#x1f1f7;\n"                                                   //emoji flag sequence
+      "JordanFlag&#x1f1ef;&#x1f1f4;\n"                                                       // emoji flag sequence
+      "EnglandFlag&#x1F3F4;&#xE0067;&#xE0062;&#xE0065;&#xE006E;&#xE0067;&#xE007F;\n"         //emoji tag sequence like England flag
+      "Runner &#x1f3c3;&#x200d;&#x27a1;&#xfe0f; \n"
+      "VictoryHandMediumLightSkinTone:&#x270C;&#xFE0F;&#x1F3FC;\n"               //emoji modifier sequence: skin tone
+      "RainbowFlag:&#x1F3F3;&#xFE0F;&#x200D;&#x1F308; \n"                        //emoji zwj sequence: Rainbow Flag
+      "keycap# &#x0023;&#xFE0F;&#x20E3; \n"                                      // fully-qualified  emoji keycap sequence
+      "keycap#_text &#x0023;&#x20E3; \n"                                         // unqualified emoji keycap sequence
+      "keycap3 &#x0033;&#xfe0f;&#x20e3; \n"                                      // fully-qualified  emoji keycap sequence
+      "keycap3_text &#x0033;&#x20e3; \n"                                         // unqualified emoji keycap sequence
+      "two adjacent glyphs &#x262a;&#xfe0f;&#xfe0f;&#xfe0f;&#x262a;&#xfe0f;\n"   //This line should be rendered as two adjacent glyphs
+      "Digit 8&#xfe0f; 8&#xfe0e; 8\n"                                            // should be rendered according to selector
+      "Surfing Medium Skin Female:  &#x1f3c4;&#x1f3fc;&#x200d;&#x2640;&#xfe0f;"; // Person Surfing + Medium Skin Tone +? Zero Width Joiner + Female Sign
+
+  label.SetProperty( TextLabel::Property::TEXT, emojiSequences );
+  label.SetProperty( TextLabel::Property::ENABLE_MARKUP, true );
+  label.SetProperty( TextLabel::Property::MULTI_LINE, true);
+  label.SetProperty( TextLabel::Property::ELLIPSIS, false);
+
+  application.SendNotification();
+  application.Render();
   END_TEST;
 }
 
index 3229006..64501d3 100644 (file)
@@ -202,6 +202,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/transition-effects/cube-transition-wave-effect-impl.cpp
    ${toolkit_src_dir}/text/xhtml-entities.cpp
    ${toolkit_src_dir}/drag-drop-detector/drag-and-drop-detector-impl.cpp
+   ${toolkit_src_dir}/text/emoji-helper.cpp
 )
 
 SET( SOURCES ${SOURCES}
diff --git a/dali-toolkit/internal/text/emoji-helper.cpp b/dali-toolkit/internal/text/emoji-helper.cpp
new file mode 100644 (file)
index 0000000..eec4ee4
--- /dev/null
@@ -0,0 +1,224 @@
+
+/*
+ * Copyright (c) 2021 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.
+ *
+ */
+
+// FILE HEADER
+#include <dali-toolkit/internal/text/emoji-helper.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+bool IsTextPresentationSequence(const TextAbstraction::Script&    currentRunScript,
+                                const TextAbstraction::Character& character)
+{
+  return (IsSymbolOrEmojiOrTextScript(currentRunScript) && TextAbstraction::IsTextPresentationSelector(character));
+}
+
+bool IsEmojiPresentationSequence(const TextAbstraction::Script&    currentRunScript,
+                                 const TextAbstraction::Character& character)
+{
+  return ((IsSymbolOrEmojiScript(currentRunScript) || IsEmojiColorScript(currentRunScript)) &&
+          TextAbstraction::IsEmojiPresentationSelector(character));
+}
+
+bool IsEmojiSequence(const TextAbstraction::Script&    currentRunScript,
+                     const TextAbstraction::Character& character,
+                     const TextAbstraction::Script&    characterScript)
+{
+  return (IsOneOfEmojiScripts(currentRunScript) &&
+          (IsOneOfEmojiScripts(characterScript) ||
+           TextAbstraction::IsZeroWidthJoiner(character) ||
+           TextAbstraction::IsZeroWidthNonJoiner(character) ||
+           TextAbstraction::IsEmojiItem(character) ||
+           TextAbstraction::IsMiscellaneousSymbolsAndArrowsEmoji(character) ||
+           TextAbstraction::IsDingbatsEmoji(character)));
+}
+
+bool IsNewSequence(const Character* const         textBuffer,
+                   const TextAbstraction::Script& currentRunScript,
+                   const Length&                  currentCharacterIndex,
+                   const Length&                  lastCharacterIndex,
+                   TextAbstraction::Script&       currentCharacterScript)
+{
+  // Until now we have two cases : VariationSelector & Keycap
+  // In-case there are more cases then should be added in this function
+
+  return IsNewKeycapSequence(textBuffer, currentCharacterIndex, lastCharacterIndex, currentCharacterScript) ||
+         IsNewVariationSelectorSequence(textBuffer, currentRunScript, currentCharacterIndex, lastCharacterIndex, currentCharacterScript);
+}
+
+//
+
+bool IsNewKeycapSequence(const Character* const   textBuffer,
+                         const Length&            currentCharacterIndex,
+                         const Length&            lastCharacterIndex,
+                         TextAbstraction::Script& currentCharacterScript)
+{
+  // Ref: https://www.unicode.org/Public/emoji/14.0/emoji-sequences.txt Search on "Emoji_Keycap_Sequence"
+  // Ref: https://www.unicode.org/Public/emoji/14.0/emoji-test.txt Search on "subgroup: keycap"
+
+  // Default initialization does not keycap sequence
+  bool isNewKeycapSequence = false;
+
+  if(currentCharacterIndex < lastCharacterIndex)
+  {
+    Character currentCharacter = *(textBuffer + currentCharacterIndex);
+    if(IsStartForKeycapSequence(currentCharacter))
+    {
+      if(!isNewKeycapSequence && currentCharacterIndex + 2 <= lastCharacterIndex)
+      {
+        Character characterOne = *(textBuffer + currentCharacterIndex + 1);
+        Character characterTwo = *(textBuffer + currentCharacterIndex + 2);
+
+        if(TextAbstraction::IsEmojiPresentationSelector(characterOne) &&
+           TextAbstraction::IsCombiningEnclosingKeycap(characterTwo))
+        {
+          isNewKeycapSequence    = true;
+          currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+        }
+      } // if(!isNewKeycapSequence && currentCharacterIndex + 2 <= lastCharacterIndex)
+    }   // if(IsStartForKeycapSequence(currentCharacter))
+  }     // if(currentCharacterIndex < lastCharacterIndex)
+
+  return isNewKeycapSequence;
+}
+
+bool IsNewVariationSelectorSequence(const Character* const         textBuffer,
+                                    const TextAbstraction::Script& currentRunScript,
+                                    const Length&                  currentCharacterIndex,
+                                    const Length&                  lastCharacterIndex,
+                                    TextAbstraction::Script&       currentCharacterScript)
+{
+  // Ref: Emoji and Text Presentation Selectors: https://www.unicode.org/reports/tr51/#Emoji_Variation_Selectors
+  // Ref: Emoji Variation Sequences for UTS #51: https://www.unicode.org/Public/14.0.0/ucd/emoji/emoji-variation-sequences.txt
+
+  // Default initialization does not VariationSelector sequence
+  bool isNewVariationSelectorSequence = false;
+
+  if(currentCharacterIndex < lastCharacterIndex)
+  {
+    Character currentCharacter = *(textBuffer + currentCharacterIndex);
+    if(TextAbstraction::IsEmojiVariationSequences(currentCharacter))
+    {
+      if(!isNewVariationSelectorSequence && currentCharacterIndex + 1 <= lastCharacterIndex)
+      {
+        Character characterVS = *(textBuffer + currentCharacterIndex + 1);
+
+        if(TextAbstraction::IsEmojiPresentationSelector(characterVS))
+        {
+          isNewVariationSelectorSequence = currentRunScript != TextAbstraction::EMOJI_COLOR;
+          currentCharacterScript         = TextAbstraction::EMOJI_COLOR;
+        }
+        else if(TextAbstraction::IsTextPresentationSelector(characterVS))
+        {
+          isNewVariationSelectorSequence = currentRunScript != TextAbstraction::EMOJI_TEXT;
+          currentCharacterScript         = TextAbstraction::EMOJI_TEXT;
+        }
+
+      } // if(!isNewVariationSelectorSequence && currentCharacterIndex + 1 <= lastCharacterIndex)
+    }   // if(TextAbstraction::IsEmojiVariationSequences(currentCharacter))
+  }     // if(currentCharacterIndex < lastCharacterIndex)
+
+  return isNewVariationSelectorSequence;
+}
+
+bool IsStartForKeycapSequence(const TextAbstraction::Character& character)
+{
+  return (TextAbstraction::IsASCIIDigits(character) ||
+          TextAbstraction::CHAR_NUMBER_SIGN == character ||
+          TextAbstraction::CHAR_ASTERISK == character);
+}
+
+bool IsScriptChangedToFollowSequence(const TextAbstraction::Script&    currentRunScript,
+                                     const TextAbstraction::Character& character,
+                                     TextAbstraction::Script&          currentCharacterScript)
+{
+  bool isUpdated = false;
+
+  // Keycap cases
+  if(TextAbstraction::IsCombiningEnclosingKeycap(character))
+  {
+    if(TextAbstraction::EMOJI == currentRunScript)
+    {
+      // Keycap and unqualified
+      // Emoji request a default presentation for an emoji character.
+      isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI);
+      currentCharacterScript = TextAbstraction::EMOJI;
+    }
+    else if(TextAbstraction::EMOJI_COLOR == currentRunScript)
+    {
+      // Keycap and fully-qualified
+      // Emoji request an emoji presentation for an emoji character.
+      isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
+      currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+    }
+  }
+  // Emoji(Text) Presentation cases
+  else if(IsTextPresentationSequence(currentRunScript, character))
+  {
+    // Emoji request a text presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_TEXT);
+    currentCharacterScript = TextAbstraction::EMOJI_TEXT;
+  }
+  // Emoji(Color) Presentation cases
+  else if(IsEmojiPresentationSequence(currentRunScript, character))
+  {
+    // Emoji request an emoji presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
+    currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+  }
+  // Default Emoji
+  else if(IsEmojiScript(currentRunScript) && IsEmojiScript(currentCharacterScript))
+  {
+    // Emoji request an emoji presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI);
+    currentCharacterScript = TextAbstraction::EMOJI;
+  }
+  // Emoji sequences
+  else if(IsEmojiSequence(currentRunScript, character, currentCharacterScript))
+  {
+    // Emoji request an emoji presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
+    currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+  }
+
+  return isUpdated;
+}
+
+Character GetVariationSelectorByScript(const TextAbstraction::Script& script)
+{
+  Character character = 0u;
+  if(TextAbstraction::EMOJI_COLOR == script)
+  {
+    character = TextAbstraction::CHAR_VARIATION_SELECTOR_16;
+  }
+  else if(TextAbstraction::EMOJI_TEXT == script)
+  {
+    character = TextAbstraction::CHAR_VARIATION_SELECTOR_15;
+  }
+
+  return character;
+}
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
\ No newline at end of file
diff --git a/dali-toolkit/internal/text/emoji-helper.h b/dali-toolkit/internal/text/emoji-helper.h
new file mode 100644 (file)
index 0000000..95efd9f
--- /dev/null
@@ -0,0 +1,170 @@
+#ifndef DALI_TOOLKIT_TEXT_EMOJI_HELPER_H
+#define DALI_TOOLKIT_TEXT_EMOJI_HELPER_H
+
+/*
+ * Copyright (c) 2021 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.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-definitions.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/text-abstraction/emoji-character-properties.h>
+#include <dali/devel-api/text-abstraction/emoji-helper.h>
+#include <dali/devel-api/text-abstraction/script.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+/**
+ * @brief Whether the sequence is a variation sequence consisting of an emoji character followed by a text presentation selector.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ *
+ * @return @e true if the sequence is text presentation sequence.
+ */
+bool IsTextPresentationSequence(const TextAbstraction::Script& currentRunScript, const TextAbstraction::Character& character);
+
+/**
+ * @brief Whether the sequence is a variation sequence consisting of an emoji character followed by a emoji presentation selector.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ *
+ * @return @e true if the sequence is emoji presentation sequence.
+ */
+bool IsEmojiPresentationSequence(const TextAbstraction::Script&    currentRunScript,
+                                 const TextAbstraction::Character& character);
+
+/**
+ * @brief Whether the sequence is an emoji sequence.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ * @param[in] characterScript The script of the next character.
+ *
+ * @return @e true if the sequence is an emoji sequence.
+ */
+bool IsEmojiSequence(const TextAbstraction::Script&    currentRunScript,
+                     const TextAbstraction::Character& character,
+                     const TextAbstraction::Script&    characterScript);
+
+/**
+ * @brief Whether the sequence is a keycap sequence and set script according to the case.
+ *
+ * @param[in] textBuffer The text.
+ * @param[in] currentCharacterIndex The index of current character.
+ * @param[in] lastCharacterIndex The index of last character.
+ * @param[out] currentCharacterScript The current character script to update it in-case it's the Keycap sequence.
+ *
+ * @return @e true if @p currentRunScript is ASCII_DIGITS and @p character is COMBINING_ENCLOSING_KEYCAP
+ */
+bool IsNewKeycapSequence(const Character* const   textBuffer,
+                         const Length&            currentCharacterIndex,
+                         const Length&            lastCharacterIndex,
+                         TextAbstraction::Script& currentCharacterScript);
+
+/**
+ * @brief Whether the sequence is a variation selector sequence and set script according to the case.
+ *
+ * @param[in] textBuffer The text.
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] currentCharacterIndex The index of current character.
+ * @param[in] lastCharacterIndex The index of last character.
+ * @param[out] currentCharacterScript The current character script to update it in-case it's the VariationSelector sequence.
+ *
+ * @return @e true if @p currentRunScript is ASCII_DIGITS and @p character is COMBINING_ENCLOSING_KEYCAP
+ */
+bool IsNewVariationSelectorSequence(const Character* const         textBuffer,
+                                    const TextAbstraction::Script& currentRunScript,
+                                    const Length&                  currentCharacterIndex,
+                                    const Length&                  lastCharacterIndex,
+                                    TextAbstraction::Script&       currentCharacterScript);
+
+/**
+ * @brief Whether the case is a new sequence and set script according to the case.
+ *
+ * @param[in] textBuffer The text.
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] currentCharacterIndex The index of current character.
+ * @param[in] lastCharacterIndex The index of last character.
+ * @param[out] currentCharacterScript The current character script to update according to sequence.
+ *
+ * @return @e true the case is a new sequence
+ */
+bool IsNewSequence(const Character* const         textBuffer,
+                   const TextAbstraction::Script& currentRunScript,
+                   const Length&                  currentCharacterIndex,
+                   const Length&                  lastCharacterIndex,
+                   TextAbstraction::Script&       currentCharacterScript);
+
+/**
+ * @brief Whether the character is ASCII digits | # Number Sign | * Asterisk.
+ *
+ * @param[in] character The character.
+ *
+ * @return @e true if the character is ASCII digits | # Number Sign | * Asterisk.
+ */
+bool IsStartForKeycapSequence(const TextAbstraction::Character& character);
+
+/**
+ * @brief Check sequence case and update script of character if needed.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ * @param[out] script The current character script to update according to sequence.
+ *
+ * @return @e true if the script is changed
+ */
+bool IsScriptChangedToFollowSequence(const TextAbstraction::Script&    currentRunScript,
+                                     const TextAbstraction::Character& character,
+                                     TextAbstraction::Script&          script);
+
+/**
+ * @brief Check sequence case and update script of character if needed.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ * @param[out] script The current character script to update according to sequence.
+ *
+ * @return @e true if the script is changed
+ */
+bool HandleEmojiSequence(const Character* const         textBuffer,
+                         const Length&                  currentCharacterIndex,
+                         const Length&                  lastCharacterIndex,
+                         const TextAbstraction::Script& currentRunScript,
+                         TextAbstraction::Script&       currentCharacterScript);
+
+/**
+ * @brief Determine the Variation Selector according to script.
+ *
+ * @param[in] script The script.
+ *
+ * @return CHAR_VARIATION_SELECTOR_15 in-case EMOJI_TEXT or CHAR_VARIATION_SELECTOR_16 in-case EMOJI_COLOR or 0 otherwise
+ */
+Character GetVariationSelectorByScript(const TextAbstraction::Script& script);
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_EMOJI_HELPER_H
\ No newline at end of file
index 82470f8..dbd110b 100644 (file)
@@ -24,6 +24,7 @@
 #include <dali/integration-api/debug.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/emoji-helper.h>
 #include <dali-toolkit/internal/text/multi-language-helper-functions.h>
 
 namespace Dali
@@ -97,11 +98,11 @@ MultilanguageSupport::MultilanguageSupport()
 {
   // 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.
-  mDefaultFontPerScriptCache.Resize(TextAbstraction::UNKNOWN + 1, NULL);
+  mDefaultFontPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
 
   // 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::UNKNOWN + 1, NULL);
+  mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
 }
 
 MultilanguageSupport::~MultilanguageSupport()
@@ -205,6 +206,7 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
 
   // Traverse all characters and set the scripts.
   const Length lastCharacter = startIndex + numberOfCharacters;
+
   for(Length index = startIndex; index < lastCharacter; ++index)
   {
     Character character = *(textBuffer + index);
@@ -222,26 +224,40 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
 
     // Skip those characters valid for many scripts like white spaces or '\n'.
     bool endOfText = index == lastCharacter;
+
+    //Handle all Emoji Sequence cases
+    if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
+    {
+      AddCurrentScriptAndCreatNewScript(script,
+                                        false,
+                                        false,
+                                        currentScriptRun,
+                                        numberOfAllScriptCharacters,
+                                        scripts,
+                                        scriptIndex);
+    }
+    else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
+    {
+      currentScriptRun.script = script;
+    }
+    else if(IsOneOfEmojiScripts(currentScriptRun.script) && (TextAbstraction::COMMON == script))
+    {
+      // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
+      AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
+                                        false,
+                                        false,
+                                        currentScriptRun,
+                                        numberOfAllScriptCharacters,
+                                        scripts,
+                                        scriptIndex);
+    }
+
     while(!endOfText &&
           (TextAbstraction::COMMON == script))
     {
       // Check if whether is right to left markup and Keeps true if the previous value was true.
       currentScriptRun.isRightToLeft = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftMark(character);
 
-      // ZWJ, ZWNJ between emojis should be treated as EMOJI.
-      if(TextAbstraction::EMOJI == currentScriptRun.script && !(TextAbstraction::IsZeroWidthJoiner(character) || TextAbstraction::IsZeroWidthNonJoiner(character)))
-      {
-        // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
-        scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
-        ++scriptIndex;
-
-        // Initialize the new one.
-        currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-        currentScriptRun.characterRun.numberOfCharacters = 0u;
-        currentScriptRun.script                          = TextAbstraction::UNKNOWN;
-        numberOfAllScriptCharacters                      = 0u;
-      }
-
       // Count all these characters to be added into a script.
       ++numberOfAllScriptCharacters;
 
@@ -253,20 +269,13 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
         // the same direction than the first script of the paragraph.
         isFirstScriptToBeSet = true;
 
-        // Characters common to all scripts at the end of the paragraph are added to the last script.
-        currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
-
-        // Store the script run.
-        scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
-        ++scriptIndex;
-
-        // Initialize the new one.
-        currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-        currentScriptRun.characterRun.numberOfCharacters = 0u;
-        currentScriptRun.script                          = TextAbstraction::UNKNOWN;
-        numberOfAllScriptCharacters                      = 0u;
-        // Initialize whether is right to left direction
-        currentScriptRun.isRightToLeft = false;
+        AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
+                                          false,
+                                          false,
+                                          currentScriptRun,
+                                          numberOfAllScriptCharacters,
+                                          scripts,
+                                          scriptIndex);
       }
 
       // Get the next character.
@@ -276,6 +285,22 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
       {
         character = *(textBuffer + index);
         script    = TextAbstraction::GetCharacterScript(character);
+
+        //Handle all Emoji Sequence cases
+        if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
+        {
+          AddCurrentScriptAndCreatNewScript(script,
+                                            false,
+                                            false,
+                                            currentScriptRun,
+                                            numberOfAllScriptCharacters,
+                                            scripts,
+                                            scriptIndex);
+        }
+        else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
+        {
+          currentScriptRun.script = script;
+        }
       }
     } // end while( !endOfText && ( TextAbstraction::COMMON == script ) )
 
@@ -290,7 +315,10 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
     if(isFirstScriptToBeSet &&
        (TextAbstraction::UNKNOWN != script) &&
        (TextAbstraction::COMMON != script) &&
-       (TextAbstraction::EMOJI != script))
+       (TextAbstraction::EMOJI != script) &&
+       (TextAbstraction::EMOJI_TEXT != script) &&
+       (TextAbstraction::EMOJI_COLOR != script) &&
+       (!TextAbstraction::IsSymbolScript(script)))
     {
       // Sets the direction of the first valid script.
       isParagraphRTL       = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftScript(script);
@@ -319,26 +347,21 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
         numberOfAllScriptCharacters = 0u;
       }
       else if((TextAbstraction::UNKNOWN == currentScriptRun.script) &&
-              (TextAbstraction::EMOJI == script))
+              (TextAbstraction::IsSymbolOrEmojiOrTextScript(script)))
       {
         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
         numberOfAllScriptCharacters = 0u;
       }
 
-      if(0u != currentScriptRun.characterRun.numberOfCharacters)
-      {
-        // Store the script run.
-        scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
-        ++scriptIndex;
-      }
-
-      // Initialize the new one.
-      currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-      currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
-      currentScriptRun.script                          = script;
-      numberOfAllScriptCharacters                      = 0u;
-      // Check if whether is right to left script.
-      currentScriptRun.isRightToLeft = TextAbstraction::IsRightToLeftScript(currentScriptRun.script);
+      // Adds the white spaces which are at the begining of the script.
+      numberOfAllScriptCharacters++;
+      AddCurrentScriptAndCreatNewScript(script,
+                                        TextAbstraction::IsRightToLeftScript(script),
+                                        true,
+                                        currentScriptRun,
+                                        numberOfAllScriptCharacters,
+                                        scripts,
+                                        scriptIndex);
     }
     else
     {
@@ -445,8 +468,8 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
   Vector<ScriptRun>::ConstIterator scriptRunEndIt          = scripts.End();
   bool                             isNewParagraphCharacter = false;
 
-  bool   isPreviousEmojiScript = false;
-  FontId previousEmojiFontId   = 0u;
+  FontId                  previousEmojiFontId = 0u;
+  TextAbstraction::Script previousScript      = TextAbstraction::UNKNOWN;
 
   CharacterIndex lastCharacter = startIndex + numberOfCharacters;
   for(Length index = startIndex; index < lastCharacter; ++index)
@@ -518,28 +541,11 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
     }
 
     bool isCommonScript = false;
-    bool isEmojiScript  = TextAbstraction::EMOJI == script;
-
-    if(isEmojiScript && !isPreviousEmojiScript)
-    {
-      if(0u != currentFontRun.characterRun.numberOfCharacters)
-      {
-        // Store the font run.
-        fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
-        ++fontIndex;
-      }
-
-      // Initialize the new one.
-      currentFontRun.characterRun.characterIndex     = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
-      currentFontRun.characterRun.numberOfCharacters = 0u;
-      currentFontRun.fontId                          = fontId;
-      currentFontRun.isItalicRequired                = false;
-      currentFontRun.isBoldRequired                  = false;
-    }
+    bool isEmojiScript  = TextAbstraction::IsEmojiScript(script) || TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script);
 
-    // ZWJ, ZWNJ between emojis should use the previous emoji font.
-    if(isEmojiScript && (TextAbstraction::IsZeroWidthJoiner(character) || TextAbstraction::IsZeroWidthNonJoiner(character)))
+    if(isEmojiScript && (previousScript == script))
     {
+      // Emoji sequence should use the previous emoji font.
       if(0u != previousEmojiFontId)
       {
         fontId      = previousEmojiFontId;
@@ -567,7 +573,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       //
       //      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);
+      isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
 
       // Check in the valid fonts cache.
       ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
@@ -659,14 +665,55 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       }   // !isValidFont (2)
     }     // !isValidFont (1)
 
+    if(isEmojiScript && (previousScript != script))
+    {
+      //New Emoji sequence should select font according to the variation selector (VS15 or VS16).
+      if(0u != currentFontRun.characterRun.numberOfCharacters)
+      {
+        // Store the font run.
+        fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
+        ++fontIndex;
+      }
+
+      // Initialize the new one.
+      currentFontRun.characterRun.characterIndex     = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
+      currentFontRun.characterRun.numberOfCharacters = 0u;
+      currentFontRun.fontId                          = fontId;
+      currentFontRun.isItalicRequired                = false;
+      currentFontRun.isBoldRequired                  = false;
+
+      if(TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script))
+      {
+        bool       isModifiedByVariationSelector = false;
+        GlyphIndex glyphIndexChar                = fontClient.GetGlyphIndex(fontId, character);
+        GlyphIndex glyphIndexCharByVS            = fontClient.GetGlyphIndex(fontId, character, Text::GetVariationSelectorByScript(script));
+
+        isModifiedByVariationSelector = glyphIndexChar != glyphIndexCharByVS;
+
+        if(isModifiedByVariationSelector)
+        {
+          FontId requestedFontId = fontClient.FindDefaultFont(character, currentFontPointSize, IsEmojiColorScript(script));
+          if(0u != requestedFontId)
+          {
+            currentFontRun.fontId = fontId = requestedFontId;
+            isValidFont                    = true;
+          }
+        }
+      }
+    }
+
     // Store the font id when the first character is an emoji.
-    if(isEmojiScript && !isPreviousEmojiScript)
+    if(isEmojiScript)
     {
-      if(0u != fontId)
+      if(0u != fontId && previousScript != script)
       {
         previousEmojiFontId = fontId;
       }
     }
+    else
+    {
+      previousEmojiFontId = 0u;
+    }
 
 #ifdef DEBUG_ENABLED
     {
@@ -715,7 +762,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
 
     // Whether the current character is a new paragraph character.
     isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
-    isPreviousEmojiScript   = isEmojiScript;
+    previousScript          = script;
   } // end traverse characters.
 
   if(0u != currentFontRun.characterRun.numberOfCharacters)
@@ -746,6 +793,34 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
   DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
 }
 
+void MultilanguageSupport::AddCurrentScriptAndCreatNewScript(const Script       requestedScript,
+                                                             const bool         isRightToLeft,
+                                                             const bool         addScriptCharactersToNewScript,
+                                                             ScriptRun&         currentScriptRun,
+                                                             Length&            numberOfAllScriptCharacters,
+                                                             Vector<ScriptRun>& scripts,
+                                                             ScriptRunIndex&    scriptIndex)
+{
+  // Add the pending characters to the current script
+  currentScriptRun.characterRun.numberOfCharacters += (addScriptCharactersToNewScript ? 0u : numberOfAllScriptCharacters);
+
+  // In-case the current script is empty then no need to add it for scripts
+  if(0u != currentScriptRun.characterRun.numberOfCharacters)
+  {
+    // Store the script run.
+    scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
+    ++scriptIndex;
+  }
+
+  // Initialize the new one by the requested script
+  currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
+  currentScriptRun.characterRun.numberOfCharacters = (addScriptCharactersToNewScript ? numberOfAllScriptCharacters : 0u);
+  currentScriptRun.script                          = requestedScript;
+  numberOfAllScriptCharacters                      = 0u;
+  // Initialize whether is right to left direction
+  currentScriptRun.isRightToLeft = isRightToLeft;
+}
+
 } // namespace Internal
 
 } // namespace Text
index de44e19..cda767a 100644 (file)
@@ -160,6 +160,28 @@ public:
 private:
   Vector<DefaultFonts*>           mDefaultFontPerScriptCache; ///< Caches default fonts for a script.
   Vector<ValidateFontsPerScript*> mValidFontsPerScriptCache;  ///< Caches valid fonts for a script.
+
+  //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.
+ *
+ */
+  void AddCurrentScriptAndCreatNewScript(const Script       requestedScript,
+                                         const bool         isRightToLeft,
+                                         const bool         addScriptCharactersToNewScript,
+                                         ScriptRun&         currentScriptRun,
+                                         Length&            numberOfAllScriptCharacters,
+                                         Vector<ScriptRun>& scripts,
+                                         ScriptRunIndex&    scriptIndex);
 };
 
 } // namespace Internal