[4.0] (AnimatedVectorImage) Ensure the progress is kept according to actions
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / multi-language-support-impl.cpp
1 /*
2  * Copyright (c) 2017 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/integration-api/debug.h>
23 #include <dali/devel-api/adaptor-framework/singleton-service.h>
24 #include <dali/devel-api/text-abstraction/font-client.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/internal/text/multi-language-helper-functions.h>
28 #include <dali-toolkit/devel-api/styling/style-manager-devel.h>
29
30 namespace Dali
31 {
32
33 namespace Toolkit
34 {
35
36 namespace
37 {
38 #if defined(DEBUG_ENABLED)
39 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
40 #endif
41
42 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
43
44 }
45
46 namespace Text
47 {
48
49 namespace Internal
50 {
51
52 bool ValidateFontsPerScript::IsValidFont( FontId fontId ) const
53 {
54   for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
55          endIt = mValidFonts.End();
56        it != endIt;
57        ++it )
58   {
59     if( fontId == *it )
60     {
61       return true;
62     }
63   }
64
65   return false;
66 }
67
68 FontId DefaultFonts::FindFont( TextAbstraction::FontClient& fontClient,
69                                const TextAbstraction::FontDescription& description,
70                                PointSize26Dot6 size ) const
71 {
72   for( std::vector<CacheItem>::const_iterator it = mFonts.begin(),
73          endIt = mFonts.end();
74        it != endIt;
75        ++it )
76   {
77     const CacheItem& item = *it;
78
79     if( ( ( TextAbstraction::FontWeight::NONE == description.weight ) || ( description.weight == item.description.weight ) ) &&
80         ( ( TextAbstraction::FontWidth::NONE == description.width )   || ( description.width == item.description.width ) ) &&
81         ( ( TextAbstraction::FontSlant::NONE == description.slant )   || ( description.slant == item.description.slant ) ) &&
82         ( size == fontClient.GetPointSize( item.fontId ) ) &&
83         ( description.family.empty() || ( description.family == item.description.family ) ) )
84     {
85       return item.fontId;
86     }
87   }
88
89   return 0u;
90 }
91
92 void DefaultFonts::Cache( const TextAbstraction::FontDescription& description, FontId fontId )
93 {
94   CacheItem item;
95   item.description = description;
96   item.fontId = fontId;
97   mFonts.push_back( item );
98 }
99
100 MultilanguageSupport::MultilanguageSupport()
101 : mDefaultFontPerScriptCache(),
102   mValidFontsPerScriptCache(),
103   mSlotDelegate( this )
104 {
105   // Initializes the default font cache to zero (invalid font).
106   // Reserves space to cache the default fonts and access them with the script as an index.
107   mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN + 1, NULL );
108
109   // Initializes the valid fonts cache to NULL (no valid fonts).
110   // Reserves space to cache the valid fonts and access them with the script as an index.
111   mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN + 1, NULL );
112
113   // Add custom font directory
114   Toolkit::StyleManager styleManager = Toolkit::StyleManager::Get();
115   if( styleManager )
116   {
117     std::string path;
118     Property::Map config = Toolkit::DevelStyleManager::GetConfigurations( styleManager );
119     if( config["customFontDirectory"].Get( path ) )
120     {
121       TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
122       fontClient.AddCustomFontDirectory( path.c_str() );
123     }
124   }
125
126   // Connect LanguageChangedSignal to clear caches when locale is changed.
127   Dali::Adaptor::Get().LanguageChangedSignal().Connect( mSlotDelegate, &Dali::Toolkit::Text::Internal::MultilanguageSupport::OnLanguageChanged );
128 }
129
130 void MultilanguageSupport::OnLanguageChanged( Dali::Adaptor& adaptor )
131 {
132   // When locale is changed, font configuration might provide a different font list.
133   // In this case, the original cached font might not be valid for same script.
134   // Therefore, we need to clear caches.
135   TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
136   fontClient.ClearCache();
137
138   mDefaultFontPerScriptCache.Clear();
139   mValidFontsPerScriptCache.Clear();
140   mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN + 1, NULL );
141   mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN + 1, NULL );
142 }
143
144 MultilanguageSupport::~MultilanguageSupport()
145 {
146   // Destroy the default font per script cache.
147   for( Vector<DefaultFonts*>::Iterator it = mDefaultFontPerScriptCache.Begin(),
148          endIt = mDefaultFontPerScriptCache.End();
149        it != endIt;
150        ++it )
151   {
152     delete *it;
153   }
154
155   // Destroy the valid fonts per script cache.
156   for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
157          endIt = mValidFontsPerScriptCache.End();
158        it != endIt;
159        ++it )
160   {
161     delete *it;
162   }
163 }
164
165 Text::MultilanguageSupport MultilanguageSupport::Get()
166 {
167   Text::MultilanguageSupport multilanguageSupportHandle;
168
169   SingletonService service( SingletonService::Get() );
170   if( service )
171   {
172     // Check whether the singleton is already created
173     Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
174     if( handle )
175     {
176       // If so, downcast the handle
177       MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
178       multilanguageSupportHandle = Text::MultilanguageSupport( impl );
179     }
180     else // create and register the object
181     {
182       multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
183       service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
184     }
185   }
186
187   return multilanguageSupportHandle;
188 }
189
190 void MultilanguageSupport::SetScripts( const Vector<Character>& text,
191                                        CharacterIndex startIndex,
192                                        Length numberOfCharacters,
193                                        Vector<ScriptRun>& scripts )
194 {
195   if( 0u == numberOfCharacters )
196   {
197     // Nothing to do if there are no characters.
198     return;
199   }
200
201   // Find the first index where to insert the script.
202   ScriptRunIndex scriptIndex = 0u;
203   if( 0u != startIndex )
204   {
205     for( Vector<ScriptRun>::ConstIterator it = scripts.Begin(),
206            endIt = scripts.End();
207          it != endIt;
208          ++it, ++scriptIndex )
209     {
210       const ScriptRun& run = *it;
211       if( startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters )
212       {
213         // Run found.
214         break;
215       }
216     }
217   }
218
219   // Stores the current script run.
220   ScriptRun currentScriptRun;
221   currentScriptRun.characterRun.characterIndex = startIndex;
222   currentScriptRun.characterRun.numberOfCharacters = 0u;
223   currentScriptRun.script = TextAbstraction::UNKNOWN;
224
225   // Reserve some space to reduce the number of reallocations.
226   scripts.Reserve( text.Count() << 2u );
227
228   // Whether the first valid script needs to be set.
229   bool isFirstScriptToBeSet = true;
230
231   // Whether the first valid script is a right to left script.
232   bool isParagraphRTL = false;
233
234   // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
235   Length numberOfAllScriptCharacters = 0u;
236
237   // Pointers to the text buffer.
238   const Character* const textBuffer = text.Begin();
239
240   // Traverse all characters and set the scripts.
241   const Length lastCharacter = startIndex + numberOfCharacters;
242   for( Length index = startIndex; index < lastCharacter; ++index )
243   {
244     Character character = *( textBuffer + index );
245
246     // Get the script of the character.
247     Script script = TextAbstraction::GetCharacterScript( character );
248
249     // Some characters (like white spaces) are valid for many scripts. The rules to set a script
250     // for them are:
251     // - If they are at the begining of a paragraph they get the script of the first character with
252     //   a defined script. If they are at the end, they get the script of the last one.
253     // - If they are between two scripts with the same direction, they get the script of the previous
254     //   character with a defined script. If the two scripts have different directions, they get the
255     //   script of the first character of the paragraph with a defined script.
256
257     // Skip those characters valid for many scripts like white spaces or '\n'.
258     bool endOfText = index == lastCharacter;
259     while( !endOfText &&
260            ( TextAbstraction::COMMON == script ) )
261     {
262       if( TextAbstraction::EMOJI == currentScriptRun.script )
263       {
264         // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
265         scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
266         ++scriptIndex;
267
268         // Initialize the new one.
269         currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
270         currentScriptRun.characterRun.numberOfCharacters = 0u;
271         currentScriptRun.script = TextAbstraction::UNKNOWN;
272         numberOfAllScriptCharacters = 0u;
273       }
274
275       // Count all these characters to be added into a script.
276       ++numberOfAllScriptCharacters;
277
278       if( TextAbstraction::IsNewParagraph( character ) )
279       {
280         // The character is a new paragraph.
281         // To know when there is a new paragraph is needed because if there is a white space
282         // between two scripts with different directions, it is added to the script with
283         // the same direction than the first script of the paragraph.
284         isFirstScriptToBeSet = true;
285
286         // Characters common to all scripts at the end of the paragraph are added to the last script.
287         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
288
289         // Store the script run.
290         scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
291         ++scriptIndex;
292
293         // Initialize the new one.
294         currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
295         currentScriptRun.characterRun.numberOfCharacters = 0u;
296         currentScriptRun.script = TextAbstraction::UNKNOWN;
297         numberOfAllScriptCharacters = 0u;
298       }
299
300       // Get the next character.
301       ++index;
302       endOfText = index == lastCharacter;
303       if( !endOfText )
304       {
305         character = *( textBuffer + index );
306         script = TextAbstraction::GetCharacterScript( character );
307       }
308     } // end while( !endOfText && ( TextAbstraction::COMMON == script ) )
309
310     if( endOfText )
311     {
312       // Last characters of the text are 'white spaces'.
313       // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
314       break;
315     }
316
317     // Check if it is the first character of a paragraph.
318     if( isFirstScriptToBeSet &&
319         ( TextAbstraction::UNKNOWN != script ) &&
320         ( TextAbstraction::COMMON != script ) &&
321         ( TextAbstraction::EMOJI != script ) )
322     {
323       // Sets the direction of the first valid script.
324       isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
325       isFirstScriptToBeSet = false;
326     }
327
328     if( ( script != currentScriptRun.script ) &&
329         ( TextAbstraction::COMMON != script ) )
330     {
331       // Current run needs to be stored and a new one initialized.
332
333       if( ( isParagraphRTL == TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) ) &&
334           ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
335       {
336         // Previous script has the same direction than the first script of the paragraph.
337         // All the previously skipped characters need to be added to the previous script before it's stored.
338         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
339         numberOfAllScriptCharacters = 0u;
340       }
341       else if( ( TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) == TextAbstraction::IsRightToLeftScript( script ) ) &&
342                ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
343       {
344         // Current script and previous one have the same direction.
345         // All the previously skipped characters need to be added to the previous script before it's stored.
346         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
347         numberOfAllScriptCharacters = 0u;
348       }
349       else if( ( TextAbstraction::UNKNOWN == currentScriptRun.script ) &&
350                ( TextAbstraction::EMOJI == script ) )
351       {
352         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
353         numberOfAllScriptCharacters = 0u;
354       }
355
356       if( 0u != currentScriptRun.characterRun.numberOfCharacters )
357       {
358         // Store the script run.
359         scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
360         ++scriptIndex;
361       }
362
363       // Initialize the new one.
364       currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
365       currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
366       currentScriptRun.script = script;
367       numberOfAllScriptCharacters = 0u;
368     }
369     else
370     {
371       if( TextAbstraction::UNKNOWN != currentScriptRun.script )
372       {
373         // Adds white spaces between characters.
374         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
375         numberOfAllScriptCharacters = 0u;
376       }
377
378       // Add one more character to the run.
379       ++currentScriptRun.characterRun.numberOfCharacters;
380     }
381   }
382
383   // Add remaining characters into the last script.
384   currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
385
386   if( 0u != currentScriptRun.characterRun.numberOfCharacters )
387   {
388     // Store the last run.
389     scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
390     ++scriptIndex;
391   }
392
393   if( scriptIndex < scripts.Count() )
394   {
395     // Update the indices of the next script runs.
396     const ScriptRun& run = *( scripts.Begin() + scriptIndex - 1u );
397     CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
398
399     for( Vector<ScriptRun>::Iterator it = scripts.Begin() + scriptIndex,
400            endIt = scripts.End();
401          it != endIt;
402          ++it )
403     {
404       ScriptRun& run = *it;
405       run.characterRun.characterIndex = nextCharacterIndex;
406       nextCharacterIndex += run.characterRun.numberOfCharacters;
407     }
408   }
409 }
410
411 void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
412                                           const Vector<ScriptRun>& scripts,
413                                           const Vector<FontDescriptionRun>& fontDescriptions,
414                                           const TextAbstraction::FontDescription& defaultFontDescription,
415                                           TextAbstraction::PointSize26Dot6 defaultFontPointSize,
416                                           CharacterIndex startIndex,
417                                           Length numberOfCharacters,
418                                           Vector<FontRun>& fonts )
419 {
420   DALI_LOG_INFO( gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n" );
421
422   if( 0u == numberOfCharacters )
423   {
424     DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
425     // Nothing to do if there are no characters.
426     return;
427   }
428
429   // Find the first index where to insert the font run.
430   FontRunIndex fontIndex = 0u;
431   if( 0u != startIndex )
432   {
433     for( Vector<FontRun>::ConstIterator it = fonts.Begin(),
434            endIt = fonts.End();
435          it != endIt;
436          ++it, ++fontIndex )
437     {
438       const FontRun& run = *it;
439       if( startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters )
440       {
441         // Run found.
442         break;
443       }
444     }
445   }
446
447   // Traverse the characters and validate/set the fonts.
448
449   // Get the caches.
450   DefaultFonts** defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
451   ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
452
453   // Stores the validated font runs.
454   fonts.Reserve( fontDescriptions.Count() );
455
456   // Initializes a validated font run.
457   FontRun currentFontRun;
458   currentFontRun.characterRun.characterIndex = startIndex;
459   currentFontRun.characterRun.numberOfCharacters = 0u;
460   currentFontRun.fontId = 0u;
461
462   // Get the font client.
463   TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
464
465   const Character* const textBuffer = text.Begin();
466
467   // Iterators of the script runs.
468   Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
469   Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
470   bool isNewParagraphCharacter = false;
471
472   FontId currentFontId = 0u;
473   FontId previousFontId = 0u;
474   bool isPreviousEmojiScript = false;
475
476   // Description of fallback font which is selected at current iteration.
477   TextAbstraction::FontDescription selectedFontDescription;
478
479   CharacterIndex lastCharacter = startIndex + numberOfCharacters;
480   for( Length index = startIndex; index < lastCharacter; ++index )
481   {
482     // Get the current character.
483     const Character character = *( textBuffer + index );
484     bool needSoftwareBoldening = false;
485     bool needSoftwareItalic = false;
486
487     // new description for current character
488     TextAbstraction::FontDescription currentFontDescription;
489     TextAbstraction::PointSize26Dot6 currentFontPointSize = defaultFontPointSize;
490     bool isDefaultFont = true;
491     MergeFontDescriptions( fontDescriptions,
492                            defaultFontDescription,
493                            defaultFontPointSize,
494                            index,
495                            currentFontDescription,
496                            currentFontPointSize,
497                            isDefaultFont );
498
499     // Get the font for the current character.
500     FontId fontId = fontClient.GetFontId( currentFontDescription, currentFontPointSize );
501     currentFontId = fontId;
502
503     // Get the script for the current character.
504     Script script = GetScript( index,
505                                scriptRunIt,
506                                scriptRunEndIt );
507
508 #ifdef DEBUG_ENABLED
509     {
510       Dali::TextAbstraction::FontDescription description;
511       fontClient.GetDescription( fontId, description );
512
513       DALI_LOG_INFO( gLogFilter,
514                      Debug::Verbose,
515                      "  Initial font set\n  Character : %x, Script : %s, Font : %s \n",
516                      character,
517                      Dali::TextAbstraction::ScriptName[script],
518                      description.path.c_str() );
519     }
520 #endif
521
522     // Validate whether the current character is supported by the given font.
523     bool isValidFont = false;
524
525     // Check first in the cache of default fonts per script and size.
526
527     FontId cachedDefaultFontId = 0u;
528     DefaultFonts* defaultFonts = *( defaultFontPerScriptCacheBuffer + script );
529     if( NULL != defaultFonts )
530     {
531       // This cache stores fall-back fonts.
532       cachedDefaultFontId = defaultFonts->FindFont( fontClient,
533                                                     currentFontDescription,
534                                                     currentFontPointSize );
535     }
536
537     // Whether the cached default font is valid.
538     const bool isValidCachedDefaultFont = 0u != cachedDefaultFontId;
539
540     // The font is valid if it matches with the default one for the current script and size and it's different than zero.
541     isValidFont = isValidCachedDefaultFont && ( fontId == cachedDefaultFontId );
542
543     if( isValidFont )
544     {
545       // Check if the font supports the character.
546       isValidFont = fontClient.IsCharacterSupportedByFont( fontId, character );
547     }
548
549     bool isCommonScript = false;
550     bool isEmojiScript = TextAbstraction::EMOJI == script;
551
552     if( isEmojiScript && !isPreviousEmojiScript )
553     {
554       if( 0u != currentFontRun.characterRun.numberOfCharacters )
555       {
556         // Store the font run.
557         fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
558         ++fontIndex;
559       }
560
561       // Initialize the new one.
562       currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
563       currentFontRun.characterRun.numberOfCharacters = 0u;
564       currentFontRun.fontId = fontId;
565     }
566
567     // If the given font is not valid, it means either:
568     // - there is no cached font for the current script yet or,
569     // - the user has set a different font than the default one for the current script or,
570     // - the platform default font is different than the default font for the current script.
571
572     // Need to check if the given font supports the current character.
573     if( !isValidFont ) // (1)
574     {
575       // Whether the current character is common for all scripts (i.e. white spaces, ...)
576
577       // Is not desirable to cache fonts for the common script.
578       //
579       // i.e. Consider the text " à¤¹à¤¿à¤‚दी", the 'white space' has assigned the DEVANAGARI script.
580       //      The user may have set a font or the platform's default is used.
581       //
582       //      As the 'white space' is the first character, no font is cached so the font validation
583       //      retrieves a glyph from the given font.
584       //
585       //      Many fonts support 'white spaces' so probably the font set by the user or the platform's default
586       //      supports the 'white space'. However, that font may not support the DEVANAGARI script.
587       isCommonScript = TextAbstraction::IsCommonScript( character );
588
589       if( isCommonScript )
590       {
591         if( isValidCachedDefaultFont &&
592             ( isDefaultFont || ( currentFontId == previousFontId ) ) &&
593             !isEmojiScript )
594         {
595           // At this point the character common for all scripts has no font assigned.
596           // If there is a valid previously cached default font for it, use that one.
597           fontId = cachedDefaultFontId;
598         }
599       }
600       else
601       {
602         // Check in the valid fonts cache.
603         ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
604
605         if( NULL != validateFontsPerScript )
606         {
607           // This cache stores valid fonts set by the user.
608           isValidFont = validateFontsPerScript->IsValidFont( fontId );
609
610           // It may happen that a validated font for a script doesn't have all the glyphs for that script.
611           // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
612           if( isValidFont )
613           {
614             // Checks if the current character is supported by the font is needed.
615             isValidFont = fontClient.IsCharacterSupportedByFont( fontId, character );
616           }
617         }
618
619         if( !isValidFont ) // (2)
620         {
621           // The selected font is not stored in any cache.
622
623           // Checks if the current character is supported by the selected font.
624           isValidFont = fontClient.IsCharacterSupportedByFont( fontId, character );
625
626           // Emojis are present in many monochrome fonts; prefer color by default.
627           if( isValidFont &&
628               isEmojiScript )
629           {
630             const GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
631
632             // For color emojis, the font is valid if the glyph is a color glyph (the bitmap is RGBA).
633             isValidFont = fontClient.IsColorGlyph( fontId, glyphIndex );
634           }
635
636           // If there is a valid font, cache it.
637           if( isValidFont )
638           {
639             if( NULL == validateFontsPerScript )
640             {
641               validateFontsPerScript = new ValidateFontsPerScript();
642
643               *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
644             }
645
646             validateFontsPerScript->mValidFonts.PushBack( fontId );
647           }
648
649           if( !isValidFont && ( fontId != cachedDefaultFontId ) ) // (3)
650           {
651             // The selected font by the user or the platform's default font has failed to validate the character.
652
653             // Checks if the previously discarted cached default font supports the character.
654             bool isValidCachedFont = false;
655             if( isValidCachedDefaultFont )
656             {
657               isValidCachedFont = fontClient.IsCharacterSupportedByFont( cachedDefaultFontId, character );
658             }
659
660             if( isValidCachedFont )
661             {
662               // Use the cached default font for the script if there is one.
663               fontId = cachedDefaultFontId;
664             }
665             else
666             {
667               // There is no valid cached default font for the script.
668
669               DefaultFonts* defaultFontsPerScript = NULL;
670
671               // Emojis are present in many monochrome fonts; prefer color by default.
672               const bool preferColor = ( TextAbstraction::EMOJI == script );
673
674               // Find a fallback-font.
675               fontId = fontClient.FindFallbackFont( character,
676                                                     currentFontDescription,
677                                                     currentFontPointSize,
678                                                     preferColor );
679
680               if( 0u == fontId )
681               {
682                 // If the system does not support a suitable font, fallback to Latin
683                 defaultFontsPerScript = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
684                 if( NULL != defaultFontsPerScript )
685                 {
686                   fontId = defaultFontsPerScript->FindFont( fontClient,
687                                                             currentFontDescription,
688                                                             currentFontPointSize );
689                 }
690               }
691
692               if( 0u == fontId )
693               {
694                 fontId = fontClient.FindDefaultFont( UTF32_A, currentFontPointSize );
695               }
696
697               if ( script != TextAbstraction::UNKNOWN )
698               {
699                 // Cache the font if it is not an unknown script
700                 if( NULL == defaultFontsPerScript )
701                 {
702                   defaultFontsPerScript = *( defaultFontPerScriptCacheBuffer + script );
703
704                   if( NULL == defaultFontsPerScript )
705                   {
706                     defaultFontsPerScript = new DefaultFonts();
707                     *( defaultFontPerScriptCacheBuffer + script ) = defaultFontsPerScript;
708                   }
709                 }
710                 defaultFontsPerScript->Cache( currentFontDescription, fontId );
711               }
712             }
713           } // !isValidFont (3)
714         } // !isValidFont (2)
715       } // !isCommonScript
716     } // !isValidFont (1)
717
718 #ifdef DEBUG_ENABLED
719     {
720       Dali::TextAbstraction::FontDescription description;
721       fontClient.GetDescription( fontId, description );
722       DALI_LOG_INFO( gLogFilter,
723                      Debug::Verbose,
724                      "  Validated font set\n  Character : %x, Script : %s, Font : %s \n",
725                      character,
726                      Dali::TextAbstraction::ScriptName[script],
727                      description.path.c_str() );
728     }
729 #endif
730
731     if( fontId != currentFontRun.fontId )
732     {
733       fontClient.GetDescription(fontId,selectedFontDescription);
734     }
735
736     // Developer sets bold to character but selected font cannot support it
737     needSoftwareBoldening = ( currentFontDescription.weight >= TextAbstraction::FontWeight::BOLD ) && ( selectedFontDescription.weight < TextAbstraction::FontWeight::BOLD );
738
739     // Developer sets italic to character but selected font cannot support it
740     needSoftwareItalic = ( currentFontDescription.slant == TextAbstraction::FontSlant::ITALIC ) && ( selectedFontDescription.slant < TextAbstraction::FontSlant::ITALIC );
741
742     // The font is now validated.
743     if( ( fontId != currentFontRun.fontId ) ||
744         isNewParagraphCharacter ||
745         // If font id is same as previous but style is diffrent, initialize new one
746         ( ( fontId == currentFontRun.fontId ) && ( ( needSoftwareBoldening != currentFontRun.softwareBold ) || ( needSoftwareItalic != currentFontRun.softwareItalic ) ) ) )
747     {
748       // Current run needs to be stored and a new one initialized.
749
750       if( 0u != currentFontRun.characterRun.numberOfCharacters )
751       {
752         // Store the font run.
753         fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
754         ++fontIndex;
755       }
756
757       // Initialize the new one.
758       currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
759       currentFontRun.characterRun.numberOfCharacters = 0u;
760       currentFontRun.fontId = fontId;
761       currentFontRun.softwareItalic = needSoftwareItalic;
762       currentFontRun.softwareBold = needSoftwareBoldening;
763     }
764
765     // Add one more character to the run.
766     ++currentFontRun.characterRun.numberOfCharacters;
767
768     // Whether the current character is a new paragraph character.
769     isNewParagraphCharacter = TextAbstraction::IsNewParagraph( character );
770     previousFontId = currentFontId;
771     isPreviousEmojiScript = isEmojiScript;
772   } // end traverse characters.
773
774   if( 0u != currentFontRun.characterRun.numberOfCharacters )
775   {
776     // Store the last run.
777     fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
778     ++fontIndex;
779   }
780
781   if( fontIndex < fonts.Count() )
782   {
783     // Update the indices of the next font runs.
784     const FontRun& run = *( fonts.Begin() + fontIndex - 1u );
785     CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
786
787     for( Vector<FontRun>::Iterator it = fonts.Begin() + fontIndex,
788            endIt = fonts.End();
789          it != endIt;
790          ++it )
791     {
792       FontRun& run = *it;
793
794       run.characterRun.characterIndex = nextCharacterIndex;
795       nextCharacterIndex += run.characterRun.numberOfCharacters;
796     }
797   }
798
799   DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
800 }
801
802 } // namespace Internal
803
804 } // namespace Text
805
806 } // namespace Toolkit
807
808 } // namespace Dali