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