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