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
*/
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
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.
};
layoutSize,
textModel,
metrics,
- false,
+ data.markupProcessorEnabled,
LineWrap::WORD,
false,
Toolkit::DevelText::EllipsisPosition::END );
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
//////////////////////////////////////////////////////////
nullptr,
nullptr,
0u,
- nullptr
+ nullptr,
+ false,
},
{
"Latin script",
charactersPerGlyph02,
0u,
nullptr,
+ false,
fontDescriptions01
},
{
charactersPerGlyph03,
2u,
newParagraphGlyphs03,
+ false,
fontDescriptions02
},
{
charactersPerGlyph04,
0u,
nullptr,
+ false,
fontDescriptions03
},
{
charactersPerGlyph05,
1u,
newParagraphGlyphs05,
- fontDescriptions04
+ false,
+ fontDescriptions04,
},
{
"Latin script with some paragraphs. Update mid paragraph.",
charactersPerGlyph05,
1u,
newParagraphGlyphs06,
+ false,
fontDescriptions05
},
{
charactersPerGlyph05,
1u,
newParagraphGlyphs07,
+ false,
fontDescriptions06
},
};
charactersPerGlyph,
0u,
nullptr,
+ false,
fontDescriptions01
},
{
charactersPerGlyph,
0u,
nullptr,
+ false,
fontDescriptions02
}
};
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",
+ "☪️",
+ 0u,
+ 2u,
+ 1u,
+ glyphsVS16,
+ characterIndicesVS16,
+ charactersPerGlyphVS16,
+ 0u,
+ nullptr,
+ true,
+ fontDescriptionsColorVS16
+ },
+ {
+ "EMOJI Sequence: Color Font with VS15",
+ "☪︎",
+ 0u,
+ 2u,
+ 2u,
+ glyphsVS15,
+ characterIndicesVS15,
+ charactersPerGlyphVS15,
+ 0u,
+ nullptr,
+ true,
+ fontDescriptionsColorVS15
+ },
+ {
+ "EMOJI Sequence: Text Font with VS16",
+ "☪️",
+ 0u,
+ 2u,
+ 1u,
+ glyphsVS16,
+ characterIndicesVS16,
+ charactersPerGlyphVS16,
+ 0u,
+ nullptr,
+ true,
+ fontDescriptionsTextVS16
+ },
+ {
+ "EMOJI Sequence: Text Font with VS15",
+ "☪︎",
+ 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;
+}
application.SendNotification();
application.Render();
+ // EMOJI Sequences case for coverage.
+ std::string emojiSequences =
+ "Text VS15 ☪︎\n" //text presentation sequence and selector
+ "Color VS16 ☪️\n" //emoji presentation sequence and selector
+ "Default ☪ \n" //default presentation
+ "FamilyManWomanGirlBoy 👨‍👩‍👧‍👦\n" // emoji multi zwj sequence
+ "WomanScientist 👩‍🔬\n" // emoji zwj sequence
+ "WomanScientistLightSkinTone👩🏻‍🔬 \n" //emoji modifier sequence: skin tone & JWZ
+ "LeftRightArrowText↔︎\n" //text presentation sequence and selector
+ "LeftRightArrowEmoji↔️\n" //emoji presentation sequence and selector
+ "SouthKoreaFlag🇰🇷\n" //emoji flag sequence
+ "JordanFlag🇯🇴\n" // emoji flag sequence
+ "EnglandFlag🏴󠁧󠁢󠁥󠁮󠁧󠁿\n" //emoji tag sequence like England flag
+ "Runner 🏃‍➡️ \n"
+ "VictoryHandMediumLightSkinTone:✌️🏼\n" //emoji modifier sequence: skin tone
+ "RainbowFlag:🏳️‍🌈 \n" //emoji zwj sequence: Rainbow Flag
+ "keycap# #️⃣ \n" // fully-qualified emoji keycap sequence
+ "keycap#_text #⃣ \n" // unqualified emoji keycap sequence
+ "keycap3 3️⃣ \n" // fully-qualified emoji keycap sequence
+ "keycap3_text 3⃣ \n" // unqualified emoji keycap sequence
+ "two adjacent glyphs ☪️️️☪️\n" //This line should be rendered as two adjacent glyphs
+ "Digit 8️ 8︎ 8\n" // should be rendered according to selector
+ "Surfing Medium Skin Female: 🏄🏼‍♀️"; // 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;
}
${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}
--- /dev/null
+
+/*
+ * 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
--- /dev/null
+#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
#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
{
// 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()
// Traverse all characters and set the scripts.
const Length lastCharacter = startIndex + numberOfCharacters;
+
for(Length index = startIndex; index < lastCharacter; ++index)
{
Character character = *(textBuffer + index);
// 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;
// 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.
{
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 ) )
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);
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
{
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)
}
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;
//
// 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);
} // !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
{
// 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)
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
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