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>
22 #include <dali/integration-api/debug.h>
23 #include <dali/devel-api/adaptor-framework/singleton-service.h>
24 #include <dali/devel-api/text-abstraction/font-client.h>
34 #if defined(DEBUG_ENABLED)
35 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
38 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
48 * @brief Retrieves the font Id from the font run for a given character's @p index.
50 * If the character's index exceeds the current font run it increases the iterator to get the next one.
52 * @param[in] index The character's index.
53 * @param[in,out] fontRunIt Iterator to the current font run.
54 * @param[in] fontRunEndIt Iterator to one after the last font run.
56 * @return The font id.
58 FontId GetFontId( Length index,
59 Vector<FontRun>::ConstIterator& fontRunIt,
60 const Vector<FontRun>::ConstIterator& fontRunEndIt )
64 if( fontRunIt != fontRunEndIt )
66 const FontRun& fontRun = *fontRunIt;
68 if( ( index >= fontRun.characterRun.characterIndex ) &&
69 ( index < fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters ) )
71 fontId = fontRun.fontId;
74 if( index + 1u == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters )
76 // All the characters of the current run have been traversed. Get the next one for the next iteration.
85 * @brief Retrieves the script Id from the script run for a given character's @p index.
87 * If the character's index exceeds the current script run it increases the iterator to get the next one.
89 * @param[in] index The character's index.
90 * @param[in,out] scriptRunIt Iterator to the current font run.
91 * @param[in] scriptRunEndIt Iterator to one after the last script run.
95 Script GetScript( Length index,
96 Vector<ScriptRun>::ConstIterator& scriptRunIt,
97 const Vector<ScriptRun>::ConstIterator& scriptRunEndIt )
99 Script script = TextAbstraction::UNKNOWN;
101 if( scriptRunIt != scriptRunEndIt )
103 const ScriptRun& scriptRun = *scriptRunIt;
105 if( ( index >= scriptRun.characterRun.characterIndex ) &&
106 ( index < scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters ) )
108 script = scriptRun.script;
111 if( index + 1u == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters )
113 // All the characters of the current run have been traversed. Get the next one for the next iteration.
121 bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
123 for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
124 endIt = mValidFonts.End();
137 FontId DefaultFonts::FindFont( TextAbstraction::FontClient& fontClient, PointSize26Dot6 size ) const
139 for( Vector<FontId>::ConstIterator it = mFonts.Begin(),
140 endIt = mFonts.End();
144 const FontId fontId = *it;
145 if( size == fontClient.GetPointSize( fontId ) )
154 MultilanguageSupport::MultilanguageSupport()
155 : mDefaultFontPerScriptCache(),
156 mValidFontsPerScriptCache()
158 // Initializes the default font cache to zero (invalid font).
159 // Reserves space to cache the default fonts and access them with the script as an index.
160 mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN, NULL );
162 // Initializes the valid fonts cache to NULL (no valid fonts).
163 // Reserves space to cache the valid fonts and access them with the script as an index.
164 mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN, NULL );
167 MultilanguageSupport::~MultilanguageSupport()
169 // Destroy the default font per script cache.
170 for( Vector<DefaultFonts*>::Iterator it = mDefaultFontPerScriptCache.Begin(),
171 endIt = mDefaultFontPerScriptCache.End();
178 // Destroy the valid fonts per script cache.
179 for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
180 endIt = mValidFontsPerScriptCache.End();
188 Text::MultilanguageSupport MultilanguageSupport::Get()
190 Text::MultilanguageSupport multilanguageSupportHandle;
192 SingletonService service( SingletonService::Get() );
195 // Check whether the singleton is already created
196 Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
199 // If so, downcast the handle
200 MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
201 multilanguageSupportHandle = Text::MultilanguageSupport( impl );
203 else // create and register the object
205 multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
206 service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
210 return multilanguageSupportHandle;
213 void MultilanguageSupport::SetScripts( const Vector<Character>& text,
214 Vector<ScriptRun>& scripts )
216 const Length numberOfCharacters = text.Count();
218 if( 0u == numberOfCharacters )
220 // Nothing to do if there are no characters.
224 // Stores the current script run.
225 ScriptRun currentScriptRun;
226 currentScriptRun.characterRun.characterIndex = 0u;
227 currentScriptRun.characterRun.numberOfCharacters = 0u;
228 currentScriptRun.script = TextAbstraction::UNKNOWN;
230 // Reserve some space to reduce the number of reallocations.
231 scripts.Reserve( numberOfCharacters << 2u );
233 // Whether the first valid script needs to be set.
234 bool isFirstScriptToBeSet = true;
236 // Whether the first valid script is a right to left script.
237 bool isParagraphRTL = false;
239 // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
240 Length numberOfAllScriptCharacters = 0u;
242 // Pointers to the text and break info buffers.
243 const Character* const textBuffer = text.Begin();
245 // Traverse all characters and set the scripts.
246 for( Length index = 0u; index < numberOfCharacters; ++index )
248 Character character = *( textBuffer + index );
250 // Get the script of the character.
251 Script script = TextAbstraction::GetCharacterScript( character );
253 // Some characters (like white spaces) are valid for many scripts. The rules to set a script
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.
261 // Skip those characters valid for many scripts like white spaces or '\n'.
262 bool endOfText = index == numberOfCharacters;
264 ( TextAbstraction::COMMON == script ) )
266 // Count all these characters to be added into a script.
267 ++numberOfAllScriptCharacters;
269 if( TextAbstraction::IsNewParagraph( character ) )
271 // The character is a new paragraph.
272 // To 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 isFirstScriptToBeSet = true;
277 // Characters common to all scripts at the end of the paragraph are added to the last script (if the last one is not unknown).
278 if( TextAbstraction::UNKNOWN != currentScriptRun.script )
280 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
281 numberOfAllScriptCharacters = 0u;
285 // Get the next character.
287 endOfText = index == numberOfCharacters;
290 character = *( textBuffer + index );
291 script = TextAbstraction::GetCharacterScript( character );
297 // Last characters of the text are 'white spaces'.
298 // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
302 // Check if it is the first character of a paragraph.
303 if( isFirstScriptToBeSet &&
304 ( TextAbstraction::UNKNOWN != script ) &&
305 ( TextAbstraction::COMMON != script ) )
307 // Sets the direction of the first valid script.
308 isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
309 isFirstScriptToBeSet = false;
312 if( ( script != currentScriptRun.script ) &&
313 ( TextAbstraction::COMMON != script ) )
315 // Current run needs to be stored and a new one initialized.
317 if( ( isParagraphRTL == TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) ) &&
318 ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
320 // Previous script has the same direction than the first script of the paragraph.
321 // All the previously skipped characters need to be added to the previous script before it's stored.
322 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
323 numberOfAllScriptCharacters = 0u;
325 else if( ( TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) == TextAbstraction::IsRightToLeftScript( script ) ) &&
326 ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
328 // Current script and previous one have the same direction.
329 // All the previously skipped characters need to be added to the previous script before it's stored.
330 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
331 numberOfAllScriptCharacters = 0u;
334 if( 0u != currentScriptRun.characterRun.numberOfCharacters )
336 // Store the script run.
337 scripts.PushBack( currentScriptRun );
340 // Initialize the new one.
341 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
342 currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
343 currentScriptRun.script = script;
344 numberOfAllScriptCharacters = 0u;
348 if( TextAbstraction::UNKNOWN != currentScriptRun.script )
350 // Adds white spaces between characters.
351 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
352 numberOfAllScriptCharacters = 0u;
355 // Add one more character to the run.
356 ++currentScriptRun.characterRun.numberOfCharacters;
360 // Add remaining characters into the last script.
361 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
363 DALI_ASSERT_DEBUG( ( 0u != currentScriptRun.characterRun.numberOfCharacters ) && "MultilanguageSupport::SetScripts() Trying to insert a script run with zero characters." );
365 if( TextAbstraction::UNKNOWN == currentScriptRun.script )
367 // There are only white spaces in the last script. Set the latin script.
368 currentScriptRun.script = TextAbstraction::LATIN;
371 // Store the last run.
372 scripts.PushBack( currentScriptRun );
375 void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
376 const Vector<ScriptRun>& scripts,
377 Vector<FontRun>& fonts )
379 DALI_LOG_INFO( gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n" );
380 const Length numberOfCharacters = text.Count();
382 if( 0u == numberOfCharacters )
384 DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
385 // Nothing to do if there are no characters.
389 // Copy the fonts set by application developers.
390 const Length numberOfFontRuns = fonts.Count();
391 const Vector<FontRun> userSetFonts = fonts;
394 // Traverse the characters and validate/set the fonts.
397 DefaultFonts** defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
398 ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
400 // Stores the validated font runs.
401 fonts.Reserve( numberOfFontRuns );
403 // Initializes a validated font run.
404 FontRun currentFontRun;
405 currentFontRun.characterRun.characterIndex = 0u;
406 currentFontRun.characterRun.numberOfCharacters = 0u;
407 currentFontRun.fontId = 0u;
408 currentFontRun.isDefault = false;
410 // Get the font client.
411 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
413 // Iterators of the font and script runs.
414 Vector<FontRun>::ConstIterator fontRunIt = userSetFonts.Begin();
415 Vector<FontRun>::ConstIterator fontRunEndIt = userSetFonts.End();
416 Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
417 Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
419 // The default font point size.
420 PointSize26Dot6 currentPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
422 FontId currentFontId = 0u;
424 for( Length index = 0u; index < numberOfCharacters; ++index )
426 // Get the character.
427 const Character character = *( text.Begin() + index );
429 // Get the font for the character.
430 FontId fontId = GetFontId( index,
434 // Get the script for the character.
435 Script script = GetScript( index,
439 // Get the current point size.
440 if( currentFontId != fontId )
442 currentPointSize = fontClient.GetPointSize( fontId );
443 currentFontId = fontId;
448 Dali::TextAbstraction::FontDescription description;
449 fontClient.GetDescription( fontId, description );
451 DALI_LOG_INFO( gLogFilter,
453 " Initial font set\n Character : %x, Script : %s, Font : %s \n",
455 Dali::TextAbstraction::ScriptName[script],
456 description.path.c_str() );
460 if( TextAbstraction::UNKNOWN == script )
462 DALI_LOG_WARNING( "MultilanguageSupport::ValidateFonts. Unknown script!" );
463 script = TextAbstraction::LATIN;
466 // Whether the font being validated is a default one not set by the user.
467 const bool isDefault = ( 0u == fontId );
468 FontId preferredFont = fontId;
470 DALI_LOG_INFO( gLogFilter,
472 " Is a default font : %s\n",
473 ( isDefault ? "true" : "false" ) );
475 FontId cachedDefaultFontId = 0u;
478 // Validate if the font set by the user supports the character.
480 // Check first in the caches.
482 DefaultFonts* defaultFonts = *( defaultFontPerScriptCacheBuffer + script );
483 if( NULL != defaultFonts )
485 cachedDefaultFontId = defaultFonts->FindFont( fontClient, currentPointSize );
488 // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
489 if( fontId != cachedDefaultFontId )
491 // Check in the valid fonts cache.
492 ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
494 if( NULL == validateFontsPerScript )
496 validateFontsPerScript = new ValidateFontsPerScript();
498 *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
501 if( NULL != validateFontsPerScript )
503 if( !validateFontsPerScript->FindValidFont( fontId ) )
505 // Use the font client to validate the font.
506 GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
508 // Emojis are present in many monochrome fonts; prefer color by default.
509 if( TextAbstraction::EMOJI == script &&
512 BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
514 Pixel::BGRA8888 != bitmap.GetPixelFormat() )
520 if( 0u == glyphIndex )
522 // The font is not valid. Set to zero and a default one will be set.
527 // Add the font to the valid font cache.
529 // At this point the validated font supports the given character. However, characters
530 // common for all scripts, like white spaces or new paragraph characters, need to be
531 // processed differently.
533 // i.e. A white space can have assigned a DEVANAGARI script but the font assigned may not
534 // support none of the DEVANAGARI glyphs. This font can't be added to the cache as a valid
535 // font for the DEVANAGARI script but the COMMON one.
536 if( TextAbstraction::IsCommonScript( character ) )
538 validateFontsPerScript = *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON );
540 if( NULL == validateFontsPerScript )
542 validateFontsPerScript = new ValidateFontsPerScript();
544 *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON ) = validateFontsPerScript;
548 validateFontsPerScript->mValidFonts.PushBack( fontId );
555 // The font has not been validated. Find a default one.
558 // The character has no font assigned. Get a default one from the cache
559 if( 0u == cachedDefaultFontId )
561 DefaultFonts* defaultFonts = *( defaultFontPerScriptCacheBuffer + script );
562 if( NULL != defaultFonts )
564 fontId = defaultFonts->FindFont( fontClient, currentPointSize );
569 fontId = cachedDefaultFontId;
572 // If the cache has not a default font, get one from the font client.
575 // Emojis are present in many monochrome fonts; prefer color by default.
576 bool preferColor = ( TextAbstraction::EMOJI == script );
578 // Find a fallback-font.
579 fontId = fontClient.FindFallbackFont( preferredFont, character, currentPointSize, preferColor );
581 // If the system does not support a suitable font, fallback to Latin
582 DefaultFonts* latinDefaults = NULL;
585 latinDefaults = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
586 if( NULL != latinDefaults )
588 fontId = latinDefaults->FindFont( fontClient, currentPointSize );
594 fontId = fontClient.FindDefaultFont( UTF32_A, currentPointSize );
598 if( NULL == latinDefaults )
600 latinDefaults = new DefaultFonts();
601 *( defaultFontPerScriptCacheBuffer + script ) = latinDefaults;
603 latinDefaults->mFonts.PushBack( fontId );
609 Dali::TextAbstraction::FontDescription description;
610 fontClient.GetDescription( fontId, description );
611 DALI_LOG_INFO( gLogFilter,
613 " Validated font set\n Character : %x, Script : %s, Font : %s \n",
615 Dali::TextAbstraction::ScriptName[script],
616 description.path.c_str() );
620 // The font is now validated.
622 if( ( fontId != currentFontRun.fontId ) ||
623 ( isDefault != currentFontRun.isDefault ) )
625 // Current run needs to be stored and a new one initialized.
627 if( 0u != currentFontRun.characterRun.numberOfCharacters )
629 // Store the font run.
630 fonts.PushBack( currentFontRun );
633 // Initialize the new one.
634 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
635 currentFontRun.characterRun.numberOfCharacters = 0u;
636 currentFontRun.fontId = fontId;
637 currentFontRun.isDefault = isDefault;
640 // Add one more character to the run.
641 ++currentFontRun.characterRun.numberOfCharacters;
644 if( 0u != currentFontRun.characterRun.numberOfCharacters )
646 // Store the last run.
647 fonts.PushBack( currentFontRun );
649 DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
652 } // namespace Internal
656 } // namespace Toolkit