Merge "[4.0] clear cache when locale is changed" into tizen_4.0
[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   CharacterIndex lastCharacter = startIndex + numberOfCharacters;
477   for( Length index = startIndex; index < lastCharacter; ++index )
478   {
479     // Get the current character.
480     const Character character = *( textBuffer + index );
481
482     TextAbstraction::FontDescription currentFontDescription;
483     TextAbstraction::PointSize26Dot6 currentFontPointSize = defaultFontPointSize;
484     bool isDefaultFont = true;
485     MergeFontDescriptions( fontDescriptions,
486                            defaultFontDescription,
487                            defaultFontPointSize,
488                            index,
489                            currentFontDescription,
490                            currentFontPointSize,
491                            isDefaultFont );
492
493     // Get the font for the current character.
494     FontId fontId = fontClient.GetFontId( currentFontDescription, currentFontPointSize );
495     currentFontId = fontId;
496
497     // Get the script for the current character.
498     Script script = GetScript( index,
499                                scriptRunIt,
500                                scriptRunEndIt );
501
502 #ifdef DEBUG_ENABLED
503     {
504       Dali::TextAbstraction::FontDescription description;
505       fontClient.GetDescription( fontId, description );
506
507       DALI_LOG_INFO( gLogFilter,
508                      Debug::Verbose,
509                      "  Initial font set\n  Character : %x, Script : %s, Font : %s \n",
510                      character,
511                      Dali::TextAbstraction::ScriptName[script],
512                      description.path.c_str() );
513     }
514 #endif
515
516     // Validate whether the current character is supported by the given font.
517     bool isValidFont = false;
518
519     // Check first in the cache of default fonts per script and size.
520
521     FontId cachedDefaultFontId = 0u;
522     DefaultFonts* defaultFonts = *( defaultFontPerScriptCacheBuffer + script );
523     if( NULL != defaultFonts )
524     {
525       // This cache stores fall-back fonts.
526       cachedDefaultFontId = defaultFonts->FindFont( fontClient,
527                                                     currentFontDescription,
528                                                     currentFontPointSize );
529     }
530
531     // Whether the cached default font is valid.
532     const bool isValidCachedDefaultFont = 0u != cachedDefaultFontId;
533
534     // The font is valid if it matches with the default one for the current script and size and it's different than zero.
535     isValidFont = isValidCachedDefaultFont && ( fontId == cachedDefaultFontId );
536
537     if( isValidFont )
538     {
539       // Check if the font supports the character.
540       isValidFont = fontClient.IsCharacterSupportedByFont( fontId, character );
541     }
542
543     bool isCommonScript = false;
544     bool isEmojiScript = TextAbstraction::EMOJI == script;
545
546     if( isEmojiScript && !isPreviousEmojiScript )
547     {
548       if( 0u != currentFontRun.characterRun.numberOfCharacters )
549       {
550         // Store the font run.
551         fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
552         ++fontIndex;
553       }
554
555       // Initialize the new one.
556       currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
557       currentFontRun.characterRun.numberOfCharacters = 0u;
558       currentFontRun.fontId = fontId;
559     }
560
561     // If the given font is not valid, it means either:
562     // - there is no cached font for the current script yet or,
563     // - the user has set a different font than the default one for the current script or,
564     // - the platform default font is different than the default font for the current script.
565
566     // Need to check if the given font supports the current character.
567     if( !isValidFont ) // (1)
568     {
569       // Whether the current character is common for all scripts (i.e. white spaces, ...)
570
571       // Is not desirable to cache fonts for the common script.
572       //
573       // i.e. Consider the text " à¤¹à¤¿à¤‚दी", the 'white space' has assigned the DEVANAGARI script.
574       //      The user may have set a font or the platform's default is used.
575       //
576       //      As the 'white space' is the first character, no font is cached so the font validation
577       //      retrieves a glyph from the given font.
578       //
579       //      Many fonts support 'white spaces' so probably the font set by the user or the platform's default
580       //      supports the 'white space'. However, that font may not support the DEVANAGARI script.
581       isCommonScript = TextAbstraction::IsCommonScript( character );
582
583       if( isCommonScript )
584       {
585         if( isValidCachedDefaultFont &&
586             ( isDefaultFont || ( currentFontId == previousFontId ) ) &&
587             !isEmojiScript )
588         {
589           // At this point the character common for all scripts has no font assigned.
590           // If there is a valid previously cached default font for it, use that one.
591           fontId = cachedDefaultFontId;
592         }
593       }
594       else
595       {
596         // Check in the valid fonts cache.
597         ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
598
599         if( NULL != validateFontsPerScript )
600         {
601           // This cache stores valid fonts set by the user.
602           isValidFont = validateFontsPerScript->IsValidFont( fontId );
603
604           // It may happen that a validated font for a script doesn't have all the glyphs for that script.
605           // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
606           if( isValidFont )
607           {
608             // Checks if the current character is supported by the font is needed.
609             isValidFont = fontClient.IsCharacterSupportedByFont( fontId, character );
610           }
611         }
612
613         if( !isValidFont ) // (2)
614         {
615           // The selected font is not stored in any cache.
616
617           // Checks if the current character is supported by the selected font.
618           isValidFont = fontClient.IsCharacterSupportedByFont( fontId, character );
619
620           // Emojis are present in many monochrome fonts; prefer color by default.
621           if( isValidFont &&
622               isEmojiScript )
623           {
624             const GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
625
626             // For color emojis, the font is valid if the glyph is a color glyph (the bitmap is RGBA).
627             isValidFont = fontClient.IsColorGlyph( fontId, glyphIndex );
628           }
629
630           // If there is a valid font, cache it.
631           if( isValidFont )
632           {
633             if( NULL == validateFontsPerScript )
634             {
635               validateFontsPerScript = new ValidateFontsPerScript();
636
637               *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
638             }
639
640             validateFontsPerScript->mValidFonts.PushBack( fontId );
641           }
642
643           if( !isValidFont && ( fontId != cachedDefaultFontId ) ) // (3)
644           {
645             // The selected font by the user or the platform's default font has failed to validate the character.
646
647             // Checks if the previously discarted cached default font supports the character.
648             bool isValidCachedFont = false;
649             if( isValidCachedDefaultFont )
650             {
651               isValidCachedFont = fontClient.IsCharacterSupportedByFont( cachedDefaultFontId, character );
652             }
653
654             if( isValidCachedFont )
655             {
656               // Use the cached default font for the script if there is one.
657               fontId = cachedDefaultFontId;
658             }
659             else
660             {
661               // There is no valid cached default font for the script.
662
663               DefaultFonts* defaultFontsPerScript = NULL;
664
665               // Emojis are present in many monochrome fonts; prefer color by default.
666               const bool preferColor = ( TextAbstraction::EMOJI == script );
667
668               // Find a fallback-font.
669               fontId = fontClient.FindFallbackFont( character,
670                                                     currentFontDescription,
671                                                     currentFontPointSize,
672                                                     preferColor );
673
674               if( 0u == fontId )
675               {
676                 // If the system does not support a suitable font, fallback to Latin
677                 defaultFontsPerScript = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
678                 if( NULL != defaultFontsPerScript )
679                 {
680                   fontId = defaultFontsPerScript->FindFont( fontClient,
681                                                             currentFontDescription,
682                                                             currentFontPointSize );
683                 }
684               }
685
686               if( 0u == fontId )
687               {
688                 fontId = fontClient.FindDefaultFont( UTF32_A, currentFontPointSize );
689               }
690
691               if ( script != TextAbstraction::UNKNOWN )
692               {
693                 // Cache the font if it is not an unknown script
694                 if( NULL == defaultFontsPerScript )
695                 {
696                   defaultFontsPerScript = *( defaultFontPerScriptCacheBuffer + script );
697
698                   if( NULL == defaultFontsPerScript )
699                   {
700                     defaultFontsPerScript = new DefaultFonts();
701                     *( defaultFontPerScriptCacheBuffer + script ) = defaultFontsPerScript;
702                   }
703                 }
704                 defaultFontsPerScript->Cache( currentFontDescription, fontId );
705               }
706             }
707           } // !isValidFont (3)
708         } // !isValidFont (2)
709       } // !isCommonScript
710     } // !isValidFont (1)
711
712 #ifdef DEBUG_ENABLED
713     {
714       Dali::TextAbstraction::FontDescription description;
715       fontClient.GetDescription( fontId, description );
716       DALI_LOG_INFO( gLogFilter,
717                      Debug::Verbose,
718                      "  Validated font set\n  Character : %x, Script : %s, Font : %s \n",
719                      character,
720                      Dali::TextAbstraction::ScriptName[script],
721                      description.path.c_str() );
722     }
723 #endif
724
725     // The font is now validated.
726     if( ( fontId != currentFontRun.fontId ) ||
727         isNewParagraphCharacter )
728     {
729       // Current run needs to be stored and a new one initialized.
730
731       if( 0u != currentFontRun.characterRun.numberOfCharacters )
732       {
733         // Store the font run.
734         fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
735         ++fontIndex;
736       }
737
738       // Initialize the new one.
739       currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
740       currentFontRun.characterRun.numberOfCharacters = 0u;
741       currentFontRun.fontId = fontId;
742     }
743
744     // Add one more character to the run.
745     ++currentFontRun.characterRun.numberOfCharacters;
746
747     // Whether the current character is a new paragraph character.
748     isNewParagraphCharacter = TextAbstraction::IsNewParagraph( character );
749     previousFontId = currentFontId;
750     isPreviousEmojiScript = isEmojiScript;
751   } // end traverse characters.
752
753   if( 0u != currentFontRun.characterRun.numberOfCharacters )
754   {
755     // Store the last run.
756     fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
757     ++fontIndex;
758   }
759
760   if( fontIndex < fonts.Count() )
761   {
762     // Update the indices of the next font runs.
763     const FontRun& run = *( fonts.Begin() + fontIndex - 1u );
764     CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
765
766     for( Vector<FontRun>::Iterator it = fonts.Begin() + fontIndex,
767            endIt = fonts.End();
768          it != endIt;
769          ++it )
770     {
771       FontRun& run = *it;
772
773       run.characterRun.characterIndex = nextCharacterIndex;
774       nextCharacterIndex += run.characterRun.numberOfCharacters;
775     }
776   }
777
778   DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
779 }
780
781 } // namespace Internal
782
783 } // namespace Text
784
785 } // namespace Toolkit
786
787 } // namespace Dali