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>
27 #include <dali-toolkit/internal/text/multi-language-helper-functions.h>
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
41 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
50 bool ValidateFontsPerScript::FindValidFont( FontId fontId ) const
52 for( Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
53 endIt = mValidFonts.End();
66 MultilanguageSupport::MultilanguageSupport()
67 : mDefaultFontPerScriptCache(),
68 mValidFontsPerScriptCache()
70 // Initializes the default font cache to zero (invalid font).
71 // Reserves space to cache the default fonts and access them with the script as an index.
72 mDefaultFontPerScriptCache.Resize( TextAbstraction::UNKNOWN, 0u );
74 // Initializes the valid fonts cache to NULL (no valid fonts).
75 // Reserves space to cache the valid fonts and access them with the script as an index.
76 mValidFontsPerScriptCache.Resize( TextAbstraction::UNKNOWN, NULL );
79 MultilanguageSupport::~MultilanguageSupport()
81 // Destroy the valid fonts per script cache.
83 for( Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
84 endIt = mValidFontsPerScriptCache.End();
92 Text::MultilanguageSupport MultilanguageSupport::Get()
94 Text::MultilanguageSupport multilanguageSupportHandle;
96 SingletonService service( SingletonService::Get() );
99 // Check whether the singleton is already created
100 Dali::BaseHandle handle = service.GetSingleton( typeid( Text::MultilanguageSupport ) );
103 // If so, downcast the handle
104 MultilanguageSupport* impl = dynamic_cast< Internal::MultilanguageSupport* >( handle.GetObjectPtr() );
105 multilanguageSupportHandle = Text::MultilanguageSupport( impl );
107 else // create and register the object
109 multilanguageSupportHandle = Text::MultilanguageSupport( new MultilanguageSupport );
110 service.Register( typeid( multilanguageSupportHandle ), multilanguageSupportHandle );
114 return multilanguageSupportHandle;
117 void MultilanguageSupport::SetScripts( const Vector<Character>& text,
118 CharacterIndex startIndex,
119 Length numberOfCharacters,
120 Vector<ScriptRun>& scripts )
122 if( 0u == numberOfCharacters )
124 // Nothing to do if there are no characters.
128 // Find the first index where to insert the script.
129 ScriptRunIndex scriptIndex = 0u;
130 if( 0u != startIndex )
132 for( Vector<ScriptRun>::ConstIterator it = scripts.Begin(),
133 endIt = scripts.End();
135 ++it, ++scriptIndex )
137 const ScriptRun& run = *it;
138 if( startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters )
146 // Stores the current script run.
147 ScriptRun currentScriptRun;
148 currentScriptRun.characterRun.characterIndex = startIndex;
149 currentScriptRun.characterRun.numberOfCharacters = 0u;
150 currentScriptRun.script = TextAbstraction::UNKNOWN;
152 // Reserve some space to reduce the number of reallocations.
153 scripts.Reserve( text.Count() << 2u );
155 // Whether the first valid script needs to be set.
156 bool isFirstScriptToBeSet = true;
158 // Whether the first valid script is a right to left script.
159 bool isParagraphRTL = false;
161 // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
162 Length numberOfAllScriptCharacters = 0u;
164 // Pointers to the text and break info buffers.
165 const Character* const textBuffer = text.Begin();
167 // Traverse all characters and set the scripts.
168 const Length lastCharacter = startIndex + numberOfCharacters;
169 for( Length index = startIndex; index < lastCharacter; ++index )
171 Character character = *( textBuffer + index );
173 // Get the script of the character.
174 Script script = TextAbstraction::GetCharacterScript( character );
176 // Some characters (like white spaces) are valid for many scripts. The rules to set a script
178 // - If they are at the begining of a paragraph they get the script of the first character with
179 // a defined script. If they are at the end, they get the script of the last one.
180 // - If they are between two scripts with the same direction, they get the script of the previous
181 // character with a defined script. If the two scripts have different directions, they get the
182 // script of the first character of the paragraph with a defined script.
184 // Skip those characters valid for many scripts like white spaces or '\n'.
185 bool endOfText = index == lastCharacter;
187 ( TextAbstraction::COMMON == script ) )
189 // Count all these characters to be added into a script.
190 ++numberOfAllScriptCharacters;
192 if( TextAbstraction::IsNewParagraph( character ) )
194 // The character is a new paragraph.
195 // To know when there is a new paragraph is needed because if there is a white space
196 // between two scripts with different directions, it is added to the script with
197 // the same direction than the first script of the paragraph.
198 isFirstScriptToBeSet = true;
200 // Characters common to all scripts at the end of the paragraph are added to the last script.
201 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
203 // Store the script run.
204 if( TextAbstraction::UNKNOWN == currentScriptRun.script )
206 currentScriptRun.script = TextAbstraction::LATIN;
208 scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
211 // Initialize the new one.
212 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
213 currentScriptRun.characterRun.numberOfCharacters = 0u;
214 currentScriptRun.script = TextAbstraction::UNKNOWN;
215 numberOfAllScriptCharacters = 0u;
218 // Get the next character.
220 endOfText = index == lastCharacter;
223 character = *( textBuffer + index );
224 script = TextAbstraction::GetCharacterScript( character );
230 // Last characters of the text are 'white spaces'.
231 // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
235 // Check if it is the first character of a paragraph.
236 if( isFirstScriptToBeSet &&
237 ( TextAbstraction::UNKNOWN != script ) &&
238 ( TextAbstraction::COMMON != script ) )
240 // Sets the direction of the first valid script.
241 isParagraphRTL = TextAbstraction::IsRightToLeftScript( script );
242 isFirstScriptToBeSet = false;
245 if( ( script != currentScriptRun.script ) &&
246 ( TextAbstraction::COMMON != script ) )
248 // Current run needs to be stored and a new one initialized.
250 if( ( isParagraphRTL == TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) ) &&
251 ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
253 // Previous script has the same direction than the first script of the paragraph.
254 // All the previously skipped characters need to be added to the previous script before it's stored.
255 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
256 numberOfAllScriptCharacters = 0u;
258 else if( ( TextAbstraction::IsRightToLeftScript( currentScriptRun.script ) == TextAbstraction::IsRightToLeftScript( script ) ) &&
259 ( TextAbstraction::UNKNOWN != currentScriptRun.script ) )
261 // Current script and previous one have the same direction.
262 // All the previously skipped characters need to be added to the previous script before it's stored.
263 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
264 numberOfAllScriptCharacters = 0u;
267 if( 0u != currentScriptRun.characterRun.numberOfCharacters )
269 // Store the script run.
270 scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
274 // Initialize the new one.
275 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
276 currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
277 currentScriptRun.script = script;
278 numberOfAllScriptCharacters = 0u;
282 if( TextAbstraction::UNKNOWN != currentScriptRun.script )
284 // Adds white spaces between characters.
285 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
286 numberOfAllScriptCharacters = 0u;
289 // Add one more character to the run.
290 ++currentScriptRun.characterRun.numberOfCharacters;
294 // Add remaining characters into the last script.
295 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
297 if( 0u != currentScriptRun.characterRun.numberOfCharacters )
299 if( TextAbstraction::UNKNOWN == currentScriptRun.script )
301 // There are only white spaces in the last script. Set the latin script.
302 currentScriptRun.script = TextAbstraction::LATIN;
305 // Store the last run.
306 scripts.Insert( scripts.Begin() + scriptIndex, currentScriptRun );
310 if( scriptIndex < scripts.Count() )
312 // Update the indices of the next script runs.
313 const ScriptRun& run = *( scripts.Begin() + scriptIndex - 1u );
314 CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
316 for( Vector<ScriptRun>::Iterator it = scripts.Begin() + scriptIndex,
317 endIt = scripts.End();
321 ScriptRun& run = *it;
322 run.characterRun.characterIndex = nextCharacterIndex;
323 nextCharacterIndex += run.characterRun.numberOfCharacters;
328 void MultilanguageSupport::ValidateFonts( const Vector<Character>& text,
329 const Vector<ScriptRun>& scripts,
330 const Vector<FontDescriptionRun>& fontDescriptions,
331 FontId defaultFontId,
332 CharacterIndex startIndex,
333 Length numberOfCharacters,
334 Vector<FontRun>& fonts )
336 DALI_LOG_INFO( gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n" );
338 if( 0u == numberOfCharacters )
340 DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
341 // Nothing to do if there are no characters.
345 // Find the first index where to insert the font run.
346 FontRunIndex fontIndex = 0u;
347 if( 0u != startIndex )
349 for( Vector<FontRun>::ConstIterator it = fonts.Begin(),
354 const FontRun& run = *it;
355 if( startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters )
363 // Traverse the characters and validate/set the fonts.
366 FontId* defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
367 ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
369 // Stores the validated font runs.
370 fonts.Reserve( fontDescriptions.Count() );
372 // Initializes a validated font run.
373 FontRun currentFontRun;
374 currentFontRun.characterRun.characterIndex = startIndex;
375 currentFontRun.characterRun.numberOfCharacters = 0u;
376 currentFontRun.fontId = 0u;
378 // Get the font client.
379 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
381 // Get the default font description and default size.
382 TextAbstraction::FontDescription defaultFontDescription;
383 TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
384 if( defaultFontId > 0u )
386 fontClient.GetDescription( defaultFontId, defaultFontDescription );
387 defaultPointSize = fontClient.GetPointSize( defaultFontId );
390 // Merge font descriptions
391 Vector<FontId> fontIds;
392 fontIds.Resize( numberOfCharacters, defaultFontId );
393 MergeFontDescriptions( fontDescriptions,
395 defaultFontDescription,
398 numberOfCharacters );
400 const Character* const textBuffer = text.Begin();
401 const FontId* const fontIdsBuffer = fontIds.Begin();
402 Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
403 Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
404 bool isNewParagraphCharacter = false;
406 CharacterIndex lastCharacter = startIndex + numberOfCharacters;
407 for( Length index = startIndex; index < lastCharacter; ++index )
409 // Get the character.
410 const Character character = *( textBuffer + index );
412 // Get the font for the character.
413 FontId fontId = *( fontIdsBuffer + index - startIndex );
415 // Get the script for the character.
416 Script script = GetScript( index,
422 Dali::TextAbstraction::FontDescription description;
423 fontClient.GetDescription( fontId, description );
425 DALI_LOG_INFO( gLogFilter,
427 " Initial font set\n Character : %x, Script : %s, Font : %s \n",
429 Dali::TextAbstraction::ScriptName[script],
430 description.path.c_str() );
434 // Whether the font being validated is a default one not set by the user.
435 FontId preferredFont = fontId;
437 // Validate if the font set by the user supports the character.
439 // Check first in the caches.
441 // The user may have set the default font. Check it. Otherwise check in the valid fonts cache.
442 if( fontId != *( defaultFontPerScriptCacheBuffer + script ) )
444 // Check in the valid fonts cache.
445 ValidateFontsPerScript* validateFontsPerScript = *( validFontsPerScriptCacheBuffer + script );
447 if( NULL == validateFontsPerScript )
449 validateFontsPerScript = new ValidateFontsPerScript();
451 *( validFontsPerScriptCacheBuffer + script ) = validateFontsPerScript;
454 if( NULL != validateFontsPerScript )
456 if( !validateFontsPerScript->FindValidFont( fontId ) )
458 // Use the font client to validate the font.
459 GlyphIndex glyphIndex = fontClient.GetGlyphIndex( fontId, character );
461 // Emojis are present in many monochrome fonts; prefer color by default.
462 if( ( TextAbstraction::EMOJI == script ) &&
463 ( 0u != glyphIndex ) )
465 BufferImage bitmap = fontClient.CreateBitmap( fontId, glyphIndex );
467 ( Pixel::BGRA8888 != bitmap.GetPixelFormat() ) )
473 if( 0u == glyphIndex )
475 // The font is not valid. Set to zero and a default one will be set.
480 // Add the font to the valid font cache.
482 // At this point the validated font supports the given character. However, characters
483 // common for all scripts, like white spaces or new paragraph characters, need to be
484 // processed differently.
486 // i.e. A white space can have assigned a DEVANAGARI script but the font assigned may not
487 // support none of the DEVANAGARI glyphs. This font can't be added to the cache as a valid
488 // font for the DEVANAGARI script but the COMMON one.
489 if( TextAbstraction::IsCommonScript( character ) )
491 validateFontsPerScript = *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON );
493 if( NULL == validateFontsPerScript )
495 validateFontsPerScript = new ValidateFontsPerScript();
497 *( validFontsPerScriptCacheBuffer + TextAbstraction::COMMON ) = validateFontsPerScript;
501 validateFontsPerScript->mValidFonts.PushBack( fontId );
507 // The font has not been validated. Find a default one.
510 // The character has no font assigned. Get a default one from the cache
511 fontId = *( defaultFontPerScriptCacheBuffer + script );
513 // If the cache has not a default font, get one from the font client.
516 // Emojis are present in many monochrome fonts; prefer color by default.
517 bool preferColor = ( TextAbstraction::EMOJI == script );
519 // Find a fallback-font.
520 fontId = fontClient.FindFallbackFont( preferredFont, character, defaultPointSize, preferColor );
522 // If the system does not support a suitable font, fallback to Latin
525 fontId = *( defaultFontPerScriptCacheBuffer + TextAbstraction::LATIN );
529 fontId = fontClient.FindDefaultFont( UTF32_A, defaultPointSize );
533 *( defaultFontPerScriptCacheBuffer + script ) = fontId;
539 Dali::TextAbstraction::FontDescription description;
540 fontClient.GetDescription( fontId, description );
541 DALI_LOG_INFO( gLogFilter,
543 " Validated font set\n Character : %x, Script : %s, Font : %s \n",
545 Dali::TextAbstraction::ScriptName[script],
546 description.path.c_str() );
550 // The font is now validated.
551 if( ( fontId != currentFontRun.fontId ) ||
552 isNewParagraphCharacter )
554 // Current run needs to be stored and a new one initialized.
556 if( 0u != currentFontRun.characterRun.numberOfCharacters )
558 // Store the font run.
559 fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
563 // Initialize the new one.
564 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
565 currentFontRun.characterRun.numberOfCharacters = 0u;
566 currentFontRun.fontId = fontId;
569 // Add one more character to the run.
570 ++currentFontRun.characterRun.numberOfCharacters;
572 // Whether the current character is a new paragraph character.
573 isNewParagraphCharacter = TextAbstraction::IsNewParagraph( character );
576 if( 0u != currentFontRun.characterRun.numberOfCharacters )
578 // Store the last run.
579 fonts.Insert( fonts.Begin() + fontIndex, currentFontRun );
583 if( fontIndex < fonts.Count() )
585 // Update the indices of the next font runs.
586 const FontRun& run = *( fonts.Begin() + fontIndex - 1u );
587 CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
589 for( Vector<FontRun>::Iterator it = fonts.Begin() + fontIndex,
596 run.characterRun.characterIndex = nextCharacterIndex;
597 nextCharacterIndex += run.characterRun.numberOfCharacters;
601 DALI_LOG_INFO( gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n" );
604 } // namespace Internal
608 } // namespace Toolkit