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 Merges the font descriptions to retrieve the font Id for each character.
50 * @param[in] fontDescriptions The font descriptions.
51 * @param[out] fontIds The font id for each character.
52 * @param[in] defaultFontDescription The default font description.
53 * @param[in] defaultPointSize The default font size.
55 void MergeFontDescriptions( const Vector<FontDescriptionRun>& fontDescriptions,
56 Vector<FontId>& fontIds,
57 const TextAbstraction::FontDescription& defaultFontDescription,
58 TextAbstraction::PointSize26Dot6 defaultPointSize )
60 // Get the handle to the font client.
61 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
63 // Pointer to the font id buffer.
64 FontId* fontIdsBuffer = fontIds.Begin();
66 // Traverse all the characters.
67 const Length numberOfCharacters = fontIds.Count();
68 for( CharacterIndex index = 0u; index < numberOfCharacters; ++index )
70 // The default font description and font point size.
71 TextAbstraction::FontDescription fontDescription = defaultFontDescription;
72 TextAbstraction::PointSize26Dot6 fontSize = defaultPointSize;
73 bool defaultFont = true;
75 // Traverse all the font descriptions.
76 for( Vector<FontDescriptionRun>::ConstIterator it = fontDescriptions.Begin(),
77 endIt = fontDescriptions.End();
81 // Check whether the character's font is modified by the current font description.
82 const FontDescriptionRun& fontRun = *it;
83 if( ( index >= fontRun.characterRun.characterIndex ) &&
84 ( index < fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters ) )
86 if( fontRun.familyDefined )
88 fontDescription.family = std::string( fontRun.familyName, fontRun.familyLength );
91 if( fontRun.weightDefined )
93 fontDescription.weight = fontRun.weight;
96 if( fontRun.widthDefined )
98 fontDescription.width = fontRun.width;
101 if( fontRun.slantDefined )
103 fontDescription.slant = fontRun.slant;
106 if( fontRun.sizeDefined )
108 fontSize = fontRun.size;
114 // Get the font id if is not the default font.
117 *( fontIdsBuffer + index ) = fontClient.GetFontId( fontDescription, fontSize );
123 * @brief Retrieves the script Id from the script run for a given character's @p index.
125 * If the character's index exceeds the current script run it increases the iterator to get the next one.
127 * @param[in] index The character's index.
128 * @param[in,out] scriptRunIt Iterator to the current font run.
129 * @param[in] scriptRunEndIt Iterator to one after the last script run.
131 * @return The script.
133 Script GetScript( Length index,
134 Vector<ScriptRun>::ConstIterator& scriptRunIt,
135 const Vector<ScriptRun>::ConstIterator& scriptRunEndIt )
137 Script script = TextAbstraction::UNKNOWN;
139 if( scriptRunIt != scriptRunEndIt )
141 const ScriptRun& scriptRun = *scriptRunIt;
143 if( ( index >= scriptRun.characterRun.characterIndex ) &&
144 ( index < scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters ) )
146 script = scriptRun.script;
149 if( index + 1u == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters )
151 // All the characters of the current run have been traversed. Get the next one for the next iteration.
159 bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
161 for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
162 endIt = mValidFonts.End();
175 MultilanguageSupport::MultilanguageSupport()
176 : mDefaultFontPerScriptCache(),
177 mValidFontsPerScriptCache()
179 // Initializes the default font cache to zero (invalid font).
180 // Reserves space to cache the default fonts and access them with the script as an index.
181 mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN, 0u );
183 // Initializes the valid fonts cache to NULL (no valid fonts).
184 // Reserves space to cache the valid fonts and access them with the script as an index.
185 mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN, NULL );
188 MultilanguageSupport::~MultilanguageSupport()
190 // Destroy the valid fonts per script cache.
192 for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
193 endIt = mValidFontsPerScriptCache.End();
201 Text::MultilanguageSupport MultilanguageSupport::Get()
203 Text::MultilanguageSupport multilanguageSupportHandle;
205 SingletonService service( SingletonService::Get() );
208 // Check whether the singleton is already created
209 Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
212 // If so, downcast the handle
213 MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
214 multilanguageSupportHandle = Text::MultilanguageSupport( impl );
216 else // create and register the object
218 multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
219 service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
223 return multilanguageSupportHandle;
226 void MultilanguageSupport::SetScripts( const Vector<Character>& text,
227 Vector<ScriptRun>& scripts )
229 const Length numberOfCharacters = text.Count();
231 if( 0u == numberOfCharacters )
233 // Nothing to do if there are no characters.
237 // Stores the current script run.
238 ScriptRun currentScriptRun;
239 currentScriptRun.characterRun.characterIndex = 0u;
240 currentScriptRun.characterRun.numberOfCharacters = 0u;
241 currentScriptRun.script = TextAbstraction::UNKNOWN;
243 // Reserve some space to reduce the number of reallocations.
244 scripts.Reserve( numberOfCharacters << 2u );
246 // Whether the first valid script needs to be set.
247 bool isFirstScriptToBeSet = true;
249 // Whether the first valid script is a right to left script.
250 bool isParagraphRTL = false;
252 // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
253 Length numberOfAllScriptCharacters = 0u;
255 // Pointers to the text and break info buffers.
256 const Character* const textBuffer = text.Begin();
258 // Traverse all characters and set the scripts.
259 for( Length index = 0u; index < numberOfCharacters; ++index )
261 Character character = *( textBuffer + index );
263 // Get the script of the character.
264 Script script = TextAbstraction::GetCharacterScript( character );
266 // Some characters (like white spaces) are valid for many scripts. The rules to set a script
268 // - If they are at the begining of a paragraph they get the script of the first character with
269 // a defined script. If they are at the end, they get the script of the last one.
270 // - If they are between two scripts with the same direction, they get the script of the previous
271 // character with a defined script. If the two scripts have different directions, they get the
272 // script of the first character of the paragraph with a defined script.
274 // Skip those characters valid for many scripts like white spaces or '\n'.
275 bool endOfText = index == numberOfCharacters;
277 ( TextAbstraction::COMMON == script ) )
279 // Count all these characters to be added into a script.
280 ++numberOfAllScriptCharacters;
282 if( TextAbstraction::IsNewParagraph( character ) )
284 // The character is a new paragraph.
285 // To know when there is a new paragraph is needed because if there is a white space
286 // between two scripts with different directions, it is added to the script with
287 // the same direction than the first script of the paragraph.
288 isFirstScriptToBeSet = true;
290 // Characters common to all scripts at the end of the paragraph are added to the last script (if the last one is not unknown).
291 if( TextAbstraction::UNKNOWN != currentScriptRun.script )
293 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
294 numberOfAllScriptCharacters = 0u;
298 // Get the next character.
300 endOfText = index == numberOfCharacters;
303 character = *( textBuffer + index );
304 script = TextAbstraction::GetCharacterScript( character );
310 // Last characters of the text are 'white spaces'.
311 // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
315 // Check if it is the first character of a paragraph.
316 if( isFirstScriptToBeSet &&
317 ( TextAbstraction::UNKNOWN != script ) &&
318 ( TextAbstraction::COMMON != script ) )
320 // Sets the direction of the first valid script.
321 isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
322 isFirstScriptToBeSet = false;
325 if( ( script != currentScriptRun.script ) &&
326 ( TextAbstraction::COMMON != script ) )
328 // Current run needs to be stored and a new one initialized.
330 if( ( isParagraphRTL == TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) ) &&
331 ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
333 // Previous script has the same direction than the first script of the paragraph.
334 // All the previously skipped characters need to be added to the previous script before it's stored.
335 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
336 numberOfAllScriptCharacters = 0u;
338 else if( ( TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) == TextAbstraction::IsRightToLeftScript( script ) ) &&
339 ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
341 // Current script and previous one have the same direction.
342 // All the previously skipped characters need to be added to the previous script before it's stored.
343 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
344 numberOfAllScriptCharacters = 0u;
347 if( 0u != currentScriptRun.characterRun.numberOfCharacters )
349 // Store the script run.
350 scripts.PushBack( currentScriptRun );
353 // Initialize the new one.
354 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
355 currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
356 currentScriptRun.script = script;
357 numberOfAllScriptCharacters = 0u;
361 if( TextAbstraction::UNKNOWN != currentScriptRun.script )
363 // Adds white spaces between characters.
364 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
365 numberOfAllScriptCharacters = 0u;
368 // Add one more character to the run.
369 ++currentScriptRun.characterRun.numberOfCharacters;
373 // Add remaining characters into the last script.
374 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
376 DALI_ASSERT_DEBUG( ( 0u != currentScriptRun.characterRun.numberOfCharacters ) && "MultilanguageSupport::SetScripts() Trying to insert a script run with zero characters." );
378 if( TextAbstraction::UNKNOWN == currentScriptRun.script )
380 // There are only white spaces in the last script. Set the latin script.
381 currentScriptRun.script = TextAbstraction::LATIN;
384 // Store the last run.
385 scripts.PushBack( currentScriptRun );
388 void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
389 const Vector<ScriptRun>& scripts,
390 const Vector<FontDescriptionRun>& fontDescriptions,
391 FontId defaultFontId,
392 Vector<FontRun>& fonts )
394 // Clear any previously validated font.
397 DALI_LOG_INFO( gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n" );
398 const Length numberOfCharacters = text.Count();
400 if( 0u == numberOfCharacters )
402 DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
403 // Nothing to do if there are no characters.
407 // Traverse the characters and validate/set the fonts.
410 FontId* defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
411 ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
413 // Stores the validated font runs.
414 fonts.Reserve( fontDescriptions.Count() );
416 // Initializes a validated font run.
417 FontRun currentFontRun;
418 currentFontRun.characterRun.characterIndex = 0u;
419 currentFontRun.characterRun.numberOfCharacters = 0u;
420 currentFontRun.fontId = 0u;
422 // Get the font client.
423 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
425 // Get the default font description and default size.
426 TextAbstraction::FontDescription defaultFontDescription;
427 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
428 if( defaultFontId > 0u )
430 fontClient.GetDescription( defaultFontId, defaultFontDescription );
431 defaultPointSize = fontClient.GetPointSize( defaultFontId );
434 // Merge font descriptions
435 Vector<FontId> fontIds;
436 fontIds.Resize( numberOfCharacters, defaultFontId );
437 MergeFontDescriptions( fontDescriptions,
439 defaultFontDescription,
442 const Character* const textBuffer = text.Begin();
443 const FontId* const fontIdsBuffer = fontIds.Begin();
444 Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
445 Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
447 for( Length index = 0u; index < numberOfCharacters; ++index )
449 // Get the character.
450 const Character character = *( textBuffer + index );
452 // Get the font for the character.
453 FontId fontId = *( fontIdsBuffer + index );
455 // Get the script for the character.
456 Script script = GetScript( index,
462 Dali::TextAbstraction::FontDescription description;
463 fontClient.GetDescription( fontId, description );
465 DALI_LOG_INFO( gLogFilter,
467 " Initial font set\n Character : %x, Script : %s, Font : %s \n",
469 Dali::TextAbstraction::ScriptName[script],
470 description.path.c_str() );
474 if( TextAbstraction::UNKNOWN == script )
476 DALI_LOG_WARNING( "MultilanguageSupport::ValidateFonts. Unknown script!" );
477 script = TextAbstraction::LATIN;
480 // Whether the font being validated is a default one not set by the user.
481 FontId preferredFont = fontId;
483 // Validate if the font set by the user supports the character.
485 // Check first in the caches.
487 // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
488 if( fontId != *( defaultFontPerScriptCacheBuffer + script ) )
490 // Check in the valid fonts cache.
491 ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
493 if( NULL == validateFontsPerScript )
495 validateFontsPerScript = new ValidateFontsPerScript();
497 *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
500 if( NULL != validateFontsPerScript )
502 if( !validateFontsPerScript->FindValidFont( fontId ) )
504 // Use the font client to validate the font.
505 GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
507 // Emojis are present in many monochrome fonts; prefer color by default.
508 if( ( TextAbstraction::EMOJI == script ) &&
509 ( 0u != glyphIndex ) )
511 BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
513 ( Pixel::BGRA8888 != bitmap.GetPixelFormat() ) )
519 if( 0u == glyphIndex )
521 // The font is not valid. Set to zero and a default one will be set.
526 // Add the font to the valid font cache.
528 // At this point the validated font supports the given character. However, characters
529 // common for all scripts, like white spaces or new paragraph characters, need to be
530 // processed differently.
532 // i.e. A white space can have assigned a DEVANAGARI script but the font assigned may not
533 // support none of the DEVANAGARI glyphs. This font can't be added to the cache as a valid
534 // font for the DEVANAGARI script but the COMMON one.
535 if( TextAbstraction::IsCommonScript( character ) )
537 validateFontsPerScript = *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON );
539 if( NULL == validateFontsPerScript )
541 validateFontsPerScript = new ValidateFontsPerScript();
543 *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON ) = validateFontsPerScript;
547 validateFontsPerScript->mValidFonts.PushBack( fontId );
553 // The font has not been validated. Find a default one.
556 // The character has no font assigned. Get a default one from the cache
557 fontId = *( defaultFontPerScriptCacheBuffer + script );
559 // If the cache has not a default font, get one from the font client.
562 // Emojis are present in many monochrome fonts; prefer color by default.
563 bool preferColor = ( TextAbstraction::EMOJI == script );
565 // Find a fallback-font.
566 fontId = fontClient.FindFallbackFont( preferredFont, character, defaultPointSize, preferColor );
568 // If the system does not support a suitable font, fallback to Latin
571 fontId = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
575 fontId = fontClient.FindDefaultFont( UTF32_A, defaultPointSize );
579 *( defaultFontPerScriptCacheBuffer + script ) = fontId;
585 Dali::TextAbstraction::FontDescription description;
586 fontClient.GetDescription( fontId, description );
587 DALI_LOG_INFO( gLogFilter,
589 " Validated font set\n Character : %x, Script : %s, Font : %s \n",
591 Dali::TextAbstraction::ScriptName[script],
592 description.path.c_str() );
596 // The font is now validated.
598 if( fontId != currentFontRun.fontId )
600 // Current run needs to be stored and a new one initialized.
602 if( 0u != currentFontRun.characterRun.numberOfCharacters )
604 // Store the font run.
605 fonts.PushBack( currentFontRun );
608 // Initialize the new one.
609 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
610 currentFontRun.characterRun.numberOfCharacters = 0u;
611 currentFontRun.fontId = fontId;
614 // Add one more character to the run.
615 ++currentFontRun.characterRun.numberOfCharacters;
618 if( 0u != currentFontRun.characterRun.numberOfCharacters )
620 // Store the last run.
621 fonts.PushBack( currentFontRun );
623 DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
626 } // namespace Internal
630 } // namespace Toolkit