2 * Copyright (c) 2023 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>
25 #include <dali/integration-api/trace.h>
28 #include <dali-toolkit/internal/text/emoji-helper.h>
29 #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 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false);
43 const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
45 // TODO : Customization required for these values.
46 constexpr std::size_t MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE = 63;
47 constexpr std::size_t MAX_DEFAULT_FONTS_CACHE_SIZE = 15;
49 constexpr int VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT = 8;
50 constexpr int DEFAULT_FONTS_REMAIN_COUNT = 2;
60 void CheckFontSupportsCharacter(
63 const Character& character,
64 ValidateFontsPerScript**& validFontsPerScriptCacheBuffer,
67 TextAbstraction::FontClient& fontClient,
68 const bool isValidCachedDefaultFont,
69 const FontId& cachedDefaultFontId,
70 const TextAbstraction::FontDescription& currentFontDescription,
71 const TextAbstraction::PointSize26Dot6& currentFontPointSize,
72 DefaultFonts**& defaultFontPerScriptCacheBuffer)
74 // Need to check if the given font supports the current character.
75 if(!isValidFont) // (1)
77 // Whether the current character is common for all scripts (i.e. white spaces, ...)
79 // Is not desirable to cache fonts for the common script.
81 // i.e. Consider the text " हिंदी", the 'white space' has assigned the DEVANAGARI script.
82 // The user may have set a font or the platform's default is used.
84 // As the 'white space' is the first character, no font is cached so the font validation
85 // retrieves a glyph from the given font.
87 // Many fonts support 'white spaces' so probably the font set by the user or the platform's default
88 // supports the 'white space'. However, that font may not support the DEVANAGARI script.
89 isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
91 // Check in the valid fonts cache.
92 ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
94 if(NULL != validateFontsPerScript)
96 // This cache stores valid fonts set by the user.
97 isValidFont = validateFontsPerScript->IsValidFont(fontId);
99 // It may happen that a validated font for a script doesn't have all the glyphs for that script.
100 // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
103 // Checks if the current character is supported by the font is needed.
104 isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
108 if(!isValidFont) // (2)
110 // The selected font is not stored in any cache.
112 // Checks if the current character is supported by the selected font.
113 isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
115 // If there is a valid font, cache it.
116 if(isValidFont && !isCommonScript)
118 if(NULL == validateFontsPerScript)
120 validateFontsPerScript = new ValidateFontsPerScript();
122 *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
125 validateFontsPerScript->Cache(fontId);
128 if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
130 // The selected font by the user or the platform's default font has failed to validate the character.
132 // Checks if the previously discarted cached default font supports the character.
133 bool isValidCachedFont = false;
134 if(isValidCachedDefaultFont)
136 isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
139 if(isValidCachedFont)
141 // Use the cached default font for the script if there is one.
142 fontId = cachedDefaultFontId;
147 // There is no valid cached default font for the script.
149 DefaultFonts* defaultFontsPerScript = NULL;
151 // Find a fallback-font.
152 fontId = fontClient.FindFallbackFont(character,
153 currentFontDescription,
154 currentFontPointSize,
159 fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
162 if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
164 // Cache the font if it is not an unknown script
165 if(NULL == defaultFontsPerScript)
167 defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
169 if(NULL == defaultFontsPerScript)
171 defaultFontsPerScript = new DefaultFonts();
172 *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
175 defaultFontsPerScript->Cache(currentFontDescription, fontId);
179 } // !isValidFont (3)
180 } // !isValidFont (2)
181 } // !isValidFont (1)
183 } // unnamed namespace
185 bool ValidateFontsPerScript::IsValidFont(FontId fontId) const
187 for(Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
188 endIt = mValidFonts.End();
200 void ValidateFontsPerScript::Cache(FontId fontId)
202 mValidFonts.PushBack(fontId);
203 if(MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE < mValidFonts.Count())
205 // Clear cache but remaind some last items.
206 const auto offset = mValidFonts.Count() - VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT;
207 for(int i = 0; i < VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT; ++i)
209 mValidFonts[i] = std::move(mValidFonts[offset + i]);
211 mValidFonts.Resize(VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT);
215 FontId DefaultFonts::FindFont(TextAbstraction::FontClient& fontClient,
216 const TextAbstraction::FontDescription& description,
217 PointSize26Dot6 size) const
219 for(std::vector<CacheItem>::const_iterator it = mFonts.begin(),
220 endIt = mFonts.end();
224 const CacheItem& item = *it;
226 if(((TextAbstraction::FontWeight::NONE == description.weight) || (description.weight == item.description.weight)) &&
227 ((TextAbstraction::FontWidth::NONE == description.width) || (description.width == item.description.width)) &&
228 ((TextAbstraction::FontSlant::NONE == description.slant) || (description.slant == item.description.slant)) &&
229 (size == fontClient.GetPointSize(item.fontId)) &&
230 (description.family.empty() || (description.family == item.description.family)))
239 void DefaultFonts::Cache(const TextAbstraction::FontDescription& description, FontId fontId)
242 item.description = description;
243 item.fontId = fontId;
244 mFonts.push_back(item);
245 if(MAX_DEFAULT_FONTS_CACHE_SIZE < mFonts.size())
247 // Clear cache but remaind some last items.
248 const auto offset = mFonts.size() - DEFAULT_FONTS_REMAIN_COUNT;
249 for(int i = 0; i < DEFAULT_FONTS_REMAIN_COUNT; ++i)
251 mFonts[i] = std::move(mFonts[offset + i]);
253 mFonts.resize(DEFAULT_FONTS_REMAIN_COUNT);
257 MultilanguageSupport::MultilanguageSupport()
258 : mDefaultFontPerScriptCache(),
259 mValidFontsPerScriptCache()
261 // Initializes the default font cache to zero (invalid font).
262 // Reserves space to cache the default fonts and access them with the script as an index.
263 mDefaultFontPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
265 // Initializes the valid fonts cache to NULL (no valid fonts).
266 // Reserves space to cache the valid fonts and access them with the script as an index.
267 mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
270 MultilanguageSupport::~MultilanguageSupport()
272 // Destroy the default font per script cache.
273 for(Vector<DefaultFonts*>::Iterator it = mDefaultFontPerScriptCache.Begin(),
274 endIt = mDefaultFontPerScriptCache.End();
281 // Destroy the valid fonts per script cache.
282 for(Vector<ValidateFontsPerScript*>::Iterator it = mValidFontsPerScriptCache.Begin(),
283 endIt = mValidFontsPerScriptCache.End();
291 Text::MultilanguageSupport MultilanguageSupport::Get()
293 Text::MultilanguageSupport multilanguageSupportHandle;
295 SingletonService service(SingletonService::Get());
298 // Check whether the singleton is already created
299 Dali::BaseHandle handle = service.GetSingleton(typeid(Text::MultilanguageSupport));
302 // If so, downcast the handle
303 MultilanguageSupport* impl = dynamic_cast<Internal::MultilanguageSupport*>(handle.GetObjectPtr());
304 multilanguageSupportHandle = Text::MultilanguageSupport(impl);
306 else // create and register the object
308 multilanguageSupportHandle = Text::MultilanguageSupport(new MultilanguageSupport);
309 service.Register(typeid(multilanguageSupportHandle), multilanguageSupportHandle);
313 return multilanguageSupportHandle;
316 void MultilanguageSupport::SetScripts(const Vector<Character>& text,
317 CharacterIndex startIndex,
318 Length numberOfCharacters,
319 Vector<ScriptRun>& scripts)
321 if(0u == numberOfCharacters)
323 // Nothing to do if there are no characters.
327 // Find the first index where to insert the script.
328 ScriptRunIndex scriptIndex = 0u;
331 for(Vector<ScriptRun>::ConstIterator it = scripts.Begin(),
332 endIt = scripts.End();
336 const ScriptRun& run = *it;
337 if(startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
345 // Stores the current script run.
346 ScriptRun currentScriptRun;
347 currentScriptRun.characterRun.characterIndex = startIndex;
348 currentScriptRun.characterRun.numberOfCharacters = 0u;
349 currentScriptRun.script = TextAbstraction::UNKNOWN;
351 // Reserve some space to reduce the number of reallocations.
352 scripts.Reserve(text.Count() << 2u);
354 // Whether the first valid script needs to be set.
355 bool isFirstScriptToBeSet = true;
357 // Whether the first valid script is a right to left script.
358 bool isParagraphRTL = false;
360 // Count the number of characters which are valid for all scripts. i.e. white spaces or '\n'.
361 Length numberOfAllScriptCharacters = 0u;
363 // Pointers to the text buffer.
364 const Character* const textBuffer = text.Begin();
366 // Initialize whether is right to left direction
367 currentScriptRun.isRightToLeft = false;
369 // Traverse all characters and set the scripts.
370 const Length lastCharacter = startIndex + numberOfCharacters - 1u;
372 for(Length index = startIndex; index <= lastCharacter; ++index)
374 Character character = *(textBuffer + index);
376 // Get the script of the character.
377 Script script = TextAbstraction::GetCharacterScript(character);
379 // Some characters (like white spaces) are valid for many scripts. The rules to set a script
381 // - If they are at the begining of a paragraph they get the script of the first character with
382 // a defined script. If they are at the end, they get the script of the last one.
383 // - If they are between two scripts with the same direction, they get the script of the previous
384 // character with a defined script. If the two scripts have different directions, they get the
385 // script of the first character of the paragraph with a defined script.
387 // Skip those characters valid for many scripts like white spaces or '\n'.
388 bool endOfText = index > lastCharacter;
390 //Handle all Emoji Sequence cases
391 if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
393 AddCurrentScriptAndCreatNewScript(script,
397 numberOfAllScriptCharacters,
401 else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
403 currentScriptRun.script = script;
405 else if(IsOneOfEmojiScripts(currentScriptRun.script) && (TextAbstraction::COMMON == script))
407 // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
408 AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
412 numberOfAllScriptCharacters,
418 (TextAbstraction::COMMON == script))
420 // Check if whether is right to left markup and Keeps true if the previous value was true.
421 currentScriptRun.isRightToLeft = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftMark(character);
423 // Count all these characters to be added into a script.
424 ++numberOfAllScriptCharacters;
426 if(TextAbstraction::IsNewParagraph(character))
428 // The character is a new paragraph.
429 // To know when there is a new paragraph is needed because if there is a white space
430 // between two scripts with different directions, it is added to the script with
431 // the same direction than the first script of the paragraph.
432 isFirstScriptToBeSet = true;
434 AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
438 numberOfAllScriptCharacters,
443 // Get the next character.
445 endOfText = index > lastCharacter;
448 character = *(textBuffer + index);
449 script = TextAbstraction::GetCharacterScript(character);
451 //Handle all Emoji Sequence cases
452 if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
454 AddCurrentScriptAndCreatNewScript(script,
458 numberOfAllScriptCharacters,
462 else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
464 currentScriptRun.script = script;
467 } // end while( !endOfText && ( TextAbstraction::COMMON == script ) )
471 // Last characters of the text are 'white spaces'.
472 // There is nothing else to do. Just add the remaining characters to the last script after this bucle.
476 // Check if it is the first character of a paragraph.
477 if(isFirstScriptToBeSet &&
478 (TextAbstraction::UNKNOWN != script) &&
479 (TextAbstraction::COMMON != script) &&
480 (TextAbstraction::EMOJI != script) &&
481 (TextAbstraction::EMOJI_TEXT != script) &&
482 (TextAbstraction::EMOJI_COLOR != script) &&
483 (!TextAbstraction::IsSymbolScript(script)))
485 // Sets the direction of the first valid script.
486 isParagraphRTL = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftScript(script);
487 isFirstScriptToBeSet = false;
490 if((script != currentScriptRun.script) &&
491 (TextAbstraction::COMMON != script))
493 // Current run needs to be stored and a new one initialized.
495 if((isParagraphRTL == TextAbstraction::IsRightToLeftScript(currentScriptRun.script)) &&
496 (TextAbstraction::UNKNOWN != currentScriptRun.script))
498 // Previous script has the same direction than the first script of the paragraph.
499 // All the previously skipped characters need to be added to the previous script before it's stored.
500 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
501 numberOfAllScriptCharacters = 0u;
503 else if((TextAbstraction::IsRightToLeftScript(currentScriptRun.script) == TextAbstraction::IsRightToLeftScript(script)) &&
504 (TextAbstraction::UNKNOWN != currentScriptRun.script))
506 // Current script and previous one have the same direction.
507 // All the previously skipped characters need to be added to the previous script before it's stored.
508 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
509 numberOfAllScriptCharacters = 0u;
511 else if((TextAbstraction::UNKNOWN == currentScriptRun.script) &&
512 (TextAbstraction::IsSymbolOrEmojiOrTextScript(script)))
514 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
515 numberOfAllScriptCharacters = 0u;
518 // Adds the white spaces which are at the begining of the script.
519 numberOfAllScriptCharacters++;
520 AddCurrentScriptAndCreatNewScript(script,
521 TextAbstraction::IsRightToLeftScript(script),
524 numberOfAllScriptCharacters,
530 if(TextAbstraction::UNKNOWN != currentScriptRun.script)
532 // Adds white spaces between characters.
533 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
534 numberOfAllScriptCharacters = 0u;
537 // Add one more character to the run.
538 ++currentScriptRun.characterRun.numberOfCharacters;
542 // Add remaining characters into the last script.
543 currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
545 if(0u != currentScriptRun.characterRun.numberOfCharacters)
547 // Store the last run.
548 scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
552 if(scriptIndex < scripts.Count())
554 // Update the indices of the next script runs.
555 const ScriptRun& run = *(scripts.Begin() + scriptIndex - 1u);
556 CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
558 for(Vector<ScriptRun>::Iterator it = scripts.Begin() + scriptIndex,
559 endIt = scripts.End();
563 ScriptRun& run = *it;
564 run.characterRun.characterIndex = nextCharacterIndex;
565 nextCharacterIndex += run.characterRun.numberOfCharacters;
570 void MultilanguageSupport::ValidateFonts(const Vector<Character>& text,
571 const Vector<ScriptRun>& scripts,
572 const Vector<FontDescriptionRun>& fontDescriptions,
573 const TextAbstraction::FontDescription& defaultFontDescription,
574 TextAbstraction::PointSize26Dot6 defaultFontPointSize,
576 CharacterIndex startIndex,
577 Length numberOfCharacters,
578 Vector<FontRun>& fonts)
580 DALI_LOG_INFO(gLogFilter, Debug::General, "-->MultilanguageSupport::ValidateFonts\n");
582 if(0u == numberOfCharacters)
584 DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
585 // Nothing to do if there are no characters.
589 DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FONTS_VALIDATE");
591 // Find the first index where to insert the font run.
592 FontRunIndex fontIndex = 0u;
595 for(Vector<FontRun>::ConstIterator it = fonts.Begin(),
600 const FontRun& run = *it;
601 if(startIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
609 // Traverse the characters and validate/set the fonts.
612 DefaultFonts** defaultFontPerScriptCacheBuffer = mDefaultFontPerScriptCache.Begin();
613 ValidateFontsPerScript** validFontsPerScriptCacheBuffer = mValidFontsPerScriptCache.Begin();
615 // Stores the validated font runs.
616 fonts.Reserve(fontDescriptions.Count());
618 // Initializes a validated font run.
619 FontRun currentFontRun;
620 currentFontRun.characterRun.characterIndex = startIndex;
621 currentFontRun.characterRun.numberOfCharacters = 0u;
622 currentFontRun.fontId = 0u;
623 currentFontRun.isBoldRequired = false;
624 currentFontRun.isItalicRequired = false;
626 // Get the font client.
627 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
629 const Character* const textBuffer = text.Begin();
631 // Iterators of the script runs.
632 Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
633 Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
634 bool isNewParagraphCharacter = false;
636 FontId currentFontId = 0u;
637 FontId previousFontId = 0u;
638 TextAbstraction::Script previousScript = TextAbstraction::UNKNOWN;
640 CharacterIndex lastCharacter = startIndex + numberOfCharacters - 1u;
641 for(Length index = startIndex; index <= lastCharacter; ++index)
643 // Get the current character.
644 const Character character = *(textBuffer + index);
645 bool isItalicRequired = false;
646 bool isBoldRequired = false;
648 // new description for current character
649 TextAbstraction::FontDescription currentFontDescription;
650 TextAbstraction::PointSize26Dot6 currentFontPointSize = defaultFontPointSize;
651 bool isDefaultFont = true;
652 MergeFontDescriptions(fontDescriptions,
653 defaultFontDescription,
654 defaultFontPointSize,
657 currentFontDescription,
658 currentFontPointSize,
661 // Get the font for the current character.
662 FontId fontId = fontClient.GetFontId(currentFontDescription, currentFontPointSize);
663 currentFontId = fontId;
665 // Get the script for the current character.
666 Script script = GetScript(index,
671 if(gLogFilter->IsEnabledFor(Debug::Verbose))
673 Dali::TextAbstraction::FontDescription description;
674 fontClient.GetDescription(fontId, description);
676 DALI_LOG_INFO(gLogFilter,
678 " Initial font set\n Character : %x, Script : %s, Font : %s \n",
680 Dali::TextAbstraction::ScriptName[script],
681 description.path.c_str());
685 // Validate whether the current character is supported by the given font.
686 bool isValidFont = false;
688 // Check first in the cache of default fonts per script and size.
690 FontId cachedDefaultFontId = 0u;
691 DefaultFonts* defaultFonts = *(defaultFontPerScriptCacheBuffer + script);
692 if(NULL != defaultFonts)
694 // This cache stores fall-back fonts.
695 cachedDefaultFontId = defaultFonts->FindFont(fontClient,
696 currentFontDescription,
697 currentFontPointSize);
700 // Whether the cached default font is valid.
701 const bool isValidCachedDefaultFont = 0u != cachedDefaultFontId;
703 // The font is valid if it matches with the default one for the current script and size and it's different than zero.
704 isValidFont = isValidCachedDefaultFont && (fontId == cachedDefaultFontId);
708 // Check if the font supports the character.
709 isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
712 bool isEmojiScript = IsEmojiColorScript(script) || IsEmojiTextScript(script);
713 bool isZWJ = TextAbstraction::IsZeroWidthJoiner(character);
715 if((previousScript == script) &&
716 (isEmojiScript || isZWJ))
718 // This sequence should use the previous font.
719 if(0u != previousFontId)
721 fontId = previousFontId;
726 if(TextAbstraction::IsSpace(character) &&
727 TextAbstraction::HasLigatureMustBreak(script) &&
728 isValidCachedDefaultFont &&
729 (isDefaultFont || (currentFontId == previousFontId)))
731 fontId = cachedDefaultFontId;
735 // This is valid after CheckFontSupportsCharacter();
736 bool isCommonScript = false;
738 // If the given font is not valid, it means either:
739 // - there is no cached font for the current script yet or,
740 // - the user has set a different font than the default one for the current script or,
741 // - the platform default font is different than the default font for the current script.
743 // Need to check if the given font supports the current character.
744 CheckFontSupportsCharacter(isValidFont, isCommonScript, character, validFontsPerScriptCacheBuffer, script, fontId, fontClient,
745 isValidCachedDefaultFont, cachedDefaultFontId, currentFontDescription, currentFontPointSize, defaultFontPerScriptCacheBuffer);
747 if(isEmojiScript && (previousScript != script))
749 //New Emoji sequence should select font according to the variation selector (VS15 or VS16).
750 if(0u != currentFontRun.characterRun.numberOfCharacters)
752 // Store the font run.
753 fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
757 // Initialize the new one.
758 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
759 currentFontRun.characterRun.numberOfCharacters = 0u;
760 currentFontRun.fontId = fontId;
761 currentFontRun.isItalicRequired = false;
762 currentFontRun.isBoldRequired = false;
764 if(TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script))
766 bool isModifiedByVariationSelector = false;
767 GlyphIndex glyphIndexChar = fontClient.GetGlyphIndex(fontId, character);
768 GlyphIndex glyphIndexCharByVS = fontClient.GetGlyphIndex(fontId, character, Text::GetVariationSelectorByScript(script));
770 isModifiedByVariationSelector = glyphIndexChar != glyphIndexCharByVS;
772 if(isModifiedByVariationSelector)
774 FontId requestedFontId = fontClient.FindDefaultFont(character, currentFontPointSize, IsEmojiColorScript(script));
775 if(0u != requestedFontId)
777 currentFontRun.fontId = fontId = requestedFontId;
785 if(gLogFilter->IsEnabledFor(Debug::Verbose))
787 Dali::TextAbstraction::FontDescription description;
788 fontClient.GetDescription(fontId, description);
789 DALI_LOG_INFO(gLogFilter,
791 " Validated font set\n Character : %x, Script : %s, Font : %s \n",
793 Dali::TextAbstraction::ScriptName[script],
794 description.path.c_str());
797 if(!isValidFont && !isCommonScript)
799 Dali::TextAbstraction::FontDescription descriptionForLog;
800 fontClient.GetDescription(fontId, descriptionForLog);
801 DALI_LOG_RELEASE_INFO("Validated font set fail : Character : %x, Script : %s, Font : %s \n",
803 Dali::TextAbstraction::ScriptName[script],
804 descriptionForLog.path.c_str());
807 // Whether bols style is required.
808 isBoldRequired = (currentFontDescription.weight >= TextAbstraction::FontWeight::BOLD);
810 // Whether italic style is required.
811 isItalicRequired = (currentFontDescription.slant >= TextAbstraction::FontSlant::ITALIC);
813 // The font is now validated.
814 if((fontId != currentFontRun.fontId) ||
815 isNewParagraphCharacter ||
816 // If font id is same as previous but style is diffrent, initialize new one
817 ((fontId == currentFontRun.fontId) && ((isBoldRequired != currentFontRun.isBoldRequired) || (isItalicRequired != currentFontRun.isItalicRequired))))
819 // Current run needs to be stored and a new one initialized.
821 if(0u != currentFontRun.characterRun.numberOfCharacters)
823 // Store the font run.
824 fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
828 // Initialize the new one.
829 currentFontRun.characterRun.characterIndex = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
830 currentFontRun.characterRun.numberOfCharacters = 0u;
831 currentFontRun.fontId = fontId;
832 currentFontRun.isBoldRequired = isBoldRequired;
833 currentFontRun.isItalicRequired = isItalicRequired;
836 // Add one more character to the run.
837 ++currentFontRun.characterRun.numberOfCharacters;
839 // Whether the current character is a new paragraph character.
840 isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
841 previousScript = script;
842 currentFontId = fontId;
843 previousFontId = currentFontId;
844 } // end traverse characters.
846 if(0u != currentFontRun.characterRun.numberOfCharacters)
848 // Store the last run.
849 fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
853 if(fontIndex < fonts.Count())
855 // Update the indices of the next font runs.
856 const FontRun& run = *(fonts.Begin() + fontIndex - 1u);
857 CharacterIndex nextCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters;
859 for(Vector<FontRun>::Iterator it = fonts.Begin() + fontIndex,
866 run.characterRun.characterIndex = nextCharacterIndex;
867 nextCharacterIndex += run.characterRun.numberOfCharacters;
871 DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
874 void MultilanguageSupport::AddCurrentScriptAndCreatNewScript(const Script requestedScript,
875 const bool isRightToLeft,
876 const bool addScriptCharactersToNewScript,
877 ScriptRun& currentScriptRun,
878 Length& numberOfAllScriptCharacters,
879 Vector<ScriptRun>& scripts,
880 ScriptRunIndex& scriptIndex)
882 // Add the pending characters to the current script
883 currentScriptRun.characterRun.numberOfCharacters += (addScriptCharactersToNewScript ? 0u : numberOfAllScriptCharacters);
885 // In-case the current script is empty then no need to add it for scripts
886 if(0u != currentScriptRun.characterRun.numberOfCharacters)
888 // Store the script run.
889 scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
893 // Initialize the new one by the requested script
894 currentScriptRun.characterRun.characterIndex = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
895 currentScriptRun.characterRun.numberOfCharacters = (addScriptCharactersToNewScript ? numberOfAllScriptCharacters : 0u);
896 currentScriptRun.script = requestedScript;
897 numberOfAllScriptCharacters = 0u;
898 // Initialize whether is right to left direction
899 currentScriptRun.isRightToLeft = isRightToLeft;
902 } // namespace Internal
906 } // namespace Toolkit