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