Fix GetHeightForWidth for text controller
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / multi-language-support-impl.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/multi-language-support-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/common/singleton-service.h>
23 #include <dali/devel-api/text-abstraction/font-client.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/integration-api/trace.h>
26
27 // INTERNAL INCLUDES
28 #include <dali-toolkit/internal/text/emoji-helper.h>
29 #include <dali-toolkit/internal/text/multi-language-helper-functions.h>
30
31 namespace Dali
32 {
33 namespace Toolkit
34 {
35 namespace
36 {
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
39 #endif
40
41 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false);
42
43 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
44
45 // TODO : Customization required for these values.
46 constexpr std::size_t MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE = 63;
47 constexpr std::size_t MAX_DEFAULT_FONTS_CACHE_SIZE             = 15;
48
49 constexpr int VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT = 8;
50 constexpr int DEFAULT_FONTS_REMAIN_COUNT             = 2;
51 } // namespace
52
53 namespace Text
54 {
55 namespace Internal
56 {
57 bool ValidateFontsPerScript::IsValidFont(FontId fontId) const
58 {
59   for(Vector<FontId>::ConstIterator it    = mValidFonts.Begin(),
60                                     endIt = mValidFonts.End();
61       it != endIt;
62       ++it)
63   {
64     if(fontId == *it)
65     {
66       return true;
67     }
68   }
69
70   return false;
71 }
72 void ValidateFontsPerScript::Cache(FontId fontId)
73 {
74   mValidFonts.PushBack(fontId);
75   if(MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE < mValidFonts.Count())
76   {
77     // Clear cache but remaind some last items.
78     const auto offset = mValidFonts.Count() - VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT;
79     for(int i = 0; i < VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT; ++i)
80     {
81       mValidFonts[i] = std::move(mValidFonts[offset + i]);
82     }
83     mValidFonts.Resize(VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT);
84   }
85 }
86
87 FontId DefaultFonts::FindFont(TextAbstraction::FontClient&            fontClient,
88                               const TextAbstraction::FontDescription& description,
89                               PointSize26Dot6                         size) const
90 {
91   for(std::vector<CacheItem>::const_iterator it    = mFonts.begin(),
92                                              endIt = mFonts.end();
93       it != endIt;
94       ++it)
95   {
96     const CacheItem& item = *it;
97
98     if(((TextAbstraction::FontWeight::NONE == description.weight) || (description.weight == item.description.weight)) &&
99        ((TextAbstraction::FontWidth::NONE == description.width) || (description.width == item.description.width)) &&
100        ((TextAbstraction::FontSlant::NONE == description.slant) || (description.slant == item.description.slant)) &&
101        (size == fontClient.GetPointSize(item.fontId)) &&
102        (description.family.empty() || (description.family == item.description.family)))
103     {
104       return item.fontId;
105     }
106   }
107
108   return 0u;
109 }
110
111 void DefaultFonts::Cache(const TextAbstraction::FontDescription& description, FontId fontId)
112 {
113   CacheItem item;
114   item.description = description;
115   item.fontId      = fontId;
116   mFonts.push_back(item);
117   if(MAX_DEFAULT_FONTS_CACHE_SIZE < mFonts.size())
118   {
119     // Clear cache but remaind some last items.
120     const auto offset = mFonts.size() - DEFAULT_FONTS_REMAIN_COUNT;
121     for(int i = 0; i < DEFAULT_FONTS_REMAIN_COUNT; ++i)
122     {
123       mFonts[i] = std::move(mFonts[offset + i]);
124     }
125     mFonts.resize(DEFAULT_FONTS_REMAIN_COUNT);
126   }
127 }
128
129 MultilanguageSupport::MultilanguageSupport()
130 : mDefaultFontPerScriptCache(),
131   mValidFontsPerScriptCache()
132 {
133   // Initializes the default font cache to zero (invalid font).
134   // Reserves space to cache the default fonts and access them with the script as an index.
135   mDefaultFontPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
136
137   // Initializes the valid fonts cache to NULL (no valid fonts).
138   // Reserves space to cache the valid fonts and access them with the script as an index.
139   mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
140 }
141
142 MultilanguageSupport::~MultilanguageSupport()
143 {
144   // Destroy the default font per script cache.
145   for(Vector<DefaultFonts*>::Iterator it    = mDefaultFontPerScriptCache.Begin(),
146                                       endIt = mDefaultFontPerScriptCache.End();
147       it != endIt;
148       ++it)
149   {
150     delete *it;
151   }
152
153   // Destroy the valid fonts per script cache.
154   for(Vector<ValidateFontsPerScript*>::Iterator it    = mValidFontsPerScriptCache.Begin(),
155                                                 endIt = mValidFontsPerScriptCache.End();
156       it != endIt;
157       ++it)
158   {
159     delete *it;
160   }
161 }
162
163 Text::MultilanguageSupport MultilanguageSupport::Get()
164 {
165   Text::MultilanguageSupport multilanguageSupportHandle;
166
167   SingletonService service(SingletonService::Get());
168   if(service)
169   {
170     // Check whether the singleton is already created
171     Dali::BaseHandle handle = service.GetSingleton(typeid(Text::MultilanguageSupport));
172     if(handle)
173     {
174       // If so, downcast the handle
175       MultilanguageSupport* impl = dynamic_cast<Internal::MultilanguageSupport*>(handle.GetObjectPtr());
176       multilanguageSupportHandle = Text::MultilanguageSupport(impl);
177     }
178     else // create and register the object
179     {
180       multilanguageSupportHandle = Text::MultilanguageSupport(new MultilanguageSupport);
181       service.Register(typeid(multilanguageSupportHandle), multilanguageSupportHandle);
182     }
183   }
184
185   return multilanguageSupportHandle;
186 }
187
188 void MultilanguageSupport::SetScripts(const Vector<Character>& text,
189                                       CharacterIndex           startIndex,
190                                       Length                   numberOfCharacters,
191                                       Vector<ScriptRun>&       scripts)
192 {
193   if(0u == numberOfCharacters)
194   {
195     // Nothing to do if there are no characters.
196     return;
197   }
198
199   // Find the first index where to insert the script.
200   ScriptRunIndex scriptIndex = 0u;
201   if(0u != startIndex)
202   {
203     for(Vector<ScriptRun>::ConstIterator it    = scripts.Begin(),
204                                          endIt = scripts.End();
205         it != endIt;
206         ++it, ++scriptIndex)
207     {
208       const ScriptRun& run = *it;
209       if(startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
210       {
211         // Run found.
212         break;
213       }
214     }
215   }
216
217   // Stores the current script run.
218   ScriptRun currentScriptRun;
219   currentScriptRun.characterRun.characterIndex     = startIndex;
220   currentScriptRun.characterRun.numberOfCharacters = 0u;
221   currentScriptRun.script                          = TextAbstraction::UNKNOWN;
222
223   // Reserve some space to reduce the number of reallocations.
224   scripts.Reserve(text.Count() << 2u);
225
226   // Whether the first valid script needs to be set.
227   bool isFirstScriptToBeSet = true;
228
229   // Whether the first valid script is a right to left script.
230   bool isParagraphRTL = false;
231
232   // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
233   Length numberOfAllScriptCharacters = 0u;
234
235   // Pointers to the text buffer.
236   const Character* const textBuffer = text.Begin();
237
238   // Initialize whether is right to left direction
239   currentScriptRun.isRightToLeft = false;
240
241   // Traverse all characters and set the scripts.
242   const Length lastCharacter = startIndex + numberOfCharacters - 1u;
243
244   for(Length index = startIndex; index <= lastCharacter; ++index)
245   {
246     Character character = *(textBuffer + index);
247
248     // Get the script of the character.
249     Script script = TextAbstraction::GetCharacterScript(character);
250
251     // Some characters (like white spaces) are valid for many scripts. The rules to set a script
252     // for them are:
253     // - If they are at the begining of a paragraph they get the script of the first character with
254     //   a defined script. If they are at the end, they get the script of the last one.
255     // - If they are between two scripts with the same direction, they get the script of the previous
256     //   character with a defined script. If the two scripts have different directions, they get the
257     //   script of the first character of the paragraph with a defined script.
258
259     // Skip those characters valid for many scripts like white spaces or '\n'.
260     bool endOfText = index > lastCharacter;
261
262     //Handle all Emoji Sequence cases
263     if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
264     {
265       AddCurrentScriptAndCreatNewScript(script,
266                                         false,
267                                         false,
268                                         currentScriptRun,
269                                         numberOfAllScriptCharacters,
270                                         scripts,
271                                         scriptIndex);
272     }
273     else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
274     {
275       currentScriptRun.script = script;
276     }
277     else if(IsOneOfEmojiScripts(currentScriptRun.script) && (TextAbstraction::COMMON == script))
278     {
279       // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
280       AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
281                                         false,
282                                         false,
283                                         currentScriptRun,
284                                         numberOfAllScriptCharacters,
285                                         scripts,
286                                         scriptIndex);
287     }
288
289     while(!endOfText &&
290           (TextAbstraction::COMMON == script))
291     {
292       // Check if whether is right to left markup and Keeps true if the previous value was true.
293       currentScriptRun.isRightToLeft = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftMark(character);
294
295       // Count all these characters to be added into a script.
296       ++numberOfAllScriptCharacters;
297
298       if(TextAbstraction::IsNewParagraph(character))
299       {
300         // The character is a new paragraph.
301         // To know when there is a new paragraph is needed because if there is a white space
302         // between two scripts with different directions, it is added to the script with
303         // the same direction than the first script of the paragraph.
304         isFirstScriptToBeSet = true;
305
306         AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
307                                           false,
308                                           false,
309                                           currentScriptRun,
310                                           numberOfAllScriptCharacters,
311                                           scripts,
312                                           scriptIndex);
313       }
314
315       // Get the next character.
316       ++index;
317       endOfText = index > lastCharacter;
318       if(!endOfText)
319       {
320         character = *(textBuffer + index);
321         script    = TextAbstraction::GetCharacterScript(character);
322
323         //Handle all Emoji Sequence cases
324         if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
325         {
326           AddCurrentScriptAndCreatNewScript(script,
327                                             false,
328                                             false,
329                                             currentScriptRun,
330                                             numberOfAllScriptCharacters,
331                                             scripts,
332                                             scriptIndex);
333         }
334         else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
335         {
336           currentScriptRun.script = script;
337         }
338       }
339     } // end while( !endOfText && ( TextAbstraction::COMMON == script ) )
340
341     if(endOfText)
342     {
343       // Last characters of the text are 'white spaces'.
344       // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
345       break;
346     }
347
348     // Check if it is the first character of a paragraph.
349     if(isFirstScriptToBeSet &&
350        (TextAbstraction::UNKNOWN != script) &&
351        (TextAbstraction::COMMON != script) &&
352        (TextAbstraction::EMOJI != script) &&
353        (TextAbstraction::EMOJI_TEXT != script) &&
354        (TextAbstraction::EMOJI_COLOR != script) &&
355        (!TextAbstraction::IsSymbolScript(script)))
356     {
357       // Sets the direction of the first valid script.
358       isParagraphRTL       = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftScript(script);
359       isFirstScriptToBeSet = false;
360     }
361
362     if((script != currentScriptRun.script) &&
363        (TextAbstraction::COMMON != script))
364     {
365       // Current run needs to be stored and a new one initialized.
366
367       if((isParagraphRTL == TextAbstraction::IsRightToLeftScript(currentScriptRun.script)) &&
368          (TextAbstraction::UNKNOWN != currentScriptRun.script))
369       {
370         // Previous script has the same direction than the first script of the paragraph.
371         // All the previously skipped characters need to be added to the previous script before it's stored.
372         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
373         numberOfAllScriptCharacters = 0u;
374       }
375       else if((TextAbstraction::IsRightToLeftScript(currentScriptRun.script) == TextAbstraction::IsRightToLeftScript(script)) &&
376               (TextAbstraction::UNKNOWN != currentScriptRun.script))
377       {
378         // Current script and previous one have the same direction.
379         // All the previously skipped characters need to be added to the previous script before it's stored.
380         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
381         numberOfAllScriptCharacters = 0u;
382       }
383       else if((TextAbstraction::UNKNOWN == currentScriptRun.script) &&
384               (TextAbstraction::IsSymbolOrEmojiOrTextScript(script)))
385       {
386         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
387         numberOfAllScriptCharacters = 0u;
388       }
389
390       // Adds the white spaces which are at the begining of the script.
391       numberOfAllScriptCharacters++;
392       AddCurrentScriptAndCreatNewScript(script,
393                                         TextAbstraction::IsRightToLeftScript(script),
394                                         true,
395                                         currentScriptRun,
396                                         numberOfAllScriptCharacters,
397                                         scripts,
398                                         scriptIndex);
399     }
400     else
401     {
402       if(TextAbstraction::UNKNOWN != currentScriptRun.script)
403       {
404         // Adds white spaces between characters.
405         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
406         numberOfAllScriptCharacters = 0u;
407       }
408
409       // Add one more character to the run.
410       ++currentScriptRun.characterRun.numberOfCharacters;
411     }
412   }
413
414   // Add remaining characters into the last script.
415   currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
416
417   if(0u != currentScriptRun.characterRun.numberOfCharacters)
418   {
419     // Store the last run.
420     scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
421     ++scriptIndex;
422   }
423
424   if(scriptIndex < scripts.Count())
425   {
426     // Update the indices of the next script runs.
427     const ScriptRun& run                = *(scripts.Begin() + scriptIndex - 1u);
428     CharacterIndex   nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
429
430     for(Vector<ScriptRun>::Iterator it    = scripts.Begin() + scriptIndex,
431                                     endIt = scripts.End();
432         it != endIt;
433         ++it)
434     {
435       ScriptRun& run                  = *it;
436       run.characterRun.characterIndex = nextCharacterIndex;
437       nextCharacterIndex += run.characterRun.numberOfCharacters;
438     }
439   }
440 }
441
442 void MultilanguageSupport::ValidateFonts(const Vector<Character>&                text,
443                                          const Vector<ScriptRun>&                scripts,
444                                          const Vector<FontDescriptionRun>&       fontDescriptions,
445                                          const TextAbstraction::FontDescription& defaultFontDescription,
446                                          TextAbstraction::PointSize26Dot6        defaultFontPointSize,
447                                          float                                   fontSizeScale,
448                                          CharacterIndex                          startIndex,
449                                          Length                                  numberOfCharacters,
450                                          Vector<FontRun>&                        fonts)
451 {
452   DALI_LOG_INFO(gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n");
453
454   if(0u == numberOfCharacters)
455   {
456     DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
457     // Nothing to do if there are no characters.
458     return;
459   }
460
461   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FONTS_VALIDATE");
462
463   // Find the first index where to insert the font run.
464   FontRunIndex fontIndex = 0u;
465   if(0u != startIndex)
466   {
467     for(Vector<FontRun>::ConstIterator it    = fonts.Begin(),
468                                        endIt = fonts.End();
469         it != endIt;
470         ++it, ++fontIndex)
471     {
472       const FontRun& run = *it;
473       if(startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
474       {
475         // Run found.
476         break;
477       }
478     }
479   }
480
481   // Traverse the characters and validate/set the fonts.
482
483   // Get the caches.
484   DefaultFonts**           defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
485   ValidateFontsPerScript** validFontsPerScriptCacheBuffer  = mValidFontsPerScriptCache.Begin();
486
487   // Stores the validated font runs.
488   fonts.Reserve(fontDescriptions.Count());
489
490   // Initializes a validated font run.
491   FontRun currentFontRun;
492   currentFontRun.characterRun.characterIndex     = startIndex;
493   currentFontRun.characterRun.numberOfCharacters = 0u;
494   currentFontRun.fontId                          = 0u;
495   currentFontRun.isBoldRequired                  = false;
496   currentFontRun.isItalicRequired                = false;
497
498   // Get the font client.
499   TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
500
501   const Character* const textBuffer = text.Begin();
502
503   // Iterators of the script runs.
504   Vector<ScriptRun>::ConstIterator scriptRunIt             = scripts.Begin();
505   Vector<ScriptRun>::ConstIterator scriptRunEndIt          = scripts.End();
506   bool                             isNewParagraphCharacter = false;
507
508   FontId                  previousEmojiFontId = 0u;
509   FontId                  currentFontId       = 0u;
510   FontId                  previousFontId      = 0u;
511   TextAbstraction::Script previousScript      = TextAbstraction::UNKNOWN;
512
513   CharacterIndex lastCharacter = startIndex + numberOfCharacters - 1u;
514   for(Length index = startIndex; index <= lastCharacter; ++index)
515   {
516     // Get the current character.
517     const Character character        = *(textBuffer + index);
518     bool            isItalicRequired = false;
519     bool            isBoldRequired   = false;
520
521     // new description for current character
522     TextAbstraction::FontDescription currentFontDescription;
523     TextAbstraction::PointSize26Dot6 currentFontPointSize = defaultFontPointSize;
524     bool                             isDefaultFont        = true;
525     MergeFontDescriptions(fontDescriptions,
526                           defaultFontDescription,
527                           defaultFontPointSize,
528                           fontSizeScale,
529                           index,
530                           currentFontDescription,
531                           currentFontPointSize,
532                           isDefaultFont);
533
534     // Get the font for the current character.
535     FontId fontId = fontClient.GetFontId(currentFontDescription, currentFontPointSize);
536     currentFontId = fontId;
537
538     // Get the script for the current character.
539     Script script = GetScript(index,
540                               scriptRunIt,
541                               scriptRunEndIt);
542
543 #ifdef DEBUG_ENABLED
544     if(gLogFilter->IsEnabledFor(Debug::Verbose))
545     {
546       Dali::TextAbstraction::FontDescription description;
547       fontClient.GetDescription(fontId, description);
548
549       DALI_LOG_INFO(gLogFilter,
550                     Debug::Verbose,
551                     "  Initial font set\n  Character : %x, Script : %s, Font : %s \n",
552                     character,
553                     Dali::TextAbstraction::ScriptName[script],
554                     description.path.c_str());
555     }
556 #endif
557
558     // Validate whether the current character is supported by the given font.
559     bool isValidFont = false;
560
561     // Check first in the cache of default fonts per script and size.
562
563     FontId        cachedDefaultFontId = 0u;
564     DefaultFonts* defaultFonts        = *(defaultFontPerScriptCacheBuffer + script);
565     if(NULL != defaultFonts)
566     {
567       // This cache stores fall-back fonts.
568       cachedDefaultFontId = defaultFonts->FindFont(fontClient,
569                                                    currentFontDescription,
570                                                    currentFontPointSize);
571     }
572
573     // Whether the cached default font is valid.
574     const bool isValidCachedDefaultFont = 0u != cachedDefaultFontId;
575
576     // The font is valid if it matches with the default one for the current script and size and it's different than zero.
577     isValidFont = isValidCachedDefaultFont && (fontId == cachedDefaultFontId);
578
579     if(isValidFont)
580     {
581       // Check if the font supports the character.
582       isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
583     }
584
585     bool isCommonScript = false;
586     bool isEmojiScript  = TextAbstraction::IsOneOfEmojiScripts(script);
587
588     if(isEmojiScript && (previousScript == script))
589     {
590       // Emoji sequence should use the previous emoji font.
591       if(0u != previousEmojiFontId)
592       {
593         fontId      = previousEmojiFontId;
594         isValidFont = true;
595       }
596     }
597
598     if(TextAbstraction::IsSpace(character) &&
599        TextAbstraction::HasLigatureMustBreak(script) &&
600        isValidCachedDefaultFont &&
601        (isDefaultFont || (currentFontId == previousFontId)))
602     {
603       fontId      = cachedDefaultFontId;
604       isValidFont = true;
605     }
606
607     // If the given font is not valid, it means either:
608     // - there is no cached font for the current script yet or,
609     // - the user has set a different font than the default one for the current script or,
610     // - the platform default font is different than the default font for the current script.
611
612     // Need to check if the given font supports the current character.
613     if(!isValidFont) // (1)
614     {
615       // Whether the current character is common for all scripts (i.e. white spaces, ...)
616
617       // Is not desirable to cache fonts for the common script.
618       //
619       // i.e. Consider the text " à¤¹à¤¿à¤‚दी", the 'white space' has assigned the DEVANAGARI script.
620       //      The user may have set a font or the platform's default is used.
621       //
622       //      As the 'white space' is the first character, no font is cached so the font validation
623       //      retrieves a glyph from the given font.
624       //
625       //      Many fonts support 'white spaces' so probably the font set by the user or the platform's default
626       //      supports the 'white space'. However, that font may not support the DEVANAGARI script.
627       isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
628
629       // Check in the valid fonts cache.
630       ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
631
632       if(NULL != validateFontsPerScript)
633       {
634         // This cache stores valid fonts set by the user.
635         isValidFont = validateFontsPerScript->IsValidFont(fontId);
636
637         // It may happen that a validated font for a script doesn't have all the glyphs for that script.
638         // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
639         if(isValidFont)
640         {
641           // Checks if the current character is supported by the font is needed.
642           isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
643         }
644       }
645
646       if(!isValidFont) // (2)
647       {
648         // The selected font is not stored in any cache.
649
650         // Checks if the current character is supported by the selected font.
651         isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
652
653         // If there is a valid font, cache it.
654         if(isValidFont && !isCommonScript)
655         {
656           if(NULL == validateFontsPerScript)
657           {
658             validateFontsPerScript = new ValidateFontsPerScript();
659
660             *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
661           }
662
663           validateFontsPerScript->Cache(fontId);
664         }
665
666         if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
667         {
668           // The selected font by the user or the platform's default font has failed to validate the character.
669
670           // Checks if the previously discarted cached default font supports the character.
671           bool isValidCachedFont = false;
672           if(isValidCachedDefaultFont)
673           {
674             isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
675           }
676
677           if(isValidCachedFont)
678           {
679             // Use the cached default font for the script if there is one.
680             fontId      = cachedDefaultFontId;
681             isValidFont = true;
682           }
683           else
684           {
685             // There is no valid cached default font for the script.
686
687             DefaultFonts* defaultFontsPerScript = NULL;
688
689             // Find a fallback-font.
690             fontId = fontClient.FindFallbackFont(character,
691                                                  currentFontDescription,
692                                                  currentFontPointSize,
693                                                  false);
694
695             if(0u == fontId)
696             {
697               fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
698             }
699
700             if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
701             {
702               // Cache the font if it is not an unknown script
703               if(NULL == defaultFontsPerScript)
704               {
705                 defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
706
707                 if(NULL == defaultFontsPerScript)
708                 {
709                   defaultFontsPerScript                       = new DefaultFonts();
710                   *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
711                 }
712               }
713               defaultFontsPerScript->Cache(currentFontDescription, fontId);
714               isValidFont = true;
715             }
716           }
717         } // !isValidFont (3)
718       }   // !isValidFont (2)
719     }     // !isValidFont (1)
720
721     if(isEmojiScript && (previousScript != script))
722     {
723       //New Emoji sequence should select font according to the variation selector (VS15 or VS16).
724       if(0u != currentFontRun.characterRun.numberOfCharacters)
725       {
726         // Store the font run.
727         fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
728         ++fontIndex;
729       }
730
731       // Initialize the new one.
732       currentFontRun.characterRun.characterIndex     = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
733       currentFontRun.characterRun.numberOfCharacters = 0u;
734       currentFontRun.fontId                          = fontId;
735       currentFontRun.isItalicRequired                = false;
736       currentFontRun.isBoldRequired                  = false;
737
738       if(TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script))
739       {
740         bool       isModifiedByVariationSelector = false;
741         GlyphIndex glyphIndexChar                = fontClient.GetGlyphIndex(fontId, character);
742         GlyphIndex glyphIndexCharByVS            = fontClient.GetGlyphIndex(fontId, character, Text::GetVariationSelectorByScript(script));
743
744         isModifiedByVariationSelector = glyphIndexChar != glyphIndexCharByVS;
745
746         if(isModifiedByVariationSelector)
747         {
748           FontId requestedFontId = fontClient.FindDefaultFont(character, currentFontPointSize, IsEmojiColorScript(script));
749           if(0u != requestedFontId)
750           {
751             currentFontRun.fontId = fontId = requestedFontId;
752             isValidFont                    = true;
753           }
754         }
755       }
756     }
757
758     // Store the font id when the first character is an emoji.
759     if(isEmojiScript)
760     {
761       if(0u != fontId && previousScript != script)
762       {
763         previousEmojiFontId = fontId;
764       }
765     }
766     else
767     {
768       previousEmojiFontId = 0u;
769     }
770
771 #ifdef DEBUG_ENABLED
772     if(gLogFilter->IsEnabledFor(Debug::Verbose))
773     {
774       Dali::TextAbstraction::FontDescription description;
775       fontClient.GetDescription(fontId, description);
776       DALI_LOG_INFO(gLogFilter,
777                     Debug::Verbose,
778                     "  Validated font set\n  Character : %x, Script : %s, Font : %s \n",
779                     character,
780                     Dali::TextAbstraction::ScriptName[script],
781                     description.path.c_str());
782     }
783 #endif
784     if(!isValidFont && !isCommonScript)
785     {
786       Dali::TextAbstraction::FontDescription descriptionForLog;
787       fontClient.GetDescription(fontId, descriptionForLog);
788       DALI_LOG_RELEASE_INFO("Validated font set fail : Character : %x, Script : %s, Font : %s \n",
789                             character,
790                             Dali::TextAbstraction::ScriptName[script],
791                             descriptionForLog.path.c_str());
792     }
793
794     // Whether bols style is required.
795     isBoldRequired = (currentFontDescription.weight >= TextAbstraction::FontWeight::BOLD);
796
797     // Whether italic style is required.
798     isItalicRequired = (currentFontDescription.slant >= TextAbstraction::FontSlant::ITALIC);
799
800     // The font is now validated.
801     if((fontId != currentFontRun.fontId) ||
802        isNewParagraphCharacter ||
803        // If font id is same as previous but style is diffrent, initialize new one
804        ((fontId == currentFontRun.fontId) && ((isBoldRequired != currentFontRun.isBoldRequired) || (isItalicRequired != currentFontRun.isItalicRequired))))
805     {
806       // Current run needs to be stored and a new one initialized.
807
808       if(0u != currentFontRun.characterRun.numberOfCharacters)
809       {
810         // Store the font run.
811         fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
812         ++fontIndex;
813       }
814
815       // Initialize the new one.
816       currentFontRun.characterRun.characterIndex     = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
817       currentFontRun.characterRun.numberOfCharacters = 0u;
818       currentFontRun.fontId                          = fontId;
819       currentFontRun.isBoldRequired                  = isBoldRequired;
820       currentFontRun.isItalicRequired                = isItalicRequired;
821     }
822
823     // Add one more character to the run.
824     ++currentFontRun.characterRun.numberOfCharacters;
825
826     // Whether the current character is a new paragraph character.
827     isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
828     previousScript          = script;
829     previousFontId          = currentFontId;
830   } // end traverse characters.
831
832   if(0u != currentFontRun.characterRun.numberOfCharacters)
833   {
834     // Store the last run.
835     fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
836     ++fontIndex;
837   }
838
839   if(fontIndex < fonts.Count())
840   {
841     // Update the indices of the next font runs.
842     const FontRun& run                = *(fonts.Begin() + fontIndex - 1u);
843     CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
844
845     for(Vector<FontRun>::Iterator it    = fonts.Begin() + fontIndex,
846                                   endIt = fonts.End();
847         it != endIt;
848         ++it)
849     {
850       FontRun& run = *it;
851
852       run.characterRun.characterIndex = nextCharacterIndex;
853       nextCharacterIndex += run.characterRun.numberOfCharacters;
854     }
855   }
856
857   DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
858 }
859
860 void MultilanguageSupport::AddCurrentScriptAndCreatNewScript(const Script       requestedScript,
861                                                              const bool         isRightToLeft,
862                                                              const bool         addScriptCharactersToNewScript,
863                                                              ScriptRun&         currentScriptRun,
864                                                              Length&            numberOfAllScriptCharacters,
865                                                              Vector<ScriptRun>& scripts,
866                                                              ScriptRunIndex&    scriptIndex)
867 {
868   // Add the pending characters to the current script
869   currentScriptRun.characterRun.numberOfCharacters += (addScriptCharactersToNewScript ? 0u : numberOfAllScriptCharacters);
870
871   // In-case the current script is empty then no need to add it for scripts
872   if(0u != currentScriptRun.characterRun.numberOfCharacters)
873   {
874     // Store the script run.
875     scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
876     ++scriptIndex;
877   }
878
879   // Initialize the new one by the requested script
880   currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
881   currentScriptRun.characterRun.numberOfCharacters = (addScriptCharactersToNewScript ? numberOfAllScriptCharacters : 0u);
882   currentScriptRun.script                          = requestedScript;
883   numberOfAllScriptCharacters                      = 0u;
884   // Initialize whether is right to left direction
885   currentScriptRun.isRightToLeft = isRightToLeft;
886 }
887
888 } // namespace Internal
889
890 } // namespace Text
891
892 } // namespace Toolkit
893
894 } // namespace Dali