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