From df2e5e80d1c4e2f47fb13da2ea605cee251dbb4c Mon Sep 17 00:00:00 2001 From: Bowon Ryu Date: Tue, 2 May 2023 13:43:55 +0900 Subject: [PATCH] [Tizen] Add support for FontClientFontPreLoad API This patch adds a new FontClientFontPreLoad API that preloads font faces. this can prevents delays on main thread during the inital call of FT_New_Face. using memoryFontPathList involves loading font buffers into memory. And using FT_New_Memory_Face at runtime, which uses more memory but can lead to greater performance improvements. Change-Id: Id4ea04c23e2e63354e818b5ba74783508c43c8b7 Signed-off-by: Bowon Ryu --- dali/devel-api/adaptor-framework/file-loader.cpp | 6 +- dali/devel-api/adaptor-framework/file-loader.h | 14 +- dali/devel-api/text-abstraction/font-client.cpp | 10 ++ dali/devel-api/text-abstraction/font-client.h | 24 ++- dali/devel-api/text-abstraction/font-list.h | 1 + .../text/text-abstraction/font-client-impl.cpp | 161 ++++++++++++++------- .../text/text-abstraction/font-client-impl.h | 35 ++++- .../plugin/font-client-plugin-cache-handler.cpp | 97 ++++++++++++- .../plugin/font-client-plugin-cache-handler.h | 69 ++++++++- .../plugin/font-client-plugin-impl.cpp | 128 ++++++++++++++-- .../plugin/font-client-plugin-impl.h | 33 ++++- 11 files changed, 504 insertions(+), 74 deletions(-) diff --git a/dali/devel-api/adaptor-framework/file-loader.cpp b/dali/devel-api/adaptor-framework/file-loader.cpp index 340b875..4a593c9 100644 --- a/dali/devel-api/adaptor-framework/file-loader.cpp +++ b/dali/devel-api/adaptor-framework/file-loader.cpp @@ -49,7 +49,11 @@ int ReadFile(const std::string& filename, Dali::Vector& memblock, FileL int ReadFile(const std::string& filename, std::streampos& fileSize, Dali::Vector& memblock, FileLoader::FileType fileType) { return Dali::Internal::Adaptor::ReadFile(filename, fileSize, memblock, fileType); - ; +} + +int ReadFile(const std::string& filename, std::streampos& fileSize, Dali::Vector& memblock, FileLoader::FileType fileType) +{ + return Dali::Internal::Adaptor::ReadFile(filename, fileSize, memblock, fileType); } std::streampos GetFileSize(const std::string& filename) diff --git a/dali/devel-api/adaptor-framework/file-loader.h b/dali/devel-api/adaptor-framework/file-loader.h index 8b1dd7c..d4f31ef 100644 --- a/dali/devel-api/adaptor-framework/file-loader.h +++ b/dali/devel-api/adaptor-framework/file-loader.h @@ -2,7 +2,7 @@ #define DALI_FILE_LOADER_H /* - * Copyright (c) 2020 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. @@ -75,6 +75,18 @@ DALI_ADAPTOR_API int ReadFile(const std::string& filename, Dali::Vector DALI_ADAPTOR_API int ReadFile(const std::string& filename, std::streampos& fileSize, Dali::Vector& memblock, FileLoader::FileType fileType = BINARY); /** + * @brief Load the file. It will load it either as a binary or as a text + * + * @param[in] filename Filename of the file to load. + * @param[in] fileSize Size of the loaded file + * @param[in] memblock Dali::Vector containing the buffer loaded + * @param[in] fileType How we want to load the file. Binary or Text. Binary default + * @return error code. 0 - Error, 1 - Ok + * + */ +DALI_ADAPTOR_API int ReadFile(const std::string& filename, std::streampos& fileSize, Dali::Vector& memblock, FileLoader::FileType fileType = BINARY); + +/** * @brief Get the file size of a file * * @param[in] filename Filename of the file to load. diff --git a/dali/devel-api/text-abstraction/font-client.cpp b/dali/devel-api/text-abstraction/font-client.cpp index 63a9e80..8899d5b 100644 --- a/dali/devel-api/text-abstraction/font-client.cpp +++ b/dali/devel-api/text-abstraction/font-client.cpp @@ -667,6 +667,11 @@ void FontClient::GetDefaultFonts(FontList& defaultFonts) GetImplementation(*this).GetDefaultFonts(defaultFonts); } +void FontClient::InitDefaultFontDescription() +{ + GetImplementation(*this).InitDefaultFontDescription(); +} + void FontClient::GetDefaultPlatformFontDescription(FontDescription& fontDescription) { GetImplementation(*this).GetDefaultPlatformFontDescription(fontDescription); @@ -859,6 +864,11 @@ void FontClientPreCache(const FontFamilyList& fallbackFamilyList, const FontFami Internal::FontClient::PreCache(fallbackFamilyList, extraFamilyList, localeFamily, useThread); } +void FontClientFontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList, bool useThread) +{ + Internal::FontClient::PreLoad(fontPathList, memoryFontPathList, useThread); +} + } // namespace TextAbstraction } // namespace Dali diff --git a/dali/devel-api/text-abstraction/font-client.h b/dali/devel-api/text-abstraction/font-client.h index 0ea768e..fd28e04 100644 --- a/dali/devel-api/text-abstraction/font-client.h +++ b/dali/devel-api/text-abstraction/font-client.h @@ -284,6 +284,11 @@ public: void GetDefaultFonts(FontList& defaultFonts); /** + * @brief Initializes and caches default font from the system. + */ + void InitDefaultFontDescription(); + + /** * @brief Retrieve the active default font from the system. * * @param[out] fontDescription font structure describing the default font. @@ -649,7 +654,7 @@ public: // Not intended for application developers DALI_ADAPTOR_API FontClient FontClientPreInitialize(); /** - * @brief This is used to pre-cache fonts in order to improve the runtime performance of the application. + * @brief This is used to pre-cache FontConfig in order to improve the runtime performance of the application. * * @param[in] fallbackFamilyList A list of fallback font families to be pre-cached. * @param[in] extraFamilyList A list of additional font families to be pre-cached. @@ -658,6 +663,23 @@ DALI_ADAPTOR_API FontClient FontClientPreInitialize(); */ DALI_ADAPTOR_API void FontClientPreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily, bool useThread); +/** + * @brief This is used to pre-load FreeType font face in order to improve the runtime performance of the application. + * + * @param[in] fontPathList A list of font paths to be pre-loaded. + * @param[in] memoryFontPathList A list of memory font paths to be pre-loaded. + * @param[in] useThread True if the font client should create thread and perform font pre-loading, false otherwise. + * + * @note + * The fonts in the fontPathList perform FT_New_Face during pre-loading, + * which can provide some performace benefits. + * + * The fonts in the memoryFontPathList read the font file and cache the buffer in memory during pre-load. + * This enables the use of FT_New_Memory_Face during runtime and provides a performance boost. + * It requires memory equivalent to the size of each font file. + */ +DALI_ADAPTOR_API void FontClientFontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList, bool useThread); + } // namespace TextAbstraction } // namespace Dali diff --git a/dali/devel-api/text-abstraction/font-list.h b/dali/devel-api/text-abstraction/font-list.h index 9cb44cc..a36fe83 100644 --- a/dali/devel-api/text-abstraction/font-list.h +++ b/dali/devel-api/text-abstraction/font-list.h @@ -34,6 +34,7 @@ typedef std::string FontPath; typedef std::string FontFamily; typedef std::string FontStyle; typedef std::vector FontFamilyList; +typedef std::vector FontPathList; namespace FontWidth { diff --git a/dali/internal/text/text-abstraction/font-client-impl.cpp b/dali/internal/text/text-abstraction/font-client-impl.cpp index 5d17658..1a0bd45 100644 --- a/dali/internal/text/text-abstraction/font-client-impl.cpp +++ b/dali/internal/text/text-abstraction/font-client-impl.cpp @@ -19,6 +19,7 @@ #include // EXTERNAL INCLUDES +#include #include #if defined(VCONF_ENABLED) #include @@ -32,19 +33,20 @@ #include -#define FONT_LOG_MESSAGE(level, format, ...) \ - do \ - { \ - char buffer[256]; \ - int result = std::snprintf(buffer, sizeof(buffer), format, ##__VA_ARGS__); \ - if (result >= static_cast(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); \ +// 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(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) namespace Dali @@ -55,10 +57,14 @@ namespace Internal { Dali::TextAbstraction::FontClient FontClient::gPreCreatedFontClient(NULL); std::thread gPreCacheThread; +std::thread gPreLoadThread; +std::mutex gMutex; + /* TODO: This is to prevent duplicate calls of font pre-cache. * We may support this later, but currently we can't guarantee the behaviour * if there is a pre-cache call from the user after the font client has been created. */ bool gFontPreCacheAvailable = true; +bool gFontPreLoadAvailable = true; FontClient::FontClient() : mPlugin(nullptr), @@ -95,6 +101,12 @@ Dali::TextAbstraction::FontClient FontClient::Get() FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient PreCache thread join\n"); } + if(gPreLoadThread.joinable()) + { + gPreLoadThread.join(); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient PreLoad thread join\n"); + } + if(gPreCreatedFontClient) { fontClientHandle = gPreCreatedFontClient; @@ -103,13 +115,12 @@ Dali::TextAbstraction::FontClient FontClient::Get() else { fontClientHandle = Dali::TextAbstraction::FontClient(new FontClient); - - // Make DefaultFontDescription cached - Dali::TextAbstraction::FontDescription defaultFontDescription; - fontClientHandle.GetDefaultPlatformFontDescription(defaultFontDescription); } + fontClientHandle.InitDefaultFontDescription(); + gFontPreCacheAvailable = false; + gFontPreLoadAvailable = false; uint32_t horizontalDpi, verticalDpi; fontClientHandle.GetDpi(horizontalDpi, verticalDpi); @@ -131,67 +142,102 @@ Dali::TextAbstraction::FontClient FontClient::PreInitialize() { // Pre-cached font client already exists or pre-cache thread already running. // Font client pre-cache includes caching of the default font description. - if((gPreCreatedFontClient && !gFontPreCacheAvailable) || - (gPreCacheThread.joinable())) + if(gPreCreatedFontClient && !gFontPreCacheAvailable) { return gPreCreatedFontClient; } - gPreCreatedFontClient = Dali::TextAbstraction::FontClient(new FontClient); + if(!gPreCreatedFontClient) + { + gPreCreatedFontClient = Dali::TextAbstraction::FontClient(new FontClient); + } - // Make DefaultFontDescription cached - Dali::TextAbstraction::FontDescription defaultFontDescription; - gPreCreatedFontClient.GetDefaultPlatformFontDescription(defaultFontDescription); + gPreCreatedFontClient.InitDefaultFontDescription(); return gPreCreatedFontClient; } void FontClient::PreCacheRun(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily) { - if(gFontPreCacheAvailable) + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "BEGIN: DALI_TEXT_PRECACHE_RUN\n"); + GetImplementation(gPreCreatedFontClient).FontPreCache(fallbackFamilyList, extraFamilyList, localeFamily); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "END: DALI_TEXT_PRECACHE_RUN\n"); +} + +void FontClient::PreLoadRun(const FontPathList& fontPathList, const FontPathList& memoryFontPathList) +{ + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "BEGIN: DALI_TEXT_FONT_PRELOAD_RUN\n"); + GetImplementation(gPreCreatedFontClient).FontPreLoad(fontPathList, memoryFontPathList); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "END: DALI_TEXT_FONT_PRELOAD_RUN\n"); +} + +void FontClient::PreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily, bool useThread) +{ + if(!gFontPreCacheAvailable) { - gFontPreCacheAvailable = false; - FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "BEGIN: DALI_TEXT_PRECACHE_RUN\n"); - if(!gPreCreatedFontClient) - { - gPreCreatedFontClient = Dali::TextAbstraction::FontClient(new FontClient); - } - GetImplementation(gPreCreatedFontClient).FontPreCache(fallbackFamilyList, extraFamilyList, localeFamily); - FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "END: DALI_TEXT_PRECACHE_RUN\n"); + FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient pre-cache run failed, as a pre-cached font client already exists.\n"); + return; + } + + if(gPreCacheThread.joinable()) + { + FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient pre-cache thread already running.\n"); + } + + if(!gPreCreatedFontClient) + { + gPreCreatedFontClient = Dali::TextAbstraction::FontClient(new FontClient); + } + + gFontPreCacheAvailable = false; + + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient FontConfig PreCache fallbackFamilyList : %zu\n", fallbackFamilyList.size()); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient FontConfig PreCache extraFamilyList : %zu\n", extraFamilyList.size()); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient FontConfig PreCache localeFamily : %s\n", localeFamily.c_str()); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient FontConfig PreCache useThread : %d\n", useThread); + + if(useThread) + { + gPreCacheThread = std::thread(PreCacheRun, fallbackFamilyList, extraFamilyList, localeFamily); } else { - FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient pre-cache run failed, as a pre-cached font client already exists.\n"); + PreCacheRun(fallbackFamilyList, extraFamilyList, localeFamily); } } -void FontClient::PreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily, bool useThread) +void FontClient::PreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList, bool useThread) { - if(!gFontPreCacheAvailable) + if(!gFontPreLoadAvailable) { - FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient pre-cache has been completed or the font client has already been created.\n"); + FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient font pre-load run failed, as a pre-loaded font client already exists.\n"); return; } - FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient PreCache fallbackFamilyList : %zu\n", fallbackFamilyList.size()); - FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient PreCache extraFamilyList : %zu\n", extraFamilyList.size()); - FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient PreCache localeFamily : %s\n", localeFamily.c_str()); - FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient PreCache useThread : %d\n", useThread); + if(gPreLoadThread.joinable()) + { + FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient font pre-load thread already running.\n"); + return; + } - if(gPreCacheThread.joinable()) + if(!gPreCreatedFontClient) { - FONT_LOG_MESSAGE(Dali::Integration::Log::ERROR, "FontClient pre-cache thread already running.\n"); + gPreCreatedFontClient = Dali::TextAbstraction::FontClient(new FontClient); + } + + gFontPreLoadAvailable = false; + + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient Font PreLoad fontPathList : %zu\n", fontPathList.size()); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient Font PreLoad memoryFontPathList : %zu\n", memoryFontPathList.size()); + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "FontClient Font PreLoad useThread : %d\n", useThread); + + if(useThread) + { + gPreLoadThread = std::thread(PreLoadRun, fontPathList, memoryFontPathList); } else { - if(useThread) - { - gPreCacheThread = std::thread(PreCacheRun, fallbackFamilyList, extraFamilyList, localeFamily); - } - else - { - PreCacheRun(fallbackFamilyList, extraFamilyList, localeFamily); - } + PreLoadRun(fontPathList, memoryFontPathList); } } @@ -253,6 +299,20 @@ void FontClient::FontPreCache(const FontFamilyList& fallbackFamilyList, const Fo mPlugin->FontPreCache(fallbackFamilyList, extraFamilyList, localeFamily); } +void FontClient::FontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList) +{ + CreatePlugin(); + + mPlugin->FontPreLoad(fontPathList, memoryFontPathList); +} + +void FontClient::InitDefaultFontDescription() +{ + CreatePlugin(); + + mPlugin->InitDefaultFontDescription(); +} + void FontClient::GetDefaultPlatformFontDescription(FontDescription& fontDescription) { CreatePlugin(); @@ -536,6 +596,7 @@ HarfBuzzFontHandle FontClient::GetHarfBuzzFont(FontId fontId) void FontClient::CreatePlugin() { + std::scoped_lock lock(gMutex); if(!mPlugin) { mPlugin = new Plugin(mDpiHorizontal, mDpiVertical); diff --git a/dali/internal/text/text-abstraction/font-client-impl.h b/dali/internal/text/text-abstraction/font-client-impl.h index f7731db..d0f7494 100644 --- a/dali/internal/text/text-abstraction/font-client-impl.h +++ b/dali/internal/text/text-abstraction/font-client-impl.h @@ -2,7 +2,7 @@ #define DALI_INTERNAL_TEXT_ABSTRACTION_FONT_CLIENT_IMPL_H /* - * Copyright (c) 2022 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. @@ -64,18 +64,30 @@ public: // API for Dali::TextAbstraction::FontClient used. static Dali::TextAbstraction::FontClient PreInitialize(); /** - * @brief This is used to pre-cache fonts in order to improve the runtime performance of the application. + * @brief This is used to pre-cache FontConfig in order to improve the runtime performance of the application. * * @see Dali::TextAbstraction::FontClientPreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily, bool useThread); */ - static void PreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily, bool useUiThread); + static void PreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily, bool useThread); /** - * @brief This is used to creates a global font client and pre-caches the fonts. + * @brief This is used to creates a global font client and pre-caches the FontConfig. */ static void PreCacheRun(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily); /** + * @brief This is used to pre-load FreeType font face in order to improve the runtime performance of the application. + * + * @see Dali::TextAbstraction:FontClientFontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList, bool useThread); + */ + static void PreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList, bool useThread); + + /** + * @brief This is used to creates a global font client and pre-loads the FreeType font face. + */ + static void PreLoadRun(const FontPathList& fontPathList, const FontPathList& memoryFontPathList); + + /** * @copydoc Dali::TextAbstraction::FontClient::ClearCache() */ void ClearCache(); @@ -106,6 +118,11 @@ public: // API for Dali::TextAbstraction::FontClient used. void GetDefaultFonts(FontList& defaultFonts); /** + * @copydoc Dali::TextAbstraction::FontClient::InitDefaultFontDescription() + */ + void InitDefaultFontDescription(); + + /** * @copydoc Dali::TextAbstraction::FontClient::GetDefaultPlatformFontDescription() */ void GetDefaultPlatformFontDescription(FontDescription& fontDescription); @@ -314,6 +331,16 @@ public: // API for Dali::TextAbstraction::Internal::FontClient used. */ void FontPreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily); + /** + * @brief This is used to pre-load FreeType font face in order to improve the runtime performance of the application. + * + * @param[in] fontPathList A list of font paths to be pre-loaded. + * @param[in] memoryFontPathList A list of memory font paths to be pre-loaded. + * @param[in] useThread True if the font client should create thread and perform font pre-loading, false otherwise. + */ + void FontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList); + + private: /** * Helper for lazy initialization. diff --git a/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.cpp b/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.cpp index d7fb50d..c248787 100644 --- a/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.cpp +++ b/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 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. @@ -25,11 +25,29 @@ // INTERNAL INCLUDES #include +#include #include +#include #include #include #include +// 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(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) extern Dali::Integration::Log::Filter* gFontClientLogFilter; @@ -274,6 +292,8 @@ FontClient::Plugin::CacheHandler::CacheHandler() mFontDescriptionCache(), mCharacterSetCache(), mFontDescriptionSizeCache(), + mFontDataCache(), + mFontFTFaceCache(), mEllipsisCache(), mEmbeddedItemCache(), mGlyphCacheManager(new GlyphCacheManager(GetMaxNumberOfGlyphCache())), @@ -320,6 +340,13 @@ void FontClient::Plugin::CacheHandler::ClearCache() mFontDescriptionSizeCache.clear(); mFontDescriptionSizeCache.rehash(0); // Note : unordered_map.clear() didn't deallocate memory + mFontDataCache.clear(); + mFontDataCache.rehash(0); + + ClearFTFaceFromFontFTFaceCache(); + mFontFTFaceCache.clear(); + mFontFTFaceCache.rehash(0); + mEllipsisCache.clear(); mPixelBufferCache.clear(); mEmbeddedItemCache.clear(); @@ -415,6 +442,14 @@ void FontClient::Plugin::CacheHandler::CreateCharacterSet() } } +void FontClient::Plugin::CacheHandler::ClearFTFaceFromFontFTFaceCache() +{ + for(auto& item : mFontFTFaceCache) + { + FT_Done_Face(item.second); + } +} + // System / Default void FontClient::Plugin::CacheHandler::InitSystemFonts() @@ -482,6 +517,8 @@ void FontClient::Plugin::CacheHandler::InitDefaultFontDescription() { if(!mDefaultFontDescriptionCached) { + mDefaultFontDescriptionCached = true; + // Clear any font config stored info in the caches. ClearCharacterSet(); @@ -534,9 +571,65 @@ void FontClient::Plugin::CacheHandler::InitDefaultFontDescription() // Create again the character sets as they are not valid after FcInitReinitialize() CreateCharacterSet(); + } +} - mDefaultFontDescriptionCached = true; +// Font + +bool FontClient::Plugin::CacheHandler::FindFontData(const std::string& fontPath) const +{ + auto it = mFontDataCache.find(fontPath); + if(it != mFontDataCache.end()) + { + return true; + } + + return false; +} + +bool FontClient::Plugin::CacheHandler::FindFontData(const std::string& fontPath, uint8_t*& fontDataPtr, std::streampos& dataSize) const +{ + auto it = mFontDataCache.find(fontPath); + if(it != mFontDataCache.end()) + { + fontDataPtr = it->second.first.Begin(); + dataSize = it->second.second; + return true; } + + return false; +} + +bool FontClient::Plugin::CacheHandler::LoadFontDataFromFile(const std::string& fontPath, Dali::Vector& fontDataBuffer, std::streampos& dataSize) const +{ + if(Dali::FileLoader::ReadFile(fontPath, dataSize, fontDataBuffer, Dali::FileLoader::BINARY)) + { + FONT_LOG_MESSAGE(Dali::Integration::Log::INFO, "PreLoad font file buffer : %lu, size : %ld, path : %s\n", fontDataBuffer.Size(), static_cast(dataSize), fontPath.c_str()); + return true; + } + + return false; +} + +void FontClient::Plugin::CacheHandler::CacheFontData(const std::string& fontPath, Dali::Vector& fontDataBuffer, std::streampos& dataSize) +{ + mFontDataCache[fontPath] = std::make_pair(std::move(fontDataBuffer), dataSize); +} + +bool FontClient::Plugin::CacheHandler::FindFontFace(const std::string& fontPath) const +{ + auto it = mFontFTFaceCache.find(fontPath); + if(it != mFontFTFaceCache.end()) + { + return true; + } + + return false; +} + +void FontClient::Plugin::CacheHandler::CacheFontFace(const std::string& fontPath, FT_Face ftFace) +{ + mFontFTFaceCache[fontPath] = std::move(ftFace); } // Validate diff --git a/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.h b/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.h index 20c5995..d61fb72 100644 --- a/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.h +++ b/dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.h @@ -2,7 +2,7 @@ #define DALI_INTERNAL_TEXT_ABSTRACTION_FONT_CLIENT_PLUGIN_CACHE_HANDLER_H /* - * Copyright (c) 2022 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. @@ -152,6 +152,11 @@ private: // Clear cache private */ void ClearCharacterSet(); + /** + * @brief Free the resources allocated in FreeType face cache. + */ + void ClearFTFaceFromFontFTFaceCache(); + private: /** * @brief Crate the charset resouces by default font and Fallback caches. @@ -179,6 +184,65 @@ public: // Find & Cache */ void InitDefaultFontDescription(); + // Font + + /** + * @brief Checks if font data for the specified font path is cached. + * + * @param[in] fontPath The font path to check for cached data. + * + * @return @e true if the font data is cached, otherwise false. + */ + bool FindFontData(const std::string& fontPath) const; + + /** + * @brief Retrieves font data for the specified font path if it is cached. + * + * @param[in] fontPath The font path to retrieve the cached data for. + * @param[out] fontDataPtr A pointer to the cached font data. + * @param[out] dataSize The size of the cached font data. + * + * @return @e true if the font data is cached and retrieved successfully, otherwise false. + */ + bool FindFontData(const std::string& fontPath, uint8_t*& fontDataPtr, std::streampos& dataSize) const; + + /** + * @brief Loads font data from the specified file path. + * + * @param[in] fontPath The file path to load the font data from. + * @param[out] fontDataBuffer A vector containing the loaded font data. + * @param[out] dataSize The size of the loaded font data. + * + * @return @e true if the font data was loaded successfully, otherwise false. + */ + bool LoadFontDataFromFile(const std::string& fontPath, Dali::Vector& fontDataBuffer, std::streampos& dataSize) const; + + /** + * @brief Caches font data for the specified font path. + * + * @param[in] fontPath The font path to cache the data for. + * @param[in] fontDataBuffer A vector containing the font data to cache. + * @param[in] dataSize The size of the font data to cache. + */ + void CacheFontData(const std::string& fontPath, Dali::Vector& fontDataBuffer, std::streampos& dataSize); + + /** + * @brief Checks if FreeType face for the specified font path is cached. + * + * @param[in] fontPath The font path to check for cached face. + * + * @return @e true if the font face is cached, otherwise false. + */ + bool FindFontFace(const std::string& fontPath) const; + + /** + * @brief Caches FreeType face for the specified font path. + * + * @param[in] fontPath The font path to cache the face for. + * @param[in] ftFace The freetype font face to cache. + */ + void CacheFontFace(const std::string& fontPath, FT_Face ftFace); + // Validate /** @@ -406,6 +470,9 @@ public: // Cache container list FontDescriptionSizeCacheContainer mFontDescriptionSizeCache; ///< Caches font identifiers for the pairs of font point size and the index to the vector with font descriptions of the validated fonts. + std::unordered_map, std::streampos>> mFontDataCache; ///< Caches font data with each font path as the key, allowing faster loading of fonts later on. + std::unordered_map mFontFTFaceCache; ///< Caches freetype font face for font pre-load. + std::vector mEllipsisCache; ///< Caches ellipsis glyphs for a particular point size. std::vector mBitmapFontCache; ///< Stores bitmap fonts. std::vector mPixelBufferCache; ///< Caches the pixel buffer of a url. diff --git a/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.cpp b/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.cpp index 2c31868..78448d9 100644 --- a/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.cpp +++ b/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,22 @@ #include #include +// 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(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" @@ -265,6 +282,71 @@ void FontClient::Plugin::ResetSystemDefaults() const 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 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(); @@ -320,6 +402,11 @@ void FontClient::Plugin::FontPreCache(const FontFamilyList& fallbackFamilyList, } } +void FontClient::Plugin::InitDefaultFontDescription() const +{ + mCacheHandler->InitDefaultFontDescription(); +} + void FontClient::Plugin::GetDefaultPlatformFontDescription(FontDescription& fontDescription) const { DALI_LOG_TRACE_METHOD(gFontClientLogFilter); @@ -1072,7 +1159,6 @@ bool FontClient::Plugin::SetCurrentMaximumBlockSizeFitInAtlas(const Size& curren 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, @@ -1085,21 +1171,37 @@ FontId FontClient::Plugin::CreateFont(const FontPath& path, DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize); DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_CREATE_FONT"); - FontId fontId = 0u; - #if defined(TRACE_ENABLED) - if(gTraceFilter && gTraceFilter->IsTraceEnabled()) + 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) { - DALI_LOG_DEBUG_INFO("DALI_TEXT_CREATE_FONT : FT_New_Face : %s\n", path.c_str()); + // Create & cache new font face from pre-loaded font + error = FT_New_Memory_Face(mFreeTypeLibrary, reinterpret_cast(fontDataPtr), static_cast(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 } - #endif - - // Create & cache new font face - FT_Face ftFace; - int error = FT_New_Face(mFreeTypeLibrary, - path.c_str(), - 0, - &ftFace); if(FT_Err_Ok == error) { diff --git a/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.h b/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.h index 2a0c5ee..507ac1d 100644 --- a/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.h +++ b/dali/internal/text/text-abstraction/plugin/font-client-plugin-impl.h @@ -2,7 +2,7 @@ #define DALI_INTERNAL_TEXT_ABSTRACTION_FONT_CLIENT_PLUGIN_IMPL_H /* - * Copyright (c) 2022 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. @@ -105,6 +105,11 @@ public: // Dali::TextAbstraction::FontClient void ResetSystemDefaults() const; /** + * @copydoc Dali::TextAbstraction::FontClient::InitDefaultFontDescription() + */ + void InitDefaultFontDescription() const; + + /** * @copydoc Dali::TextAbstraction::FontClient::GetDefaultPlatformFontDescription() */ void GetDefaultPlatformFontDescription(FontDescription& fontDescription) const; @@ -318,6 +323,11 @@ public: // Dali::TextAbstraction::Internal::FontClient */ void FontPreCache(const FontFamilyList& fallbackFamilyList, const FontFamilyList& extraFamilyList, const FontFamily& localeFamily) const; + /** + * @copydoc Dali::TextAbstraction::Internal::FontClient::FontPreLoad() + */ + void FontPreLoad(const FontPathList& fontPathList, const FontPathList& memoryFontPathList) const; + private: /** * Get the cached font item for the given font @@ -358,6 +368,27 @@ private: FaceIndex faceIndex, bool cacheDescription) const; + + /** + * @brief Caches font data for the specified font path if it is not already cached. + * + * If the font data is not already cached, this function will load the font file + * from disk and cache the data for future use. + * + * @param[in] fontPath The font path to cache the data for. + */ + void CacheFontDataFromFile(const std::string& fontPath) const; + + /** + * @brief Caches FreeType face for the specified font path if it is not already cached. + * + * If the font face is not already cached, this function will perform the new face + * from font file and cache the face for future use. + * + * @param[in] fontPath The font path to cache the face for. + */ + void CacheFontFaceFromFile(const std::string& fontPath) const; + private: Plugin(const Plugin&) = delete; Plugin& operator=(const Plugin&) = delete; -- 2.7.4