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