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