/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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/text-abstraction/font-list.h>
#include <dali/integration-api/debug.h>
#include <dali/integration-api/platform-abstraction.h>
+#include <dali/integration-api/trace.h>
#include <dali/internal/adaptor/common/adaptor-impl.h>
#include <dali/internal/imaging/common/image-operations.h>
+#include <dali/internal/system/common/logging.h>
#include <dali/internal/text/text-abstraction/plugin/bitmap-font-cache-item.h>
#include <dali/internal/text/text-abstraction/plugin/embedded-item.h>
#include <dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.h>
#include <algorithm>
#include <iterator>
+// Use this macro only if need to log messages before the log function is set.
+#define FONT_LOG_MESSAGE(level, format, ...) \
+ do \
+ { \
+ char buffer[256]; \
+ int result = std::snprintf(buffer, sizeof(buffer), format, ##__VA_ARGS__); \
+ if(result >= static_cast<int>(sizeof(buffer))) \
+ { \
+ std::string log("Font log message is too long to fit in the buffer.\n"); \
+ Dali::TizenPlatform::LogMessage(Dali::Integration::Log::ERROR, log); \
+ break; \
+ } \
+ std::string log(buffer); \
+ Dali::TizenPlatform::LogMessage(level, log); \
+ } while(0)
+
#if defined(DEBUG_ENABLED)
// Note, to turn on trace and verbose logging, use "export LOG_FONT_CLIENT=3,true"
namespace
{
+DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false);
+
/**
* Conversion from Fractional26.6 to float
*/
error = FT_Set_Char_Size(ftFace,
0,
- requestedPointSize,
+ FT_F26Dot6(requestedPointSize),
horizontalDpi,
verticalDpi);
mCacheHandler->ClearCache();
}
+void FontClient::Plugin::ClearCacheOnLocaleChanged() const
+{
+ mCacheHandler->ClearCacheOnLocaleChanged();
+}
+
void FontClient::Plugin::SetDpi(unsigned int horizontalDpi,
unsigned int verticalDpi)
{
mCacheHandler->ResetSystemDefaults();
}
+void FontClient::Plugin::CacheFontDataFromFile(const std::string& fontPath) const
+{
+ if(fontPath.empty())
+ {
+ return;
+ }
+
+ if(mCacheHandler->FindFontData(fontPath))
+ {
+ // Font data is already cached, no need to reload
+ return;
+ }
+
+ Dali::Vector<uint8_t> fontDataBuffer;
+ std::streampos dataSize = 0;
+ if(!mCacheHandler->LoadFontDataFromFile(fontPath, fontDataBuffer, dataSize))
+ {
+ fontDataBuffer.Clear();
+ FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "Failed to load font data : %s\n", fontPath.c_str());
+ return;
+ }
+
+ // Cache font data
+ mCacheHandler->CacheFontData(fontPath, fontDataBuffer, dataSize);
+}
+
+void FontClient::Plugin::CacheFontFaceFromFile(const std::string& fontPath) const
+{
+ if(fontPath.empty())
+ {
+ return;
+ }
+
+ if(mCacheHandler->FindFontFace(fontPath))
+ {
+ // Font face is already cached, no need to reload
+ return;
+ }
+
+ FT_Face ftFace;
+ int error = FT_New_Face(mFreeTypeLibrary, fontPath.c_str(), 0, &ftFace);
+ if(FT_Err_Ok != error)
+ {
+ FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "Failed to load font face : %s\n", fontPath.c_str());
+ return;
+ }
+
+ // Cache font face
+ mCacheHandler->CacheFontFace(fontPath, ftFace);
+ FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "PreLoad font new face : %s\n", fontPath.c_str());
+}
+
+void FontClient::Plugin::FontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList) const
+{
+ for(const auto& fontPath : fontPathList)
+ {
+ CacheFontFaceFromFile(fontPath);
+ }
+
+ for(const auto& memoryFontPath : memoryFontPathList)
+ {
+ CacheFontDataFromFile(memoryFontPath);
+ }
+}
+
+void FontClient::Plugin::FontPreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily) const
+{
+ mCacheHandler->InitDefaultFontDescription();
+
+ FontFamilyList familyList;
+ familyList.reserve(extraFamilyList.size() + 1);
+
+ for(const auto& fallbackFont : fallbackFamilyList)
+ {
+ FontList* fontList = nullptr;
+ CharacterSetList* characterSetList = nullptr;
+ FontDescriptionId fontDescriptionId = 0u;
+ FontDescription fontDescription;
+ fontDescription.family = FontFamily(fallbackFont);
+ fontDescription.weight = DefaultFontWeight();
+ fontDescription.width = DefaultFontWidth();
+ fontDescription.slant = DefaultFontSlant();
+
+ if(!mCacheHandler->FindFallbackFontList(fontDescription, fontList, characterSetList))
+ {
+ FontDescription copiedFontDescription = fontDescription;
+ mCacheHandler->CacheFallbackFontList(std::move(copiedFontDescription), fontList, characterSetList);
+ }
+ if(!mCacheHandler->FindValidatedFont(fontDescription, fontDescriptionId))
+ {
+ mCacheHandler->ValidateFont(fontDescription, fontDescriptionId);
+ }
+
+ if(extraFamilyList.empty() && localeFamily.empty())
+ {
+ continue;
+ }
+
+ familyList.clear();
+ familyList.insert(familyList.end(), extraFamilyList.begin(), extraFamilyList.end());
+ if(!localeFamily.empty())
+ {
+ familyList.push_back(localeFamily);
+ }
+
+ for(const auto& font : *fontList)
+ {
+ auto it = std::find(familyList.begin(), familyList.end(), font.family);
+ if(it != familyList.end())
+ {
+ if(!mCacheHandler->FindValidatedFont(font, fontDescriptionId))
+ {
+ mCacheHandler->ValidateFont(font, fontDescriptionId);
+ }
+ familyList.erase(it);
+ }
+ }
+ }
+}
+
+void FontClient::Plugin::InitDefaultFontDescription() const
+{
+ mCacheHandler->InitDefaultFontDescription();
+}
+
void FontClient::Plugin::GetDefaultPlatformFontDescription(FontDescription& fontDescription) const
{
DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
FONT_LOG_REQUEST(charcode, requestedPointSize, preferColor);
+ DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FIND_FALLBACKFONT");
+
// The font id to be returned.
FontId fontId = 0u;
DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontWidth::Name[preferredFontDescription.width], FontWidth::Name[fontDescription.width]);
DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontSlant::Name[preferredFontDescription.slant], FontSlant::Name[fontDescription.slant]);
+#if defined(TRACE_ENABLED)
+ if(gTraceFilter && gTraceFilter->IsTraceEnabled())
+ {
+ DALI_LOG_DEBUG_INFO("DALI_TEXT_FIND_FALLBACKFONT : %s -> %s\n", preferredFontDescription.family.c_str(), fontDescription.family.c_str());
+ }
+#endif
+
// Check first if the font's description has been queried before.
FontList* fontList = nullptr;
CharacterSetList* characterSetList = nullptr;
// Check if exists a pair 'fontDescriptionId, requestedPointSize' in the cache.
if(!mCacheHandler->FindFont(fontDescriptionId, requestedPointSize, fontCacheIndex))
{
- // Retrieve the font file name path.
- const FontDescription& description = *(mCacheHandler->mFontDescriptionCache.begin() + fontDescriptionId - 1u);
+ if(fontDescriptionId > 0u && fontDescriptionId <= mCacheHandler->mCharacterSetCache.Count())
+ {
+ // Retrieve the font file name path.
+ const FontDescription& description = *(mCacheHandler->mFontDescriptionCache.begin() + fontDescriptionId - 1u);
- // Retrieve the font id. Do not cache the description as it has been already cached.
- // Note : CacheFontPath() API call ValidateFont() + setup CharacterSet + cache the font description.
- // So set cacheDescription=false, that we don't call CacheFontPath().
- fontId = GetFontIdByPath(description.path, requestedPointSize, faceIndex, false);
+ // Retrieve the font id. Do not cache the description as it has been already cached.
+ // Note : CacheFontPath() API call ValidateFont() + setup CharacterSet + cache the font description.
+ // So set cacheDescription=false, that we don't call CacheFontPath().
+ fontId = GetFontIdByPath(description.path, requestedPointSize, faceIndex, false);
- if((fontId > 0u) && (fontId - 1u < mCacheHandler->mFontIdCache.size()))
- {
- fontCacheIndex = mCacheHandler->mFontIdCache[fontId - 1u].index;
- mCacheHandler->mFontFaceCache[fontCacheIndex].mCharacterSet = FcCharSetCopy(mCacheHandler->mCharacterSetCache[fontDescriptionId - 1u]);
+ if((fontId > 0u) && (fontId - 1u < mCacheHandler->mFontIdCache.size()))
+ {
+ fontCacheIndex = mCacheHandler->mFontIdCache[fontId - 1u].index;
+ mCacheHandler->mFontFaceCache[fontCacheIndex].mCharacterSet = FcCharSetCopy(mCacheHandler->mCharacterSetCache[fontDescriptionId - 1u]);
- // Cache the pair 'fontDescriptionId, requestedPointSize' to improve the following queries.
- mCacheHandler->CacheFontDescriptionSize(fontDescriptionId, requestedPointSize, fontCacheIndex);
+ // Cache the pair 'fontDescriptionId, requestedPointSize' to improve the following queries.
+ mCacheHandler->CacheFontDescriptionSize(fontDescriptionId, requestedPointSize, fontCacheIndex);
+ }
}
}
else
#endif
}
-void FontClient::Plugin::CreateBitmap(FontId fontId, GlyphIndex glyphIndex, bool isItalicRequired, bool isBoldRequired, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth) const
+void FontClient::Plugin::CreateBitmap(FontId fontId, GlyphIndex glyphIndex, bool isItalicRequired, bool isBoldRequired, Dali::TextAbstraction::GlyphBufferData& data, int outlineWidth) const
{
data.isColorBitmap = false;
data.isColorEmoji = false;
PixelData FontClient::Plugin::CreateBitmap(FontId fontId, GlyphIndex glyphIndex, int outlineWidth) const
{
- TextAbstraction::FontClient::GlyphBufferData data;
+ TextAbstraction::GlyphBufferData data;
CreateBitmap(fontId, glyphIndex, false, false, data, outlineWidth);
// If data is compressed or not owned buffer, copy this.
- if(!data.isBufferOwned || data.compressionType != TextAbstraction::FontClient::GlyphBufferData::CompressionType::NO_COMPRESSION)
+ if(!data.isBufferOwned || data.compressionType != TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION)
{
uint8_t* newBuffer = (uint8_t*)malloc(data.width * data.height * Pixel::GetBytesPerPixel(data.format));
- TextAbstraction::FontClient::GlyphBufferData::Decompress(data, newBuffer);
+ if(DALI_UNLIKELY(!newBuffer))
+ {
+ DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", data.width, data.height, Pixel::GetBytesPerPixel(data.format));
+ return Dali::PixelData();
+ }
+
+ TextAbstraction::GlyphBufferData::Decompress(data, newBuffer);
if(data.isBufferOwned)
{
free(data.buffer);
data.buffer = newBuffer;
data.isBufferOwned = true;
- data.compressionType = TextAbstraction::FontClient::GlyphBufferData::CompressionType::NO_COMPRESSION;
+ data.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
}
return PixelData::New(data.buffer,
EllipsisItem item;
item.requestedPointSize = requestedPointSize;
- item.index = ellipsisCacheIndex;
// Find a font for the ellipsis glyph.
item.glyph.fontId = FindDefaultFont(ELLIPSIS_CHARACTER,
DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " glyph id %d found in the cache.\n", item.glyph.index);
DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font %d.\n", item.glyph.fontId);
+ // EllipsisCacheIndex is stored in item.index.
ellipsisCacheIndex = mCacheHandler->CacheEllipsis(std::move(item));
+ if(!mCacheHandler->mEllipsisCache.empty())
+ {
+ mCacheHandler->mEllipsisCache.back().index = ellipsisCacheIndex;
+ }
}
return mCacheHandler->mEllipsisCache[ellipsisCacheIndex].glyph;
}
uint32_t FontClient::Plugin::GetNumberOfPointsPerOneUnitOfPointSize() const
{
return TextAbstraction::FontClient::NUMBER_OF_POINTS_PER_ONE_UNIT_OF_POINT_SIZE;
- ;
}
FontId FontClient::Plugin::CreateFont(const FontPath& path,
DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " path : [%s]\n", path.c_str());
DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize);
- FontId fontId = 0u;
+ DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_CREATE_FONT");
- // Create & cache new font face
- FT_Face ftFace;
- int error = FT_New_Face(mFreeTypeLibrary,
- path.c_str(),
- 0,
- &ftFace);
+ FontId fontId = 0u;
+ FT_Face ftFace;
+ FT_Error error;
+
+ uint8_t* fontDataPtr = nullptr;
+ std::streampos dataSize = 0;
+ bool fontDataFound = mCacheHandler->FindFontData(path, fontDataPtr, dataSize);
+
+ if(fontDataFound)
+ {
+ // Create & cache new font face from pre-loaded font
+ error = FT_New_Memory_Face(mFreeTypeLibrary, reinterpret_cast<FT_Byte*>(fontDataPtr), static_cast<FT_Long>(dataSize), 0, &ftFace);
+#if defined(TRACE_ENABLED)
+ if(gTraceFilter && gTraceFilter->IsTraceEnabled())
+ {
+ DALI_LOG_DEBUG_INFO("DALI_TEXT_CREATE_FONT : FT_New_Memory_Face : %s\n", path.c_str());
+ }
+#endif
+ }
+ else
+ {
+ // Create & cache new font face
+ error = FT_New_Face(mFreeTypeLibrary, path.c_str(), 0, &ftFace);
+#if defined(TRACE_ENABLED)
+ if(gTraceFilter && gTraceFilter->IsTraceEnabled())
+ {
+ DALI_LOG_DEBUG_INFO("DALI_TEXT_CREATE_FONT : FT_New_Face : %s\n", path.c_str());
+ }
+#endif
+ }
if(FT_Err_Ok == error)
{
const float fixedHeight = static_cast<float>(ftFace->available_sizes[fixedSizeIndex].height);
// Create the FreeType font face item to cache.
- FontFaceCacheItem fontFaceCacheItem(mFreeTypeLibrary, ftFace, path, requestedPointSize, faceIndex, metrics, fixedSizeIndex, fixedWidth, fixedHeight, hasColorTables);
+ FontFaceCacheItem fontFaceCacheItem(mFreeTypeLibrary, ftFace, mCacheHandler->GetGlyphCacheManager(), path, requestedPointSize, faceIndex, metrics, fixedSizeIndex, fixedWidth, fixedHeight, hasColorTables);
fontId = mCacheHandler->CacheFontFaceCacheItem(std::move(fontFaceCacheItem));
}
{
error = FT_Set_Char_Size(ftFace,
0,
- requestedPointSize,
+ FT_F26Dot6(requestedPointSize),
mDpiHorizontal,
mDpiVertical);
}
static_cast<float>(ftFace->underline_thickness) * FROM_266);
// Create the FreeType font face item to cache.
- FontFaceCacheItem fontFaceCacheItem(mFreeTypeLibrary, ftFace, path, requestedPointSize, faceIndex, metrics);
+ FontFaceCacheItem fontFaceCacheItem(mFreeTypeLibrary, ftFace, mCacheHandler->GetGlyphCacheManager(), path, requestedPointSize, faceIndex, metrics);
fontId = mCacheHandler->CacheFontFaceCacheItem(std::move(fontFaceCacheItem));
}