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