/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali/devel-api/common/singleton-service.h>
#include <dali/devel-api/text-abstraction/font-client.h>
#include <dali/integration-api/debug.h>
+#include <dali/integration-api/trace.h>
// INTERNAL INCLUDES
#include <dali-toolkit/internal/text/emoji-helper.h>
Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_MULTI_LANGUAGE_SUPPORT");
#endif
+DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false);
+
const Dali::Toolkit::Text::Character UTF32_A = 0x0041;
+
+// TODO : Customization required for these values.
+constexpr std::size_t MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE = 63;
+constexpr std::size_t MAX_DEFAULT_FONTS_CACHE_SIZE = 15;
+
+constexpr int VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT = 8;
+constexpr int DEFAULT_FONTS_REMAIN_COUNT = 2;
} // namespace
namespace Text
{
namespace Internal
{
+
+namespace
+{
+void CheckFontSupportsCharacter(
+ bool& isValidFont,
+ bool& isCommonScript,
+ const Character& character,
+ ValidateFontsPerScript**& validFontsPerScriptCacheBuffer,
+ const Script& script,
+ FontId& fontId,
+ TextAbstraction::FontClient& fontClient,
+ const bool isValidCachedDefaultFont,
+ const FontId& cachedDefaultFontId,
+ const TextAbstraction::FontDescription& currentFontDescription,
+ const TextAbstraction::PointSize26Dot6& currentFontPointSize,
+ DefaultFonts**& defaultFontPerScriptCacheBuffer)
+{
+ // Need to check if the given font supports the current character.
+ if(!isValidFont) // (1)
+ {
+ // Whether the current character is common for all scripts (i.e. white spaces, ...)
+
+ // Is not desirable to cache fonts for the common script.
+ //
+ // i.e. Consider the text " हिंदी", the 'white space' has assigned the DEVANAGARI script.
+ // The user may have set a font or the platform's default is used.
+ //
+ // As the 'white space' is the first character, no font is cached so the font validation
+ // retrieves a glyph from the given font.
+ //
+ // Many fonts support 'white spaces' so probably the font set by the user or the platform's default
+ // supports the 'white space'. However, that font may not support the DEVANAGARI script.
+ isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
+
+ // Check in the valid fonts cache.
+ ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
+
+ if(NULL != validateFontsPerScript)
+ {
+ // This cache stores valid fonts set by the user.
+ isValidFont = validateFontsPerScript->IsValidFont(fontId);
+
+ // It may happen that a validated font for a script doesn't have all the glyphs for that script.
+ // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
+ if(isValidFont)
+ {
+ // Checks if the current character is supported by the font is needed.
+ isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
+ }
+ }
+
+ if(!isValidFont) // (2)
+ {
+ // The selected font is not stored in any cache.
+
+ // Checks if the current character is supported by the selected font.
+ isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
+
+ // If there is a valid font, cache it.
+ if(isValidFont && !isCommonScript)
+ {
+ if(NULL == validateFontsPerScript)
+ {
+ validateFontsPerScript = new ValidateFontsPerScript();
+
+ *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
+ }
+
+ validateFontsPerScript->Cache(fontId);
+ }
+
+ if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
+ {
+ // The selected font by the user or the platform's default font has failed to validate the character.
+
+ // Checks if the previously discarted cached default font supports the character.
+ bool isValidCachedFont = false;
+ if(isValidCachedDefaultFont)
+ {
+ isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
+ }
+
+ if(isValidCachedFont)
+ {
+ // Use the cached default font for the script if there is one.
+ fontId = cachedDefaultFontId;
+ isValidFont = true;
+ }
+ else
+ {
+ // There is no valid cached default font for the script.
+
+ DefaultFonts* defaultFontsPerScript = NULL;
+
+ // Find a fallback-font.
+ fontId = fontClient.FindFallbackFont(character,
+ currentFontDescription,
+ currentFontPointSize,
+ false);
+
+ if(0u == fontId)
+ {
+ fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
+ }
+
+ if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
+ {
+ // Cache the font if it is not an unknown script
+ if(NULL == defaultFontsPerScript)
+ {
+ defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
+
+ if(NULL == defaultFontsPerScript)
+ {
+ defaultFontsPerScript = new DefaultFonts();
+ *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
+ }
+ }
+ defaultFontsPerScript->Cache(currentFontDescription, fontId);
+ isValidFont = true;
+ }
+ }
+ } // !isValidFont (3)
+ } // !isValidFont (2)
+ } // !isValidFont (1)
+}
+} // unnamed namespace
+
bool ValidateFontsPerScript::IsValidFont(FontId fontId) const
{
for(Vector<FontId>::ConstIterator it = mValidFonts.Begin(),
return false;
}
+void ValidateFontsPerScript::Cache(FontId fontId)
+{
+ mValidFonts.PushBack(fontId);
+ if(MAX_VALIDATE_FONTS_PER_SCRIPT_CACHE_SIZE < mValidFonts.Count())
+ {
+ // Clear cache but remaind some last items.
+ const auto offset = mValidFonts.Count() - VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT;
+ for(int i = 0; i < VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT; ++i)
+ {
+ mValidFonts[i] = std::move(mValidFonts[offset + i]);
+ }
+ mValidFonts.Resize(VALIDATE_FONTS_PER_SCRIPT_REMAIN_COUNT);
+ }
+}
FontId DefaultFonts::FindFont(TextAbstraction::FontClient& fontClient,
const TextAbstraction::FontDescription& description,
item.description = description;
item.fontId = fontId;
mFonts.push_back(item);
+ if(MAX_DEFAULT_FONTS_CACHE_SIZE < mFonts.size())
+ {
+ // Clear cache but remaind some last items.
+ const auto offset = mFonts.size() - DEFAULT_FONTS_REMAIN_COUNT;
+ for(int i = 0; i < DEFAULT_FONTS_REMAIN_COUNT; ++i)
+ {
+ mFonts[i] = std::move(mFonts[offset + i]);
+ }
+ mFonts.resize(DEFAULT_FONTS_REMAIN_COUNT);
+ }
}
MultilanguageSupport::MultilanguageSupport()
currentScriptRun.isRightToLeft = false;
// Traverse all characters and set the scripts.
- const Length lastCharacter = startIndex + numberOfCharacters;
+ const Length lastCharacter = startIndex + numberOfCharacters - 1u;
- for(Length index = startIndex; index < lastCharacter; ++index)
+ for(Length index = startIndex; index <= lastCharacter; ++index)
{
Character character = *(textBuffer + index);
// script of the first character of the paragraph with a defined script.
// Skip those characters valid for many scripts like white spaces or '\n'.
- bool endOfText = index == lastCharacter;
+ bool endOfText = index > lastCharacter;
//Handle all Emoji Sequence cases
if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
// Get the next character.
++index;
- endOfText = index == lastCharacter;
+ endOfText = index > lastCharacter;
if(!endOfText)
{
character = *(textBuffer + index);
const Vector<FontDescriptionRun>& fontDescriptions,
const TextAbstraction::FontDescription& defaultFontDescription,
TextAbstraction::PointSize26Dot6 defaultFontPointSize,
+ float fontSizeScale,
CharacterIndex startIndex,
Length numberOfCharacters,
Vector<FontRun>& fonts)
return;
}
+ DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FONTS_VALIDATE");
+
// Find the first index where to insert the font run.
FontRunIndex fontIndex = 0u;
if(0u != startIndex)
Vector<ScriptRun>::ConstIterator scriptRunEndIt = scripts.End();
bool isNewParagraphCharacter = false;
- FontId previousEmojiFontId = 0u;
+ FontId currentFontId = 0u;
+ FontId previousFontId = 0u;
TextAbstraction::Script previousScript = TextAbstraction::UNKNOWN;
- CharacterIndex lastCharacter = startIndex + numberOfCharacters;
- for(Length index = startIndex; index < lastCharacter; ++index)
+ CharacterIndex lastCharacter = startIndex + numberOfCharacters - 1u;
+ for(Length index = startIndex; index <= lastCharacter; ++index)
{
// Get the current character.
const Character character = *(textBuffer + index);
MergeFontDescriptions(fontDescriptions,
defaultFontDescription,
defaultFontPointSize,
+ fontSizeScale,
index,
currentFontDescription,
currentFontPointSize,
// Get the font for the current character.
FontId fontId = fontClient.GetFontId(currentFontDescription, currentFontPointSize);
+ currentFontId = fontId;
// Get the script for the current character.
Script script = GetScript(index,
scriptRunEndIt);
#ifdef DEBUG_ENABLED
+ if(gLogFilter->IsEnabledFor(Debug::Verbose))
{
Dali::TextAbstraction::FontDescription description;
fontClient.GetDescription(fontId, description);
isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
}
- bool isCommonScript = false;
- bool isEmojiScript = TextAbstraction::IsEmojiScript(script) || TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script);
+ bool isEmojiScript = IsEmojiColorScript(script) || IsEmojiTextScript(script);
+ bool isZWJ = TextAbstraction::IsZeroWidthJoiner(character);
- if(isEmojiScript && (previousScript == script))
+ if((previousScript == script) &&
+ (isEmojiScript || isZWJ))
{
- // Emoji sequence should use the previous emoji font.
- if(0u != previousEmojiFontId)
+ // This sequence should use the previous font.
+ if(0u != previousFontId)
{
- fontId = previousEmojiFontId;
+ fontId = previousFontId;
isValidFont = true;
}
}
+ if(TextAbstraction::IsSpace(character) &&
+ TextAbstraction::HasLigatureMustBreak(script) &&
+ isValidCachedDefaultFont &&
+ (isDefaultFont || (currentFontId == previousFontId)))
+ {
+ fontId = cachedDefaultFontId;
+ isValidFont = true;
+ }
+
+ // This is valid after CheckFontSupportsCharacter();
+ bool isCommonScript = false;
+
// If the given font is not valid, it means either:
// - there is no cached font for the current script yet or,
// - the user has set a different font than the default one for the current script or,
// - the platform default font is different than the default font for the current script.
// Need to check if the given font supports the current character.
- if(!isValidFont) // (1)
- {
- // Whether the current character is common for all scripts (i.e. white spaces, ...)
-
- // Is not desirable to cache fonts for the common script.
- //
- // i.e. Consider the text " हिंदी", the 'white space' has assigned the DEVANAGARI script.
- // The user may have set a font or the platform's default is used.
- //
- // As the 'white space' is the first character, no font is cached so the font validation
- // retrieves a glyph from the given font.
- //
- // Many fonts support 'white spaces' so probably the font set by the user or the platform's default
- // supports the 'white space'. However, that font may not support the DEVANAGARI script.
- isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
-
- // Check in the valid fonts cache.
- ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
-
- if(NULL != validateFontsPerScript)
- {
- // This cache stores valid fonts set by the user.
- isValidFont = validateFontsPerScript->IsValidFont(fontId);
-
- // It may happen that a validated font for a script doesn't have all the glyphs for that script.
- // i.e a font validated for the CJK script may contain glyphs for the chinese language but not for the Japanese.
- if(isValidFont)
- {
- // Checks if the current character is supported by the font is needed.
- isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
- }
- }
-
- if(!isValidFont) // (2)
- {
- // The selected font is not stored in any cache.
-
- // Checks if the current character is supported by the selected font.
- isValidFont = fontClient.IsCharacterSupportedByFont(fontId, character);
-
- // If there is a valid font, cache it.
- if(isValidFont && !isCommonScript)
- {
- if(NULL == validateFontsPerScript)
- {
- validateFontsPerScript = new ValidateFontsPerScript();
-
- *(validFontsPerScriptCacheBuffer + script) = validateFontsPerScript;
- }
-
- validateFontsPerScript->mValidFonts.PushBack(fontId);
- }
-
- if(!isValidFont && (fontId != cachedDefaultFontId) && (!TextAbstraction::IsNewParagraph(character))) // (3)
- {
- // The selected font by the user or the platform's default font has failed to validate the character.
-
- // Checks if the previously discarted cached default font supports the character.
- bool isValidCachedFont = false;
- if(isValidCachedDefaultFont)
- {
- isValidCachedFont = fontClient.IsCharacterSupportedByFont(cachedDefaultFontId, character);
- }
-
- if(isValidCachedFont)
- {
- // Use the cached default font for the script if there is one.
- fontId = cachedDefaultFontId;
- }
- else
- {
- // There is no valid cached default font for the script.
-
- DefaultFonts* defaultFontsPerScript = NULL;
-
- // Find a fallback-font.
- fontId = fontClient.FindFallbackFont(character,
- currentFontDescription,
- currentFontPointSize,
- false);
-
- if(0u == fontId)
- {
- fontId = fontClient.FindDefaultFont(UTF32_A, currentFontPointSize);
- }
-
- if(!isCommonScript && (script != TextAbstraction::UNKNOWN))
- {
- // Cache the font if it is not an unknown script
- if(NULL == defaultFontsPerScript)
- {
- defaultFontsPerScript = *(defaultFontPerScriptCacheBuffer + script);
-
- if(NULL == defaultFontsPerScript)
- {
- defaultFontsPerScript = new DefaultFonts();
- *(defaultFontPerScriptCacheBuffer + script) = defaultFontsPerScript;
- }
- }
- defaultFontsPerScript->Cache(currentFontDescription, fontId);
- }
- }
- } // !isValidFont (3)
- } // !isValidFont (2)
- } // !isValidFont (1)
+ CheckFontSupportsCharacter(isValidFont, isCommonScript, character, validFontsPerScriptCacheBuffer, script, fontId, fontClient,
+ isValidCachedDefaultFont, cachedDefaultFontId, currentFontDescription, currentFontPointSize, defaultFontPerScriptCacheBuffer);
if(isEmojiScript && (previousScript != script))
{
}
}
- // Store the font id when the first character is an emoji.
- if(isEmojiScript)
- {
- if(0u != fontId && previousScript != script)
- {
- previousEmojiFontId = fontId;
- }
- }
- else
- {
- previousEmojiFontId = 0u;
- }
-
#ifdef DEBUG_ENABLED
+ if(gLogFilter->IsEnabledFor(Debug::Verbose))
{
Dali::TextAbstraction::FontDescription description;
fontClient.GetDescription(fontId, description);
description.path.c_str());
}
#endif
+ if(!isValidFont && !isCommonScript)
+ {
+ Dali::TextAbstraction::FontDescription descriptionForLog;
+ fontClient.GetDescription(fontId, descriptionForLog);
+ DALI_LOG_RELEASE_INFO("Validated font set fail : Character : %x, Script : %s, Font : %s \n",
+ character,
+ Dali::TextAbstraction::ScriptName[script],
+ descriptionForLog.path.c_str());
+ }
// Whether bols style is required.
isBoldRequired = (currentFontDescription.weight >= TextAbstraction::FontWeight::BOLD);
// Whether the current character is a new paragraph character.
isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
previousScript = script;
+ currentFontId = fontId;
+ previousFontId = currentFontId;
} // end traverse characters.
if(0u != currentFontRun.characterRun.numberOfCharacters)