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