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