bb3398173ced902e68ecbcd22a69e80b1c395d0c
[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 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::Concise, 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 MultilanguageSupport::MultilanguageSupport()
138 : mDefaultFontPerScriptCache(),
139   mValidFontsPerScriptCache()
140 {
141   // Initializes the default font cache to zero (invalid font).
142   // Reserves space to cache the default fonts and access them with the script as an index.
143   mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN, 0u );
144
145   // Initializes the valid fonts cache to NULL (no valid fonts).
146   // Reserves space to cache the valid fonts and access them with the script as an index.
147   mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN, NULL );
148 }
149
150 MultilanguageSupport::~MultilanguageSupport()
151 {
152   // Destroy the valid fonts per script cache.
153
154   for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
155          endIt = mValidFontsPerScriptCache.End();
156        it != endIt;
157        ++it )
158   {
159     delete *it;
160   }
161 }
162
163 Text::MultilanguageSupport MultilanguageSupport::Get()
164 {
165   Text::MultilanguageSupport multilanguageSupportHandle;
166
167   SingletonService service( SingletonService::Get() );
168   if( service )
169   {
170     // Check whether the singleton is already created
171     Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
172     if( handle )
173     {
174       // If so, downcast the handle
175       MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
176       multilanguageSupportHandle = Text::MultilanguageSupport( impl );
177     }
178     else // create and register the object
179     {
180       multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
181       service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
182     }
183   }
184
185   return multilanguageSupportHandle;
186 }
187
188 void MultilanguageSupport::SetScripts( const Vector<Character>& text,
189                                        Vector<ScriptRun>& scripts )
190 {
191   const Length numberOfCharacters = text.Count();
192
193   if( 0u == numberOfCharacters )
194   {
195     // Nothing to do if there are no characters.
196     return;
197   }
198
199   // Stores the current script run.
200   ScriptRun currentScriptRun;
201   currentScriptRun.characterRun.characterIndex = 0u;
202   currentScriptRun.characterRun.numberOfCharacters = 0u;
203   currentScriptRun.script = TextAbstraction::UNKNOWN;
204
205   // Reserve some space to reduce the number of reallocations.
206   scripts.Reserve( numberOfCharacters << 2u );
207
208   // Whether the first valid script needs to be set.
209   bool isFirstScriptToBeSet = true;
210
211   // Whether the first valid script is a right to left script.
212   bool isParagraphRTL = false;
213
214   // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
215   Length numberOfAllScriptCharacters = 0u;
216
217   // Pointers to the text and break info buffers.
218   const Character* const textBuffer = text.Begin();
219
220   // Traverse all characters and set the scripts.
221   for( Length index = 0u; index < numberOfCharacters; ++index )
222   {
223     Character character = *( textBuffer + index );
224
225     // Get the script of the character.
226     Script script = TextAbstraction::GetCharacterScript( character );
227
228     // Some characters (like white spaces) are valid for many scripts. The rules to set a script
229     // for them are:
230     // - If they are at the begining of a paragraph they get the script of the first character with
231     //   a defined script. If they are at the end, they get the script of the last one.
232     // - If they are between two scripts with the same direction, they get the script of the previous
233     //   character with a defined script. If the two scripts have different directions, they get the
234     //   script of the first character of the paragraph with a defined script.
235
236     // Skip those characters valid for many scripts like white spaces or '\n'.
237     bool endOfText = index == numberOfCharacters;
238     while( !endOfText &&
239            ( TextAbstraction::COMMON == script ) )
240     {
241       // Count all these characters to be added into a script.
242       ++numberOfAllScriptCharacters;
243
244       if( TextAbstraction::IsNewParagraph( character ) )
245       {
246         // The character is a new paragraph.
247         // To know when there is a new paragraph is needed because if there is a white space
248         // between two scripts with different directions, it is added to the script with
249         // the same direction than the first script of the paragraph.
250         isFirstScriptToBeSet = true;
251
252         // Characters common to all scripts at the end of the paragraph are added to the last script (if the last one is not unknown).
253         if( TextAbstraction::UNKNOWN != currentScriptRun.script )
254         {
255           currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
256           numberOfAllScriptCharacters = 0u;
257         }
258       }
259
260       // Get the next character.
261       ++index;
262       endOfText = index == numberOfCharacters;
263       if( !endOfText )
264       {
265         character = *( textBuffer + index );
266         script = TextAbstraction::GetCharacterScript( character );
267       }
268     }
269
270     if( endOfText )
271     {
272       // Last characters of the text are 'white spaces'.
273       // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
274       break;
275     }
276
277     // Check if it is the first character of a paragraph.
278     if( isFirstScriptToBeSet &&
279         ( TextAbstraction::UNKNOWN != script ) &&
280         ( TextAbstraction::COMMON != script ) )
281     {
282       // Sets the direction of the first valid script.
283       isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
284       isFirstScriptToBeSet = false;
285     }
286
287     if( ( script != currentScriptRun.script ) &&
288         ( TextAbstraction::COMMON != script ) )
289     {
290       // Current run needs to be stored and a new one initialized.
291
292       if( ( isParagraphRTL == TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) ) &&
293           ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
294       {
295         // Previous script has the same direction than the first script of the paragraph.
296         // All the previously skipped characters need to be added to the previous script before it's stored.
297         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
298         numberOfAllScriptCharacters = 0u;
299       }
300       else if( ( TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) == TextAbstraction::IsRightToLeftScript( script ) ) &&
301                ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
302       {
303         // Current script and previous one have the same direction.
304         // All the previously skipped characters need to be added to the previous script before it's stored.
305         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
306         numberOfAllScriptCharacters = 0u;
307       }
308
309       if( 0u != currentScriptRun.characterRun.numberOfCharacters )
310       {
311         // Store the script run.
312         scripts.PushBack( currentScriptRun );
313       }
314
315       // Initialize the new one.
316       currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
317       currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
318       currentScriptRun.script = script;
319       numberOfAllScriptCharacters = 0u;
320     }
321     else
322     {
323       if( TextAbstraction::UNKNOWN != currentScriptRun.script )
324       {
325         // Adds white spaces between characters.
326         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
327         numberOfAllScriptCharacters = 0u;
328       }
329
330       // Add one more character to the run.
331       ++currentScriptRun.characterRun.numberOfCharacters;
332     }
333   }
334
335   // Add remaining characters into the last script.
336   currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
337
338   DALI_ASSERT_DEBUG( ( 0u != currentScriptRun.characterRun.numberOfCharacters ) && "MultilanguageSupport::SetScripts() Trying to insert a script run with zero characters." );
339
340   if( TextAbstraction::UNKNOWN == currentScriptRun.script )
341   {
342     // There are only white spaces in the last script. Set the latin script.
343     currentScriptRun.script = TextAbstraction::LATIN;
344   }
345
346   // Store the last run.
347   scripts.PushBack( currentScriptRun );
348 }
349
350 void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
351                                           const Vector<ScriptRun>& scripts,
352                                           Vector<FontRun>& fonts )
353 {
354   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->MultilanguageSupport::ValidateFonts\n" );
355   const Length numberOfCharacters = text.Count();
356
357   if( 0u == numberOfCharacters )
358   {
359     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--MultilanguageSupport::ValidateFonts\n" );
360     // Nothing to do if there are no characters.
361     return;
362   }
363
364   // Copy the fonts set by application developers.
365   const Length numberOfFontRuns = fonts.Count();
366   const Vector<FontRun> userSetFonts = fonts;
367   fonts.Clear();
368
369   // Traverse the characters and validate/set the fonts.
370
371   // Get the caches.
372   FontId* defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
373   ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
374
375   // Stores the validated font runs.
376   fonts.Reserve( numberOfFontRuns );
377
378   // Initializes a validated font run.
379   FontRun currentFontRun;
380   currentFontRun.characterRun.characterIndex = 0u;
381   currentFontRun.characterRun.numberOfCharacters = 0u;
382   currentFontRun.fontId = 0u;
383   currentFontRun.isDefault = false;
384
385   // Get the font client.
386   TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
387
388   // Iterators of the font and script runs.
389   Vector<FontRun>::ConstIterator fontRunIt = userSetFonts.Begin();
390   Vector<FontRun>::ConstIterator fontRunEndIt = userSetFonts.End();
391   Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
392   Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
393
394   for( Length index = 0u; index < numberOfCharacters; ++index )
395   {
396     // Get the character.
397     const Character character = *( text.Begin() + index );
398
399     // Get the font for the character.
400     FontId fontId = GetFontId( index,
401                                fontRunIt,
402                                fontRunEndIt );
403
404     // Get the script for the character.
405     Script script = GetScript( index,
406                                scriptRunIt,
407                                scriptRunEndIt );
408
409 #ifdef DEBUG_ENABLED
410     {
411       Dali::TextAbstraction::FontDescription description;
412       fontClient.GetDescription( fontId, description );
413
414       DALI_LOG_INFO( gLogFilter,
415                      Debug::Verbose,
416                      "  Initial font set\n  Character : %x, Script : %s, Font : %s \n",
417                      character,
418                      Dali::TextAbstraction::ScriptName[script],
419                      description.path.c_str() );
420     }
421 #endif
422
423     if( TextAbstraction::UNKNOWN == script )
424     {
425       DALI_LOG_WARNING( "MultilanguageSupport::ValidateFonts. Unknown script!" );
426       script = TextAbstraction::LATIN;
427     }
428
429     // Whether the font being validated is a default one not set by the user.
430     const bool isDefault = ( 0u == fontId );
431
432     DALI_LOG_INFO( gLogFilter,
433                    Debug::Verbose,
434                    "  Is a default font : %s\n",
435                    ( isDefault ? "true" : "false" ) );
436
437     // The default font point size.
438     PointSize26Dot6 pointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
439
440     if( !isDefault )
441     {
442       // Validate if the font set by the user supports the character.
443
444       // Check first in the caches.
445
446       // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
447       if( fontId != *( defaultFontPerScriptCacheBuffer + script ) )
448       {
449         // Check in the valid fonts cache.
450         ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
451
452         if( NULL == validateFontsPerScript )
453         {
454           validateFontsPerScript = new ValidateFontsPerScript();
455
456           *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
457         }
458
459         if( NULL != validateFontsPerScript )
460         {
461           if( !validateFontsPerScript->FindValidFont( fontId ) )
462           {
463             // Use the font client to validate the font.
464             GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
465
466             // Emojis are present in many monochrome fonts; prefer color by default.
467             if( TextAbstraction::EMOJI == script &&
468                 0u != glyphIndex )
469             {
470               BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
471               if( bitmap &&
472                   Pixel::BGRA8888 != bitmap.GetPixelFormat() )
473               {
474                 glyphIndex = 0;
475               }
476             }
477
478             if( 0u == glyphIndex )
479             {
480               // Get the point size of the current font. It will be used to get a default font id.
481               pointSize = fontClient.GetPointSize( fontId );
482
483               // The font is not valid. Set to zero and a default one will be set.
484               fontId = 0u;
485             }
486             else
487             {
488               // Add the font to the valid font cache.
489
490               //   At this point the validated font supports the given character. However, characters
491               // common for all scripts, like white spaces or new paragraph characters, need to be
492               // processed differently.
493               //
494               //   i.e. A white space can have assigned a DEVANAGARI script but the font assigned may not
495               // support none of the DEVANAGARI glyphs. This font can't be added to the cache as a valid
496               // font for the DEVANAGARI script but the COMMON one.
497               if( TextAbstraction::IsCommonScript( character ) )
498               {
499                 validateFontsPerScript = *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON );
500
501                 if( NULL == validateFontsPerScript )
502                 {
503                   validateFontsPerScript = new ValidateFontsPerScript();
504
505                   *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON ) = validateFontsPerScript;
506                 }
507               }
508
509               validateFontsPerScript->mValidFonts.PushBack( fontId );
510             }
511           }
512         }
513       }
514     } // !isDefault
515
516     // The font has not been validated. Find a default one.
517     if( 0u == fontId )
518     {
519       // The character has no font assigned. Get a default one from the cache
520       fontId = *( defaultFontPerScriptCacheBuffer + script );
521
522       // If the cache has not a default font, get one from the font client.
523       if( 0u == fontId )
524       {
525         // Emojis are present in many monochrome fonts; prefer color by default.
526         bool preferColor = ( TextAbstraction::EMOJI == script );
527
528         // Find a default font.
529         fontId = fontClient.FindDefaultFont( character, pointSize, preferColor );
530
531         // If the system does not support a suitable font, fallback to Latin
532         if( 0u == fontId )
533         {
534           fontId = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
535         }
536         if( 0u == fontId )
537         {
538           fontId = fontClient.FindDefaultFont( UTF32_A, pointSize );
539         }
540
541         // Cache the font.
542         *( defaultFontPerScriptCacheBuffer + script ) = fontId;
543       }
544     }
545
546 #ifdef DEBUG_ENABLED
547     {
548       Dali::TextAbstraction::FontDescription description;
549       fontClient.GetDescription( fontId, description );
550       DALI_LOG_INFO( gLogFilter,
551                      Debug::Verbose,
552                      "  Validated font set\n  Character : %x, Script : %s, Font : %s \n",
553                      character,
554                      Dali::TextAbstraction::ScriptName[script],
555                      description.path.c_str() );
556     }
557 #endif
558
559     // The font is now validated.
560
561     if( ( fontId != currentFontRun.fontId ) ||
562         ( isDefault != currentFontRun.isDefault ) )
563     {
564       // Current run needs to be stored and a new one initialized.
565
566       if( 0u != currentFontRun.characterRun.numberOfCharacters )
567       {
568         // Store the font run.
569         fonts.PushBack( currentFontRun );
570       }
571
572       // Initialize the new one.
573       currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
574       currentFontRun.characterRun.numberOfCharacters = 0u;
575       currentFontRun.fontId = fontId;
576       currentFontRun.isDefault = isDefault;
577     }
578
579     // Add one more character to the run.
580     ++currentFontRun.characterRun.numberOfCharacters;
581   }
582
583   if( 0u != currentFontRun.characterRun.numberOfCharacters )
584   {
585     // Store the last run.
586     fonts.PushBack( currentFontRun );
587   }
588   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--MultilanguageSupport::ValidateFonts\n" );
589 }
590
591 } // namespace Internal
592
593 } // namespace Text
594
595 } // namespace Toolkit
596
597 } // namespace Dali