2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/text/multi-language-support-impl.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>
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>
42 #if defined(DEBUG_ENABLED)
43 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_MULTI_LANGUAGE_SUPPORT");
46 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
56 * @brief Retrieves the font Id from the font run for a given character's @p index.
58 * If the character's index exceeds the current font run it increases the iterator to get the next one.
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.
64 * @return The font id.
66 FontId GetFontId( Length index,
67 Vector<FontRun>::ConstIterator& fontRunIt,
68 const Vector<FontRun>::ConstIterator& fontRunEndIt )
72 if( fontRunIt != fontRunEndIt )
74 const FontRun& fontRun = *fontRunIt;
76 if( ( index >= fontRun.characterRun.characterIndex ) &&
77 ( index < fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters ) )
79 fontId = fontRun.fontId;
82 if( index + 1u == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters )
84 // All the characters of the current run have been traversed. Get the next one for the next iteration.
93 * @brief Retrieves the script Id from the script run for a given character's @p index.
95 * If the character's index exceeds the current script run it increases the iterator to get the next one.
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.
101 * @return The script.
103 Script GetScript( Length index,
104 Vector<ScriptRun>::ConstIterator& scriptRunIt,
105 const Vector<ScriptRun>::ConstIterator& scriptRunEndIt )
107 Script script = TextAbstraction::UNKNOWN;
109 if( scriptRunIt != scriptRunEndIt )
111 const ScriptRun& scriptRun = *scriptRunIt;
113 if( ( index >= scriptRun.characterRun.characterIndex ) &&
114 ( index < scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters ) )
116 script = scriptRun.script;
119 if( index + 1u == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters )
121 // All the characters of the current run have been traversed. Get the next one for the next iteration.
129 bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
131 for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
132 endIt = mValidFonts.End();
145 MultilanguageSupport::MultilanguageSupport()
146 : mDefaultFontPerScriptCache(),
147 mValidFontsPerScriptCache()
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 );
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 );
158 MultilanguageSupport::~MultilanguageSupport()
160 // Destroy the valid fonts per script cache.
162 for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
163 endIt = mValidFontsPerScriptCache.End();
171 Text::MultilanguageSupport MultilanguageSupport::Get()
173 Text::MultilanguageSupport multilanguageSupportHandle;
175 SingletonService service( SingletonService::Get() );
178 // Check whether the singleton is already created
179 Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
182 // If so, downcast the handle
183 MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
184 multilanguageSupportHandle = Text::MultilanguageSupport( impl );
186 else // create and register the object
188 multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
189 service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
193 return multilanguageSupportHandle;
196 void MultilanguageSupport::SetScripts( const Vector<Character>& text,
197 const Vector<LineBreakInfo>& lineBreakInfo,
198 Vector<ScriptRun>& scripts )
200 const Length numberOfCharacters = text.Count();
202 if( 0u == numberOfCharacters )
204 // Nothing to do if there are no characters.
208 // Stores the current script run.
209 ScriptRun currentScriptRun;
210 currentScriptRun.characterRun.characterIndex = 0u;
211 currentScriptRun.characterRun.numberOfCharacters = 0u;
212 currentScriptRun.script = TextAbstraction::UNKNOWN;
214 // Reserve some space to reduce the number of reallocations.
215 scripts.Reserve( numberOfCharacters << 2u );
217 // Whether the first valid script need to be set.
218 bool firstValidScript = true;
220 // Whether the first valid script is a right to left script.
221 bool isParagraphRTL = false;
223 // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
224 Length numberOfAllScriptCharacters = 0u;
226 // Pointers to the text and break info buffers.
227 const Character* textBuffer = text.Begin();
228 const LineBreakInfo* breakInfoBuffer = lineBreakInfo.Begin();
230 // Traverse all characters and set the scripts.
231 for( Length index = 0u; index < numberOfCharacters; ++index )
233 Character character = *( textBuffer + index );
234 LineBreakInfo breakInfo = *( breakInfoBuffer + index );
236 // Some characters (like white spaces) are valid for many scripts. The rules to set a script
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.
244 // Skip those characters valid for many scripts like white spaces or '\n'.
245 bool endOfText = index == numberOfCharacters;
247 TextAbstraction::IsCommonScript( character ) )
249 // Count all these characters to be added into a script.
250 ++numberOfAllScriptCharacters;
252 if( TextAbstraction::LINE_MUST_BREAK == breakInfo )
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;
262 // Get the next character.
264 endOfText = index == numberOfCharacters;
267 character = *( textBuffer + index );
268 breakInfo = *( breakInfoBuffer + index );
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.
279 // Get the script of the character.
280 Script script = TextAbstraction::GetCharacterScript( character );
282 // Check if it is the first character of a paragraph.
283 if( firstValidScript &&
284 ( TextAbstraction::UNKNOWN != script ) )
286 // Sets the direction of the first valid script.
287 isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
288 firstValidScript = false;
291 if( script != currentScriptRun.script )
293 // Current run needs to be stored and a new one initialized.
295 if( isParagraphRTL != TextAbstraction::IsRightToLeftScript( script ) )
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;
303 if( 0u != currentScriptRun.characterRun.numberOfCharacters )
305 // Store the script run.
306 scripts.PushBack( currentScriptRun );
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;
317 // Adds white spaces between characters.
318 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
319 numberOfAllScriptCharacters = 0u;
322 if( TextAbstraction::LINE_MUST_BREAK == breakInfo )
324 // The next character is a new paragraph.
325 firstValidScript = true;
326 isParagraphRTL = false;
329 // Add one more character to the run.
330 ++currentScriptRun.characterRun.numberOfCharacters;
333 // Add remaining characters into the last script.
334 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
335 if( 0u != currentScriptRun.characterRun.numberOfCharacters )
337 if( TextAbstraction::UNKNOWN == currentScriptRun.script )
339 // There are only white spaces in the last script. Set the latin script.
340 currentScriptRun.script = TextAbstraction::LATIN;
343 // Store the last run.
344 scripts.PushBack( currentScriptRun );
348 void MultilanguageSupport::ReplaceScripts( LogicalModel& model,
349 CharacterIndex characterIndex,
350 Length numberOfCharactersToRemove,
351 Length numberOfCharactersToInsert )
355 void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
356 const Vector<ScriptRun>& scripts,
357 Vector<FontRun>& fonts )
359 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->MultilanguageSupport::ValidateFonts\n" );
360 const Length numberOfCharacters = text.Count();
362 if( 0u == numberOfCharacters )
364 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--MultilanguageSupport::ValidateFonts\n" );
365 // Nothing to do if there are no characters.
369 // Copy the fonts set by application developers.
370 const Length numberOfFontRuns = fonts.Count();
371 const Vector<FontRun> definedFonts = fonts;
374 // Traverse the characters and validate/set the fonts.
377 FontId* defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
378 ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
380 // Stores the validated font runs.
381 fonts.Reserve( numberOfFontRuns );
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;
390 // Get the font client.
391 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
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();
399 for( Length index = 0u; index < numberOfCharacters; ++index )
401 // Get the character.
402 const Character character = *( text.Begin() + index );
404 // Get the font for the character.
405 FontId fontId = GetFontId( index,
409 // Get the script for the character.
410 Script script = GetScript( index,
416 Dali::TextAbstraction::FontDescription description;
417 fontClient.GetDescription( fontId, description );
419 DALI_LOG_INFO( gLogFilter,
421 " Initial font set\n Character : %x, Script : %s, Font : %s \n",
423 Dali::TextAbstraction::ScriptName[script],
424 description.path.c_str() );
428 if( TextAbstraction::UNKNOWN == script )
430 DALI_LOG_WARNING( "MultilanguageSupport::ValidateFonts. Unknown script!" );
431 script = TextAbstraction::LATIN;
434 // Whether the font being validated is a default one not set by the user.
435 const bool isDefault = ( 0u == fontId );
437 DALI_LOG_INFO( gLogFilter,
439 " Is a default font : %s\n",
440 ( isDefault ? "true" : "false" ) );
442 // The default font point size.
443 PointSize26Dot6 pointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
447 // Validate if the font set by the user supports the character.
449 // Check first in the caches.
451 // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
452 if( fontId != *( defaultFontPerScriptCacheBuffer + script ) )
454 // Check in the valid fonts cache.
455 ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
457 if( NULL == validateFontsPerScript )
459 validateFontsPerScript = new ValidateFontsPerScript();
461 *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
464 if( NULL != validateFontsPerScript )
466 if( !validateFontsPerScript->FindValidFont( fontId ) )
468 // Use the font client to validate the font.
469 GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
471 // Emojis are present in many monochrome fonts; prefer color by default.
472 if( TextAbstraction::EMOJI == script &&
475 BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
477 Pixel::BGRA8888 != bitmap.GetPixelFormat() )
483 if( 0u == glyphIndex )
485 // Get the point size of the current font. It will be used to get a default font id.
486 pointSize = fontClient.GetPointSize( fontId );
488 // The font is not valid. Set to zero and a default one will be set.
493 // Add the font to the valid font cache.
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.
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 ) )
504 validateFontsPerScript = *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON );
506 if( NULL == validateFontsPerScript )
508 validateFontsPerScript = new ValidateFontsPerScript();
510 *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON ) = validateFontsPerScript;
514 validateFontsPerScript->mValidFonts.PushBack( fontId );
521 // The font has not been validated. Find a default one.
524 // The character has no font assigned. Get a default one from the cache
525 fontId = *( defaultFontPerScriptCacheBuffer + script );
527 // If the cache has not a default font, get one from the font client.
530 // Emojis are present in many monochrome fonts; prefer color by default.
531 bool preferColor = ( TextAbstraction::EMOJI == script );
533 // Find a default font.
534 fontId = fontClient.FindDefaultFont( character, pointSize, preferColor );
536 // If the system does not support a suitable font, fallback to Latin
539 fontId = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
543 fontId = fontClient.FindDefaultFont( UTF32_A, pointSize );
547 *( defaultFontPerScriptCacheBuffer + script ) = fontId;
553 Dali::TextAbstraction::FontDescription description;
554 fontClient.GetDescription( fontId, description );
555 DALI_LOG_INFO( gLogFilter,
557 " Validated font set\n Character : %x, Script : %s, Font : %s \n",
559 Dali::TextAbstraction::ScriptName[script],
560 description.path.c_str() );
564 // The font is now validated.
566 if( ( fontId != currentFontRun.fontId ) ||
567 ( isDefault != currentFontRun.isDefault ) )
569 // Current run needs to be stored and a new one initialized.
571 if( 0u != currentFontRun.characterRun.numberOfCharacters )
573 // Store the font run.
574 fonts.PushBack( currentFontRun );
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;
584 // Add one more character to the run.
585 ++currentFontRun.characterRun.numberOfCharacters;
588 if( 0u != currentFontRun.characterRun.numberOfCharacters )
590 // Store the last run.
591 fonts.PushBack( currentFontRun );
593 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--MultilanguageSupport::ValidateFonts\n" );
596 void MultilanguageSupport::ValidateFonts( LogicalModel& model,
597 CharacterIndex characterIndex,
598 Length numberOfCharactersToRemove,
599 Length numberOfCharactersToInsert )
603 } // namespace Internal
607 } // namespace Toolkit