Merge "Add a callback to get textfitted font size." into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / emoji-helper.cpp
1
2 /*
3  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18
19 // FILE HEADER
20 #include <dali-toolkit/internal/text/emoji-helper.h>
21
22 namespace Dali
23 {
24 namespace Toolkit
25 {
26 namespace Text
27 {
28 bool IsTextPresentationSequence(const TextAbstraction::Script&    currentRunScript,
29                                 const TextAbstraction::Character& character)
30 {
31   return (IsSymbolOrEmojiOrTextScript(currentRunScript) && TextAbstraction::IsTextPresentationSelector(character));
32 }
33
34 bool IsEmojiPresentationSequence(const TextAbstraction::Script&    currentRunScript,
35                                  const TextAbstraction::Character& character)
36 {
37   return ((IsSymbolOrEmojiScript(currentRunScript) || IsEmojiColorScript(currentRunScript)) &&
38           TextAbstraction::IsEmojiPresentationSelector(character));
39 }
40
41 bool IsEmojiSequence(const TextAbstraction::Script&    currentRunScript,
42                      const TextAbstraction::Character& character,
43                      const TextAbstraction::Script&    characterScript)
44 {
45   return (IsOneOfEmojiScripts(currentRunScript) &&
46           (IsOneOfEmojiScripts(characterScript) ||
47            TextAbstraction::IsZeroWidthJoiner(character) ||
48            TextAbstraction::IsZeroWidthNonJoiner(character) ||
49            TextAbstraction::IsEmojiItem(character) ||
50            TextAbstraction::IsMiscellaneousSymbolsAndArrowsEmoji(character) ||
51            TextAbstraction::IsDingbatsEmoji(character)));
52 }
53
54 bool IsNewSequence(const Character* const         textBuffer,
55                    const TextAbstraction::Script& currentRunScript,
56                    const Length&                  currentCharacterIndex,
57                    const Length&                  lastCharacterIndex,
58                    TextAbstraction::Script&       currentCharacterScript)
59 {
60   // Until now we have two cases : VariationSelector & Keycap
61   // In-case there are more cases then should be added in this function
62
63   return IsNewKeycapSequence(textBuffer, currentCharacterIndex, lastCharacterIndex, currentCharacterScript) ||
64          IsNewVariationSelectorSequence(textBuffer, currentRunScript, currentCharacterIndex, lastCharacterIndex, currentCharacterScript);
65 }
66
67 //
68
69 bool IsNewKeycapSequence(const Character* const   textBuffer,
70                          const Length&            currentCharacterIndex,
71                          const Length&            lastCharacterIndex,
72                          TextAbstraction::Script& currentCharacterScript)
73 {
74   // Ref: https://www.unicode.org/Public/emoji/14.0/emoji-sequences.txt Search on "Emoji_Keycap_Sequence"
75   // Ref: https://www.unicode.org/Public/emoji/14.0/emoji-test.txt Search on "subgroup: keycap"
76
77   // Default initialization does not keycap sequence
78   bool isNewKeycapSequence = false;
79
80   if(currentCharacterIndex < lastCharacterIndex)
81   {
82     Character currentCharacter = *(textBuffer + currentCharacterIndex);
83     if(IsStartForKeycapSequence(currentCharacter))
84     {
85       if(!isNewKeycapSequence && currentCharacterIndex + 2 <= lastCharacterIndex)
86       {
87         Character characterOne = *(textBuffer + currentCharacterIndex + 1);
88         Character characterTwo = *(textBuffer + currentCharacterIndex + 2);
89
90         if(TextAbstraction::IsEmojiPresentationSelector(characterOne) &&
91            TextAbstraction::IsCombiningEnclosingKeycap(characterTwo))
92         {
93           isNewKeycapSequence    = true;
94           currentCharacterScript = TextAbstraction::EMOJI_COLOR;
95         }
96       } // if(!isNewKeycapSequence && currentCharacterIndex + 2 <= lastCharacterIndex)
97     }   // if(IsStartForKeycapSequence(currentCharacter))
98   }     // if(currentCharacterIndex < lastCharacterIndex)
99
100   return isNewKeycapSequence;
101 }
102
103 bool IsNewVariationSelectorSequence(const Character* const         textBuffer,
104                                     const TextAbstraction::Script& currentRunScript,
105                                     const Length&                  currentCharacterIndex,
106                                     const Length&                  lastCharacterIndex,
107                                     TextAbstraction::Script&       currentCharacterScript)
108 {
109   // Ref: Emoji and Text Presentation Selectors: https://www.unicode.org/reports/tr51/#Emoji_Variation_Selectors
110   // Ref: Emoji Variation Sequences for UTS #51: https://www.unicode.org/Public/14.0.0/ucd/emoji/emoji-variation-sequences.txt
111
112   // Default initialization does not VariationSelector sequence
113   bool isNewVariationSelectorSequence = false;
114
115   if(currentCharacterIndex < lastCharacterIndex)
116   {
117     Character currentCharacter = *(textBuffer + currentCharacterIndex);
118     if(TextAbstraction::IsEmojiVariationSequences(currentCharacter))
119     {
120       if(!isNewVariationSelectorSequence && currentCharacterIndex + 1 <= lastCharacterIndex)
121       {
122         Character characterVS = *(textBuffer + currentCharacterIndex + 1);
123
124         if(TextAbstraction::IsEmojiPresentationSelector(characterVS))
125         {
126           isNewVariationSelectorSequence = currentRunScript != TextAbstraction::EMOJI_COLOR;
127           currentCharacterScript         = TextAbstraction::EMOJI_COLOR;
128         }
129         else if(TextAbstraction::IsTextPresentationSelector(characterVS))
130         {
131           isNewVariationSelectorSequence = currentRunScript != TextAbstraction::EMOJI_TEXT;
132           currentCharacterScript         = TextAbstraction::EMOJI_TEXT;
133         }
134
135       } // if(!isNewVariationSelectorSequence && currentCharacterIndex + 1 <= lastCharacterIndex)
136     }   // if(TextAbstraction::IsEmojiVariationSequences(currentCharacter))
137   }     // if(currentCharacterIndex < lastCharacterIndex)
138
139   return isNewVariationSelectorSequence;
140 }
141
142 bool IsStartForKeycapSequence(const TextAbstraction::Character& character)
143 {
144   return (TextAbstraction::IsASCIIDigits(character) ||
145           TextAbstraction::CHAR_NUMBER_SIGN == character ||
146           TextAbstraction::CHAR_ASTERISK == character);
147 }
148
149 bool IsScriptChangedToFollowSequence(const TextAbstraction::Script&    currentRunScript,
150                                      const TextAbstraction::Character& character,
151                                      TextAbstraction::Script&          currentCharacterScript)
152 {
153   bool isUpdated = false;
154
155   // Keycap cases
156   if(TextAbstraction::IsCombiningEnclosingKeycap(character))
157   {
158     if(TextAbstraction::EMOJI == currentRunScript)
159     {
160       // Keycap and unqualified
161       // Emoji request a default presentation for an emoji character.
162       isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI);
163       currentCharacterScript = TextAbstraction::EMOJI;
164     }
165     else if(TextAbstraction::EMOJI_COLOR == currentRunScript)
166     {
167       // Keycap and fully-qualified
168       // Emoji request an emoji presentation for an emoji character.
169       isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
170       currentCharacterScript = TextAbstraction::EMOJI_COLOR;
171     }
172   }
173   // Emoji(Text) Presentation cases
174   else if(IsTextPresentationSequence(currentRunScript, character))
175   {
176     // Emoji request a text presentation for an emoji character.
177     isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_TEXT);
178     currentCharacterScript = TextAbstraction::EMOJI_TEXT;
179   }
180   // Emoji(Color) Presentation cases
181   else if(IsEmojiPresentationSequence(currentRunScript, character))
182   {
183     // Emoji request an emoji presentation for an emoji character.
184     isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
185     currentCharacterScript = TextAbstraction::EMOJI_COLOR;
186   }
187   // Default Emoji
188   else if(IsEmojiScript(currentRunScript) && IsEmojiScript(currentCharacterScript))
189   {
190     // Emoji request an emoji presentation for an emoji character.
191     isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI);
192     currentCharacterScript = TextAbstraction::EMOJI;
193   }
194   // Emoji sequences
195   else if(IsEmojiSequence(currentRunScript, character, currentCharacterScript))
196   {
197     // Emoji request an emoji presentation for an emoji character.
198     isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
199     currentCharacterScript = TextAbstraction::EMOJI_COLOR;
200   }
201
202   return isUpdated;
203 }
204
205 Character GetVariationSelectorByScript(const TextAbstraction::Script& script)
206 {
207   Character character = 0u;
208   if(TextAbstraction::EMOJI_COLOR == script)
209   {
210     character = TextAbstraction::CHAR_VARIATION_SELECTOR_16;
211   }
212   else if(TextAbstraction::EMOJI_TEXT == script)
213   {
214     character = TextAbstraction::CHAR_VARIATION_SELECTOR_15;
215   }
216
217   return character;
218 }
219
220 } // namespace Text
221
222 } // namespace Toolkit
223
224 } // namespace Dali