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