[dali_1.1.29] Merge branch 'devel/master'
[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::FindValidFont( 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 and break info buffers.
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   MergeFontDescriptions( fontDescriptions,
419                          fontIds,
420                          defaultFontDescription,
421                          defaultPointSize,
422                          startIndex,
423                          numberOfCharacters );
424
425   const Character* const textBuffer = text.Begin();
426   const FontId* const fontIdsBuffer = fontIds.Begin();
427   Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
428   Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
429   bool isNewParagraphCharacter = false;
430
431   PointSize26Dot6 currentPointSize = defaultPointSize;
432   FontId currentFontId = 0u;
433
434   CharacterIndex lastCharacter = startIndex + numberOfCharacters;
435   for( Length index = startIndex; index < lastCharacter; ++index )
436   {
437     // Get the character.
438     const Character character = *( textBuffer + index );
439
440     // Get the font for the character.
441     FontId fontId = *( fontIdsBuffer + index - startIndex );
442
443     // Get the script for the character.
444     Script script = GetScript( index,
445                                scriptRunIt,
446                                scriptRunEndIt );
447
448     // Get the current point size.
449     if( currentFontId != fontId )
450     {
451       currentPointSize = fontClient.GetPointSize( fontId );
452       currentFontId = fontId;
453     }
454
455 #ifdef DEBUG_ENABLED
456     {
457       Dali::TextAbstraction::FontDescription description;
458       fontClient.GetDescription( fontId, description );
459
460       DALI_LOG_INFO( gLogFilter,
461                      Debug::Verbose,
462                      "  Initial font set\n  Character : %x, Script : %s, Font : %s \n",
463                      character,
464                      Dali::TextAbstraction::ScriptName[script],
465                      description.path.c_str() );
466     }
467 #endif
468
469     // Whether the font being validated is a default one not set by the user.
470     FontId preferredFont = fontId;
471
472     // Validate if the font set by the user supports the character.
473
474     // Check first in the caches.
475
476     DefaultFonts* defaultFonts = *( defaultFontPerScriptCacheBuffer + script );
477     FontId cachedDefaultFontId = 0u;
478     if( NULL != defaultFonts )
479     {
480       cachedDefaultFontId = defaultFonts->FindFont( fontClient, currentPointSize );
481     }
482
483     // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
484     if( fontId != cachedDefaultFontId )
485     {
486       // Check in the valid fonts cache.
487       ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
488
489       if( NULL == validateFontsPerScript )
490       {
491         validateFontsPerScript = new ValidateFontsPerScript();
492
493         *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
494       }
495
496       if( NULL != validateFontsPerScript )
497       {
498         if( !validateFontsPerScript->FindValidFont( fontId ) )
499         {
500           // Use the font client to validate the font.
501           GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
502
503           // Emojis are present in many monochrome fonts; prefer color by default.
504           if( ( TextAbstraction::EMOJI == script ) &&
505               ( 0u != glyphIndex ) )
506           {
507             BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
508             if( bitmap &&
509                 ( Pixel::BGRA8888 != bitmap.GetPixelFormat() ) )
510             {
511               glyphIndex = 0u;
512             }
513           }
514
515           if( 0u == glyphIndex )
516           {
517             // The font is not valid. Set to zero and a default one will be set.
518             fontId = 0u;
519           }
520           else
521           {
522             // Add the font to the valid font cache.
523
524             //   At this point the validated font supports the given character. However, characters
525             // common for all scripts, like white spaces or new paragraph characters, need to be
526             // processed differently.
527             //
528             //   i.e. A white space can have assigned a DEVANAGARI script but the font assigned may not
529             // support none of the DEVANAGARI glyphs. This font can't be added to the cache as a valid
530             // font for the DEVANAGARI script but the COMMON one.
531             if( TextAbstraction::IsCommonScript( character ) )
532             {
533               validateFontsPerScript = *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON );
534
535               if( NULL == validateFontsPerScript )
536               {
537                 validateFontsPerScript = new ValidateFontsPerScript();
538
539                 *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON ) = validateFontsPerScript;
540               }
541             }
542
543             validateFontsPerScript->mValidFonts.PushBack( fontId );
544           }
545         }
546       }
547     }
548
549     // The font has not been validated. Find a default one.
550     if( 0u == fontId )
551     {
552       // The character has no font assigned. Get a default one from the cache
553       fontId = cachedDefaultFontId;
554
555       // If the cache has not a default font, get one from the font client.
556       if( 0u == fontId )
557       {
558         // Emojis are present in many monochrome fonts; prefer color by default.
559         bool preferColor = ( TextAbstraction::EMOJI == script );
560
561         // Find a fallback-font.
562         fontId = fontClient.FindFallbackFont( preferredFont, character, currentPointSize, preferColor );
563
564         // If the system does not support a suitable font, fallback to Latin
565         DefaultFonts* latinDefaults = NULL;
566         if( 0u == fontId )
567         {
568           latinDefaults = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
569           if( NULL != latinDefaults )
570           {
571             fontId = latinDefaults->FindFont( fontClient, currentPointSize );
572           }
573         }
574
575         if( 0u == fontId )
576         {
577           fontId = fontClient.FindDefaultFont( UTF32_A, currentPointSize );
578         }
579
580         // Cache the font.
581         if( NULL == latinDefaults )
582         {
583           latinDefaults = new DefaultFonts();
584           *( defaultFontPerScriptCacheBuffer + script ) = latinDefaults;
585         }
586         latinDefaults->mFonts.PushBack( fontId );
587       }
588     }
589
590 #ifdef DEBUG_ENABLED
591     {
592       Dali::TextAbstraction::FontDescription description;
593       fontClient.GetDescription( fontId, description );
594       DALI_LOG_INFO( gLogFilter,
595                      Debug::Verbose,
596                      "  Validated font set\n  Character : %x, Script : %s, Font : %s \n",
597                      character,
598                      Dali::TextAbstraction::ScriptName[script],
599                      description.path.c_str() );
600     }
601 #endif
602
603     // The font is now validated.
604     if( ( fontId != currentFontRun.fontId ) ||
605         isNewParagraphCharacter )
606     {
607       // Current run needs to be stored and a new one initialized.
608
609       if( 0u != currentFontRun.characterRun.numberOfCharacters )
610       {
611         // Store the font run.
612         fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
613         ++fontIndex;
614       }
615
616       // Initialize the new one.
617       currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
618       currentFontRun.characterRun.numberOfCharacters = 0u;
619       currentFontRun.fontId = fontId;
620     }
621
622     // Add one more character to the run.
623     ++currentFontRun.characterRun.numberOfCharacters;
624
625     // Whether the current character is a new paragraph character.
626     isNewParagraphCharacter = TextAbstraction::IsNewParagraph( character );
627   }
628
629   if( 0u != currentFontRun.characterRun.numberOfCharacters )
630   {
631     // Store the last run.
632     fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
633     ++fontIndex;
634   }
635
636   if( fontIndex < fonts.Count() )
637   {
638     // Update the indices of the next font runs.
639     const FontRun& run = *( fonts.Begin() + fontIndex - 1u );
640     CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
641
642     for( Vector<FontRun>::Iterator it = fonts.Begin() + fontIndex,
643            endIt = fonts.End();
644          it != endIt;
645          ++it )
646     {
647       FontRun& run = *it;
648
649       run.characterRun.characterIndex = nextCharacterIndex;
650       nextCharacterIndex += run.characterRun.numberOfCharacters;
651     }
652   }
653
654   DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
655 }
656
657 } // namespace Internal
658
659 } // namespace Text
660
661 } // namespace Toolkit
662
663 } // namespace Dali