2 * Copyright (c) 2021 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/devel-api/common/singleton-service.h>
23 #include <dali/devel-api/text-abstraction/font-client.h>
24 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/text/multi-language-helper-functions.h>
35 #if defined(DEBUG_ENABLED)
36 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
39 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
46 bool ValidateFontsPerScript::IsValidFont(FontId fontId) const
48 for(Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
49 endIt = mValidFonts.End();
62 FontId DefaultFonts::FindFont(TextAbstraction::FontClient& fontClient,
63 const TextAbstraction::FontDescription& description,
64 PointSize26Dot6 size) const
66 for(std::vector<CacheItem>::const_iterator it = mFonts.begin(),
71 const CacheItem& item = *it;
73 if(((TextAbstraction::FontWeight::NONE == description.weight) || (description.weight == item.description.weight)) &&
74 ((TextAbstraction::FontWidth::NONE == description.width) || (description.width == item.description.width)) &&
75 ((TextAbstraction::FontSlant::NONE == description.slant) || (description.slant == item.description.slant)) &&
76 (size == fontClient.GetPointSize(item.fontId)) &&
77 (description.family.empty() || (description.family == item.description.family)))
86 void DefaultFonts::Cache(const TextAbstraction::FontDescription& description, FontId fontId)
89 item.description = description;
91 mFonts.push_back(item);
94 MultilanguageSupport::MultilanguageSupport()
95 : mDefaultFontPerScriptCache(),
96 mValidFontsPerScriptCache()
98 // Initializes the default font cache to zero (invalid font).
99 // Reserves space to cache the default fonts and access them with the script as an index.
100 mDefaultFontPerScriptCache.Resize(TextAbstraction::UNKNOWN + 1, NULL);
102 // Initializes the valid fonts cache to NULL (no valid fonts).
103 // Reserves space to cache the valid fonts and access them with the script as an index.
104 mValidFontsPerScriptCache.Resize(TextAbstraction::UNKNOWN + 1, NULL);
107 MultilanguageSupport::~MultilanguageSupport()
109 // Destroy the default font per script cache.
110 for(Vector<DefaultFonts*>::Iterator it = mDefaultFontPerScriptCache.Begin(),
111 endIt = mDefaultFontPerScriptCache.End();
118 // Destroy the valid fonts per script cache.
119 for(Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
120 endIt = mValidFontsPerScriptCache.End();
128 Text::MultilanguageSupport MultilanguageSupport::Get()
130 Text::MultilanguageSupport multilanguageSupportHandle;
132 SingletonService service(SingletonService::Get());
135 // Check whether the singleton is already created
136 Dali::BaseHandle handle = service.GetSingleton(typeid(Text::MultilanguageSupport));
139 // If so, downcast the handle
140 MultilanguageSupport* impl = dynamic_cast<Internal::MultilanguageSupport*>(handle.GetObjectPtr());
141 multilanguageSupportHandle = Text::MultilanguageSupport(impl);
143 else // create and register the object
145 multilanguageSupportHandle = Text::MultilanguageSupport(new MultilanguageSupport);
146 service.Register(typeid(multilanguageSupportHandle), multilanguageSupportHandle);
150 return multilanguageSupportHandle;
153 void MultilanguageSupport::SetScripts(const Vector<Character>& text,
154 CharacterIndex startIndex,
155 Length numberOfCharacters,
156 Vector<ScriptRun>& scripts)
158 if(0u == numberOfCharacters)
160 // Nothing to do if there are no characters.
164 // Find the first index where to insert the script.
165 ScriptRunIndex scriptIndex = 0u;
168 for(Vector<ScriptRun>::ConstIterator it = scripts.Begin(),
169 endIt = scripts.End();
173 const ScriptRun& run = *it;
174 if(startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
182 // Stores the current script run.
183 ScriptRun currentScriptRun;
184 currentScriptRun.characterRun.characterIndex = startIndex;
185 currentScriptRun.characterRun.numberOfCharacters = 0u;
186 currentScriptRun.script = TextAbstraction::UNKNOWN;
188 // Reserve some space to reduce the number of reallocations.
189 scripts.Reserve(text.Count() << 2u);
191 // Whether the first valid script needs to be set.
192 bool isFirstScriptToBeSet = true;
194 // Whether the first valid script is a right to left script.
195 bool isParagraphRTL = false;
197 // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
198 Length numberOfAllScriptCharacters = 0u;
200 // Pointers to the text buffer.
201 const Character* const textBuffer = text.Begin();
203 // Initialize whether is right to left direction
204 currentScriptRun.isRightToLeft = false;
206 // Traverse all characters and set the scripts.
207 const Length lastCharacter = startIndex + numberOfCharacters;
208 for(Length index = startIndex; index < lastCharacter; ++index)
210 Character character = *(textBuffer + index);
212 // Get the script of the character.
213 Script script = TextAbstraction::GetCharacterScript(character);
215 // Some characters (like white spaces) are valid for many scripts. The rules to set a script
217 // - If they are at the begining of a paragraph they get the script of the first character with
218 // a defined script. If they are at the end, they get the script of the last one.
219 // - If they are between two scripts with the same direction, they get the script of the previous
220 // character with a defined script. If the two scripts have different directions, they get the
221 // script of the first character of the paragraph with a defined script.
223 // Skip those characters valid for many scripts like white spaces or '\n'.
224 bool endOfText = index == lastCharacter;
226 (TextAbstraction::COMMON == script))
228 // Check if whether is right to left markup and Keeps true if the previous value was true.
229 currentScriptRun.isRightToLeft = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftMark(character);
231 if(TextAbstraction::EMOJI == currentScriptRun.script)
233 // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
234 scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
237 // Initialize the new one.
238 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
239 currentScriptRun.characterRun.numberOfCharacters = 0u;
240 currentScriptRun.script = TextAbstraction::UNKNOWN;
241 numberOfAllScriptCharacters = 0u;
244 // Count all these characters to be added into a script.
245 ++numberOfAllScriptCharacters;
247 if(TextAbstraction::IsNewParagraph(character))
249 // The character is a new paragraph.
250 // To know when there is a new paragraph is needed because if there is a white space
251 // between two scripts with different directions, it is added to the script with
252 // the same direction than the first script of the paragraph.
253 isFirstScriptToBeSet = true;
255 // Characters common to all scripts at the end of the paragraph are added to the last script.
256 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
258 // Store the script run.
259 scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
262 // Initialize the new one.
263 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
264 currentScriptRun.characterRun.numberOfCharacters = 0u;
265 currentScriptRun.script = TextAbstraction::UNKNOWN;
266 numberOfAllScriptCharacters = 0u;
267 // Initialize whether is right to left direction
268 currentScriptRun.isRightToLeft = false;
271 // Get the next character.
273 endOfText = index == lastCharacter;
276 character = *(textBuffer + index);
277 script = TextAbstraction::GetCharacterScript(character);
279 } // end while( !endOfText && ( TextAbstraction::COMMON == script ) )
283 // Last characters of the text are 'white spaces'.
284 // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
288 // Check if it is the first character of a paragraph.
289 if(isFirstScriptToBeSet &&
290 (TextAbstraction::UNKNOWN != script) &&
291 (TextAbstraction::COMMON != script) &&
292 (TextAbstraction::EMOJI != script))
294 // Sets the direction of the first valid script.
295 isParagraphRTL = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftScript(script);
296 isFirstScriptToBeSet = false;
299 if((script != currentScriptRun.script) &&
300 (TextAbstraction::COMMON != script))
302 // Current run needs to be stored and a new one initialized.
304 if((isParagraphRTL == TextAbstraction::IsRightToLeftScript(currentScriptRun.script)) &&
305 (TextAbstraction::UNKNOWN != currentScriptRun.script))
307 // Previous script has the same direction than the first script of the paragraph.
308 // All the previously skipped characters need to be added to the previous script before it's stored.
309 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
310 numberOfAllScriptCharacters = 0u;
312 else if((TextAbstraction::IsRightToLeftScript(currentScriptRun.script) == TextAbstraction::IsRightToLeftScript(script)) &&
313 (TextAbstraction::UNKNOWN != currentScriptRun.script))
315 // Current script and previous one have the same direction.
316 // All the previously skipped characters need to be added to the previous script before it's stored.
317 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
318 numberOfAllScriptCharacters = 0u;
320 else if((TextAbstraction::UNKNOWN == currentScriptRun.script) &&
321 (TextAbstraction::EMOJI == script))
323 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
324 numberOfAllScriptCharacters = 0u;
327 if(0u != currentScriptRun.characterRun.numberOfCharacters)
329 // Store the script run.
330 scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
334 // Initialize the new one.
335 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
336 currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
337 currentScriptRun.script = script;
338 numberOfAllScriptCharacters = 0u;
339 // Check if whether is right to left script.
340 currentScriptRun.isRightToLeft = TextAbstraction::IsRightToLeftScript(currentScriptRun.script);
344 if(TextAbstraction::UNKNOWN != currentScriptRun.script)
346 // Adds white spaces between characters.
347 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
348 numberOfAllScriptCharacters = 0u;
351 // Add one more character to the run.
352 ++currentScriptRun.characterRun.numberOfCharacters;
356 // Add remaining characters into the last script.
357 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
359 if(0u != currentScriptRun.characterRun.numberOfCharacters)
361 // Store the last run.
362 scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
366 if(scriptIndex < scripts.Count())
368 // Update the indices of the next script runs.
369 const ScriptRun& run = *(scripts.Begin() + scriptIndex - 1u);
370 CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
372 for(Vector<ScriptRun>::Iterator it = scripts.Begin() + scriptIndex,
373 endIt = scripts.End();
377 ScriptRun& run = *it;
378 run.characterRun.characterIndex = nextCharacterIndex;
379 nextCharacterIndex += run.characterRun.numberOfCharacters;
384 void MultilanguageSupport::ValidateFonts(const Vector<Character>& text,
385 const Vector<ScriptRun>& scripts,
386 const Vector<FontDescriptionRun>& fontDescriptions,
387 const TextAbstraction::FontDescription& defaultFontDescription,
388 TextAbstraction::PointSize26Dot6 defaultFontPointSize,
389 CharacterIndex startIndex,
390 Length numberOfCharacters,
391 Vector<FontRun>& fonts)
393 DALI_LOG_INFO(gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n");
395 if(0u == numberOfCharacters)
397 DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
398 // Nothing to do if there are no characters.
402 // Find the first index where to insert the font run.
403 FontRunIndex fontIndex = 0u;
406 for(Vector<FontRun>::ConstIterator it = fonts.Begin(),
411 const FontRun& run = *it;
412 if(startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
420 // Traverse the characters and validate/set the fonts.
423 DefaultFonts** defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
424 ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
426 // Stores the validated font runs.
427 fonts.Reserve(fontDescriptions.Count());
429 // Initializes a validated font run.
430 FontRun currentFontRun;
431 currentFontRun.characterRun.characterIndex = startIndex;
432 currentFontRun.characterRun.numberOfCharacters = 0u;
433 currentFontRun.fontId = 0u;
434 currentFontRun.isBoldRequired = false;
435 currentFontRun.isItalicRequired = false;
437 // Get the font client.
438 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
440 const Character* const textBuffer = text.Begin();
442 // Iterators of the script runs.
443 Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
444 Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
445 bool isNewParagraphCharacter = false;
447 bool isPreviousEmojiScript = false;
449 CharacterIndex lastCharacter = startIndex + numberOfCharacters;
450 for(Length index = startIndex; index < lastCharacter; ++index)
452 // Get the current character.
453 const Character character = *(textBuffer + index);
454 bool isItalicRequired = false;
455 bool isBoldRequired = false;
457 // new description for current character
458 TextAbstraction::FontDescription currentFontDescription;
459 TextAbstraction::PointSize26Dot6 currentFontPointSize = defaultFontPointSize;
460 bool isDefaultFont = true;
461 MergeFontDescriptions(fontDescriptions,
462 defaultFontDescription,
463 defaultFontPointSize,
465 currentFontDescription,
466 currentFontPointSize,
469 // Get the font for the current character.
470 FontId fontId = fontClient.GetFontId(currentFontDescription, currentFontPointSize);
472 // Get the script for the current character.
473 Script script = GetScript(index,
479 Dali::TextAbstraction::FontDescription description;
480 fontClient.GetDescription(fontId, description);
482 DALI_LOG_INFO(gLogFilter,
484 " Initial font set\n Character : %x, Script : %s, Font : %s \n",
486 Dali::TextAbstraction::ScriptName[script],
487 description.path.c_str());
491 // Validate whether the current character is supported by the given font.
492 bool isValidFont = false;
494 // Check first in the cache of default fonts per script and size.
496 FontId cachedDefaultFontId = 0u;
497 DefaultFonts* defaultFonts = *(defaultFontPerScriptCacheBuffer + script);
498 if(NULL != defaultFonts)
500 // This cache stores fall-back fonts.
501 cachedDefaultFontId = defaultFonts->FindFont(fontClient,
502 currentFontDescription,
503 currentFontPointSize);
506 // Whether the cached default font is valid.
507 const bool isValidCachedDefaultFont = 0u != cachedDefaultFontId;
509 // The font is valid if it matches with the default one for the current script and size and it's different than zero.
510 isValidFont = isValidCachedDefaultFont && (fontId == cachedDefaultFontId);
514 // Check if the font supports the character.
515 isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
518 bool isCommonScript = false;
519 bool isEmojiScript = TextAbstraction::EMOJI == script;
521 if(isEmojiScript && !isPreviousEmojiScript)
523 if(0u != currentFontRun.characterRun.numberOfCharacters)
525 // Store the font run.
526 fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
530 // Initialize the new one.
531 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
532 currentFontRun.characterRun.numberOfCharacters = 0u;
533 currentFontRun.fontId = fontId;
534 currentFontRun.isItalicRequired = false;
535 currentFontRun.isBoldRequired = false;
538 // If the given font is not valid, it means either:
539 // - there is no cached font for the current script yet or,
540 // - the user has set a different font than the default one for the current script or,
541 // - the platform default font is different than the default font for the current script.
543 // Need to check if the given font supports the current character.
544 if(!isValidFont) // (1)
546 // Whether the current character is common for all scripts (i.e. white spaces, ...)
548 // Is not desirable to cache fonts for the common script.
550 // i.e. Consider the text " हिंदी", the 'white space' has assigned the DEVANAGARI script.
551 // The user may have set a font or the platform's default is used.
553 // As the 'white space' is the first character, no font is cached so the font validation
554 // retrieves a glyph from the given font.
556 // Many fonts support 'white spaces' so probably the font set by the user or the platform's default
557 // supports the 'white space'. However, that font may not support the DEVANAGARI script.
558 isCommonScript = TextAbstraction::IsCommonScript(character);
560 // Check in the valid fonts cache.
561 ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
563 if(NULL != validateFontsPerScript)
565 // This cache stores valid fonts set by the user.
566 isValidFont = validateFontsPerScript->IsValidFont(fontId);
568 // It may happen that a validated font for a script doesn't have all the glyphs for that script.
569 // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
572 // Checks if the current character is supported by the font is needed.
573 isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
577 if(!isValidFont) // (2)
579 // The selected font is not stored in any cache.
581 // Checks if the current character is supported by the selected font.
582 isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
584 // If there is a valid font, cache it.
585 if(isValidFont && !isCommonScript)
587 if(NULL == validateFontsPerScript)
589 validateFontsPerScript = new ValidateFontsPerScript();
591 *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
594 validateFontsPerScript->mValidFonts.PushBack(fontId);
597 if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
599 // The selected font by the user or the platform's default font has failed to validate the character.
601 // Checks if the previously discarted cached default font supports the character.
602 bool isValidCachedFont = false;
603 if(isValidCachedDefaultFont)
605 isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
608 if(isValidCachedFont)
610 // Use the cached default font for the script if there is one.
611 fontId = cachedDefaultFontId;
615 // There is no valid cached default font for the script.
617 DefaultFonts* defaultFontsPerScript = NULL;
619 // Find a fallback-font.
620 fontId = fontClient.FindFallbackFont(character,
621 currentFontDescription,
622 currentFontPointSize,
627 fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
630 if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
632 // Cache the font if it is not an unknown script
633 if(NULL == defaultFontsPerScript)
635 defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
637 if(NULL == defaultFontsPerScript)
639 defaultFontsPerScript = new DefaultFonts();
640 *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
643 defaultFontsPerScript->Cache(currentFontDescription, fontId);
646 } // !isValidFont (3)
647 } // !isValidFont (2)
648 } // !isValidFont (1)
652 Dali::TextAbstraction::FontDescription description;
653 fontClient.GetDescription(fontId, description);
654 DALI_LOG_INFO(gLogFilter,
656 " Validated font set\n Character : %x, Script : %s, Font : %s \n",
658 Dali::TextAbstraction::ScriptName[script],
659 description.path.c_str());
663 // Whether bols style is required.
664 isBoldRequired = (currentFontDescription.weight >= TextAbstraction::FontWeight::BOLD);
666 // Whether italic style is required.
667 isItalicRequired = (currentFontDescription.slant >= TextAbstraction::FontSlant::ITALIC);
669 // The font is now validated.
670 if((fontId != currentFontRun.fontId) ||
671 isNewParagraphCharacter ||
672 // If font id is same as previous but style is diffrent, initialize new one
673 ((fontId == currentFontRun.fontId) && ((isBoldRequired != currentFontRun.isBoldRequired) || (isItalicRequired != currentFontRun.isItalicRequired))))
675 // Current run needs to be stored and a new one initialized.
677 if(0u != currentFontRun.characterRun.numberOfCharacters)
679 // Store the font run.
680 fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
684 // Initialize the new one.
685 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
686 currentFontRun.characterRun.numberOfCharacters = 0u;
687 currentFontRun.fontId = fontId;
688 currentFontRun.isBoldRequired = isBoldRequired;
689 currentFontRun.isItalicRequired = isItalicRequired;
692 // Add one more character to the run.
693 ++currentFontRun.characterRun.numberOfCharacters;
695 // Whether the current character is a new paragraph character.
696 isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
697 isPreviousEmojiScript = isEmojiScript;
698 } // end traverse characters.
700 if(0u != currentFontRun.characterRun.numberOfCharacters)
702 // Store the last run.
703 fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
707 if(fontIndex < fonts.Count())
709 // Update the indices of the next font runs.
710 const FontRun& run = *(fonts.Begin() + fontIndex - 1u);
711 CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
713 for(Vector<FontRun>::Iterator it = fonts.Begin() + fontIndex,
720 run.characterRun.characterIndex = nextCharacterIndex;
721 nextCharacterIndex += run.characterRun.numberOfCharacters;
725 DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
728 } // namespace Internal
732 } // namespace Toolkit