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