2 * Copyright (c) 2022 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/internal/text/text-abstraction/plugin/font-client-plugin-impl.h>
22 #include <dali/devel-api/text-abstraction/font-list.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/integration-api/trace.h>
25 #include <dali/integration-api/platform-abstraction.h>
26 #include <dali/internal/adaptor/common/adaptor-impl.h>
27 #include <dali/internal/imaging/common/image-operations.h>
28 #include <dali/internal/text/text-abstraction/plugin/bitmap-font-cache-item.h>
29 #include <dali/internal/text/text-abstraction/plugin/embedded-item.h>
30 #include <dali/internal/text/text-abstraction/plugin/font-client-plugin-cache-handler.h>
31 #include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
32 #include <dali/internal/text/text-abstraction/plugin/font-face-cache-item.h>
33 #include <dali/public-api/common/dali-vector.h>
34 #include <dali/public-api/common/vector-wrapper.h>
37 #include <fontconfig/fontconfig.h>
41 #if defined(DEBUG_ENABLED)
43 // Note, to turn on trace and verbose logging, use "export LOG_FONT_CLIENT=3,true"
44 // Or re-define the following filter using Verbose,true instead of NoLogging,false,
45 // Or, add DALI_LOG_FILTER_ENABLE_TRACE(gFontClientLogFilter) in the code below.
47 Dali::Integration::Log::Filter* gFontClientLogFilter = Dali::Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_FONT_CLIENT");
49 #define FONT_LOG_DESCRIPTION(fontDescription, prefix) \
50 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, #prefix " description; family : [%s]\n", fontDescription.family.c_str()); \
51 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, \
55 " slant : [%s]\n\n", \
56 fontDescription.path.c_str(), \
57 FontWidth::Name[fontDescription.width], \
58 FontWeight::Name[fontDescription.weight], \
59 FontSlant::Name[fontDescription.slant])
61 #define FONT_LOG_REQUEST(charcode, requestedPointSize, preferColor) \
62 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, \
64 " requestedPointSize : %d\n" \
65 " preferColor : %s\n", \
68 (preferColor ? "true" : "false"))
72 #define FONT_LOG_DESCRIPTION(fontDescription, prefix)
73 #define FONT_LOG_REQUEST(charcode, requestedPointSize, preferColor)
80 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_FONT_PERFORMANCE_MARKER, false);
83 * Conversion from Fractional26.6 to float
85 const float FROM_266 = 1.0f / 64.0f;
86 const float POINTS_PER_INCH = 72.f;
88 const uint32_t ELLIPSIS_CHARACTER = 0x2026;
94 namespace Dali::TextAbstraction::Internal
99 * @brief Check if @p ftFace and @p requestedPointSize produces block that fit into atlas block
101 * @param[in/out] ftFace Face type object.
102 * @param[in] horizontalDpi The horizontal dpi.
103 * @param[in] verticalDpi The vertical dpi.
104 * @param[in] maxSizeFitInAtlas The maximum size of block to fit into atlas
105 * @param[in] requestedPointSize The requested point-size.
106 * @return whether the ftFace's block can fit into atlas
108 bool IsFitIntoAtlas(FT_Face& ftFace, int& error, const unsigned int& horizontalDpi, const unsigned int& verticalDpi, const Size& maxSizeFitInAtlas, const uint32_t& requestedPointSize)
112 error = FT_Set_Char_Size(ftFace,
118 if(error == FT_Err_Ok)
120 //Check width and height of block for requestedPointSize
121 //If the width or height is greater than the maximum-size then decrement by one unit of point-size.
122 if(static_cast<float>(ftFace->size->metrics.height) * FROM_266 <= maxSizeFitInAtlas.height && (static_cast<float>(ftFace->size->metrics.ascender) - static_cast<float>(ftFace->size->metrics.descender)) * FROM_266 <= maxSizeFitInAtlas.width)
132 * @brief Search on proper @p requestedPointSize that produces block that fit into atlas block considering on @p ftFace, @p horizontalDpi, and @p verticalDpi
134 * @param[in/out] ftFace Face type object.
135 * @param[in] horizontalDpi The horizontal dpi.
136 * @param[in] verticalDpi The vertical dpi.
137 * @param[in] maxSizeFitInAtlas The maximum size of block to fit into atlas
138 * @param[in/out] requestedPointSize The requested point-size.
139 * @return FreeType error code. 0 means success when requesting the nominal size (in points).
141 int SearchOnProperPointSize(FT_Face& ftFace, const unsigned int& horizontalDpi, const unsigned int& verticalDpi, const Size& maxSizeFitInAtlas, uint32_t& requestedPointSize)
143 //To improve performance of sequential search. This code is applying Exponential search then followed by Binary search.
144 const uint32_t& pointSizePerOneUnit = TextAbstraction::FontClient::NUMBER_OF_POINTS_PER_ONE_UNIT_OF_POINT_SIZE;
146 int error; // FreeType error code.
148 canFitInAtlas = IsFitIntoAtlas(ftFace, error, horizontalDpi, verticalDpi, maxSizeFitInAtlas, requestedPointSize);
149 if(FT_Err_Ok != error)
157 uint32_t exponentialDecrement = 1;
159 while(!canFitInAtlas && requestedPointSize > pointSizePerOneUnit * exponentialDecrement)
161 requestedPointSize -= (pointSizePerOneUnit * exponentialDecrement);
162 canFitInAtlas = IsFitIntoAtlas(ftFace, error, horizontalDpi, verticalDpi, maxSizeFitInAtlas, requestedPointSize);
163 if(FT_Err_Ok != error)
168 exponentialDecrement *= 2;
172 uint32_t minPointSize;
173 uint32_t maxPointSize;
177 exponentialDecrement /= 2;
178 minPointSize = requestedPointSize;
179 maxPointSize = requestedPointSize + (pointSizePerOneUnit * exponentialDecrement);
184 maxPointSize = requestedPointSize;
187 while(minPointSize < maxPointSize)
189 requestedPointSize = ((maxPointSize / pointSizePerOneUnit - minPointSize / pointSizePerOneUnit) / 2) * pointSizePerOneUnit + minPointSize;
190 canFitInAtlas = IsFitIntoAtlas(ftFace, error, horizontalDpi, verticalDpi, maxSizeFitInAtlas, requestedPointSize);
191 if(FT_Err_Ok != error)
198 if(minPointSize == requestedPointSize)
200 //Found targeted point-size
204 minPointSize = requestedPointSize;
208 maxPointSize = requestedPointSize;
218 FontClient::Plugin::Plugin(unsigned int horizontalDpi,
219 unsigned int verticalDpi)
220 : mFreeTypeLibrary(nullptr),
221 mDpiHorizontal(horizontalDpi),
222 mDpiVertical(verticalDpi),
223 mIsAtlasLimitationEnabled(TextAbstraction::FontClient::DEFAULT_ATLAS_LIMITATION_ENABLED),
224 mCurrentMaximumBlockSizeFitInAtlas(TextAbstraction::FontClient::MAX_SIZE_FIT_IN_ATLAS),
225 mVectorFontCache(nullptr),
226 mCacheHandler(new CacheHandler())
228 int error = FT_Init_FreeType(&mFreeTypeLibrary);
229 if(FT_Err_Ok != error)
231 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FreeType Init error: %d\n", error);
234 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
235 mVectorFontCache = new VectorFontCache(mFreeTypeLibrary);
239 FontClient::Plugin::~Plugin()
241 // Delete cache hanlder before remove mFreeTypeLibrary
242 delete mCacheHandler;
244 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
245 delete mVectorFontCache;
248 FT_Done_FreeType(mFreeTypeLibrary);
251 void FontClient::Plugin::ClearCache() const
253 mCacheHandler->ClearCache();
256 void FontClient::Plugin::SetDpi(unsigned int horizontalDpi,
257 unsigned int verticalDpi)
259 mDpiHorizontal = horizontalDpi;
260 mDpiVertical = verticalDpi;
263 void FontClient::Plugin::ResetSystemDefaults() const
265 mCacheHandler->ResetSystemDefaults();
268 void FontClient::Plugin::GetDefaultPlatformFontDescription(FontDescription& fontDescription) const
270 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
272 mCacheHandler->InitDefaultFontDescription();
273 fontDescription = mCacheHandler->mDefaultFontDescription;
275 FONT_LOG_DESCRIPTION(fontDescription, "");
278 void FontClient::Plugin::GetDefaultFonts(FontList& defaultFonts) const
280 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
282 mCacheHandler->InitDefaultFonts();
283 defaultFonts = mCacheHandler->mDefaultFonts;
285 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " number of default fonts : [%d]\n", mCacheHandler->mDefaultFonts.size());
288 void FontClient::Plugin::GetSystemFonts(FontList& systemFonts) const
290 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
292 mCacheHandler->InitSystemFonts();
293 systemFonts = mCacheHandler->mSystemFonts;
295 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " number of system fonts : [%d]\n", mCacheHandler->mSystemFonts.size());
298 void FontClient::Plugin::GetDescription(FontId fontId,
299 FontDescription& fontDescription) const
301 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
302 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
304 if((fontId > 0u) && (fontId - 1u < mCacheHandler->mFontIdCache.size()))
306 const auto& fontIdCacheItem = mCacheHandler->mFontIdCache[fontId - 1u];
307 switch(fontIdCacheItem.type)
309 case FontDescription::FACE_FONT:
311 for(const auto& item : mCacheHandler->mFontDescriptionSizeCache)
313 if(item.second == fontIdCacheItem.index)
315 fontDescription = *(mCacheHandler->mFontDescriptionCache.begin() + item.first.fontDescriptionId - 1u);
317 FONT_LOG_DESCRIPTION(fontDescription, "");
323 case FontDescription::BITMAP_FONT:
325 fontDescription.type = FontDescription::BITMAP_FONT;
326 fontDescription.family = mCacheHandler->mBitmapFontCache[fontIdCacheItem.index].font.name;
331 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " Invalid type of font\n");
332 fontDescription.type = FontDescription::INVALID;
333 fontDescription.family.clear();
338 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " No description found for the font id %d\n", fontId);
341 PointSize26Dot6 FontClient::Plugin::GetPointSize(FontId fontId) const
343 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
344 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
346 PointSize26Dot6 pointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
347 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
348 if(fontCacheItem != nullptr)
350 pointSize = fontCacheItem->GetPointSize();
352 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " point size : %d\n", pointSize);
357 bool FontClient::Plugin::IsCharacterSupportedByFont(FontId fontId, Character character) const
359 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
360 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
361 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " character : %p\n", character);
363 bool isSupported = false;
364 auto fontCacheItem = const_cast<FontCacheItemInterface*>(GetCachedFontItem(fontId));
365 if(fontCacheItem != nullptr)
367 isSupported = fontCacheItem->IsCharacterSupported(character); // May cache
370 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " is supported : %s\n", (isSupported ? "true" : "false"));
374 const FontCacheItemInterface* FontClient::Plugin::GetCachedFontItem(FontId fontId) const
376 if((fontId > 0u) && (fontId - 1u < mCacheHandler->mFontIdCache.size()))
378 const auto& fontIdCacheItem = mCacheHandler->mFontIdCache[fontId - 1u];
379 switch(fontIdCacheItem.type)
381 case FontDescription::FACE_FONT:
383 return &mCacheHandler->mFontFaceCache[fontIdCacheItem.index];
385 case FontDescription::BITMAP_FONT:
387 return &mCacheHandler->mBitmapFontCache[fontIdCacheItem.index];
391 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " Invalid type of font\n");
398 FontId FontClient::Plugin::FindFontForCharacter(const FontList& fontList,
399 const CharacterSetList& characterSetList,
401 PointSize26Dot6 requestedPointSize,
402 bool preferColor) const
404 DALI_ASSERT_DEBUG((fontList.size() == characterSetList.Count()) && "FontClient::Plugin::FindFontForCharacter. Different number of fonts and character sets.");
405 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
406 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " character : %p\n", character);
407 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize);
408 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " preferColor : %s\n", (preferColor ? "true" : "false"));
411 bool foundColor = false;
413 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " number of fonts : %d\n", fontList.size());
415 // Traverse the list of fonts.
416 // Check for each font if supports the character.
417 for(unsigned int index = 0u, numberOfFonts = fontList.size(); index < numberOfFonts; ++index)
419 const FontDescription& description = fontList[index];
420 const FcCharSet* const characterSet = characterSetList[index];
422 FONT_LOG_DESCRIPTION(description, "");
424 bool foundInRanges = false;
425 if(nullptr != characterSet)
427 foundInRanges = FcCharSetHasChar(characterSet, character);
432 fontId = GetFontId(description, requestedPointSize, 0u);
434 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " font id : %d\n", fontId);
438 if((fontId > 0) && (fontId - 1u < mCacheHandler->mFontIdCache.size()))
440 const FontFaceCacheItem& item = mCacheHandler->mFontFaceCache[mCacheHandler->mFontIdCache[fontId - 1u].index];
442 foundColor = item.mHasColorTables;
445 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " foundColor : %s\n", (foundColor ? "true" : "false"));
448 // Keep going unless we prefer a different (color) font.
449 if(!preferColor || foundColor)
456 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
460 FontId FontClient::Plugin::FindDefaultFont(Character charcode,
461 PointSize26Dot6 requestedPointSize,
462 bool preferColor) const
464 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
465 FONT_LOG_REQUEST(charcode, requestedPointSize, preferColor);
469 // Create the list of default fonts if it has not been created.
470 mCacheHandler->InitDefaultFonts();
471 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " number of default fonts : %d\n", mCacheHandler->mDefaultFonts.size());
473 // Traverse the list of default fonts.
474 // Check for each default font if supports the character.
475 fontId = FindFontForCharacter(mCacheHandler->mDefaultFonts, mCacheHandler->mDefaultFontCharacterSets, charcode, requestedPointSize, preferColor);
477 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
481 FontId FontClient::Plugin::FindFallbackFont(Character charcode,
482 const FontDescription& preferredFontDescription,
483 PointSize26Dot6 requestedPointSize,
484 bool preferColor) const
486 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
487 FONT_LOG_REQUEST(charcode, requestedPointSize, preferColor);
489 DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FIND_FALLBACKFONT");
491 // The font id to be returned.
494 FontDescription fontDescription;
496 // Fill the font description with the preferred font description and complete with the defaults.
497 fontDescription.family = preferredFontDescription.family.empty() ? DefaultFontFamily() : preferredFontDescription.family;
498 fontDescription.weight = ((FontWeight::NONE == preferredFontDescription.weight) ? DefaultFontWeight() : preferredFontDescription.weight);
499 fontDescription.width = ((FontWidth::NONE == preferredFontDescription.width) ? DefaultFontWidth() : preferredFontDescription.width);
500 fontDescription.slant = ((FontSlant::NONE == preferredFontDescription.slant) ? DefaultFontSlant() : preferredFontDescription.slant);
502 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " preferredFontDescription --> fontDescription\n");
503 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " [%s] --> [%s]\n", preferredFontDescription.family.c_str(), fontDescription.family.c_str());
504 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontWeight::Name[preferredFontDescription.weight], FontWeight::Name[fontDescription.weight]);
505 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontWidth::Name[preferredFontDescription.width], FontWidth::Name[fontDescription.width]);
506 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " [%s] --> [%s]\n", FontSlant::Name[preferredFontDescription.slant], FontSlant::Name[fontDescription.slant]);
508 #if defined(TRACE_ENABLED)
509 if(gTraceFilter && gTraceFilter->IsTraceEnabled())
511 DALI_LOG_DEBUG_INFO("DALI_TEXT_FIND_FALLBACKFONT : %s -> %s\n", preferredFontDescription.family.c_str(), fontDescription.family.c_str());
515 // Check first if the font's description has been queried before.
516 FontList* fontList = nullptr;
517 CharacterSetList* characterSetList = nullptr;
519 if(!mCacheHandler->FindFallbackFontList(fontDescription, fontList, characterSetList))
521 mCacheHandler->CacheFallbackFontList(std::move(fontDescription), fontList, characterSetList);
524 if(fontList && characterSetList)
526 fontId = FindFontForCharacter(*fontList, *characterSetList, charcode, requestedPointSize, preferColor);
529 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
533 FontId FontClient::Plugin::GetFontIdByPath(const FontPath& path,
534 PointSize26Dot6 requestedPointSize,
536 bool cacheDescription) const
538 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
539 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " path : [%s]\n", path.c_str());
540 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize);
544 if(nullptr != mFreeTypeLibrary)
547 if(mCacheHandler->FindFontByPath(path, requestedPointSize, faceIndex, foundId))
553 id = CreateFont(path, requestedPointSize, faceIndex, cacheDescription);
557 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", id);
561 FontId FontClient::Plugin::GetFontId(const FontDescription& fontDescription,
562 PointSize26Dot6 requestedPointSize,
563 FaceIndex faceIndex) const
565 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
566 FONT_LOG_DESCRIPTION(fontDescription, "");
568 // Special case when font Description don't have family information.
569 // In this case, we just use default description family and path.
570 const FontDescription& realFontDescription = fontDescription.family.empty() ? FontDescription(mCacheHandler->mDefaultFontDescription.path,
571 mCacheHandler->mDefaultFontDescription.family,
572 fontDescription.width,
573 fontDescription.weight,
574 fontDescription.slant,
575 fontDescription.type)
578 FONT_LOG_DESCRIPTION(realFontDescription, "");
579 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize);
581 // This method uses three vectors which caches:
582 // * The bitmap font cache
583 // * Pairs of non validated font descriptions and an index to a vector with paths to font file names.
584 // * The path to font file names.
585 // * The font ids of pairs 'font point size, index to the vector with paths to font file names'.
587 // 1) Checks if the font description matches with a previously loaded bitmap font.
588 // Returns if a font is found.
589 // 2) Checks in the cache if the font's description has been validated before.
590 // If it was it gets an index to the vector with paths to font file names. Otherwise,
591 // retrieves using font config a path to a font file name which matches with the
592 // font's description. The path is stored in the cache.
594 // 3) Checks in the cache if the pair 'font point size, index to the vector with paths to
595 // font file names' exists. If exists, it gets the font id. If it doesn't it calls
596 // the GetFontId() method with the path to the font file name and the point size to
599 // The font id to be returned.
602 // Check first if the font description matches with a previously loaded bitmap font.
603 if(mCacheHandler->FindBitmapFont(realFontDescription.family, fontId))
608 // Check if the font's description have been validated before.
609 FontDescriptionId fontDescriptionId = 0u;
611 if(!mCacheHandler->FindValidatedFont(realFontDescription, fontDescriptionId))
613 // Use font config to validate the font's description.
614 mCacheHandler->ValidateFont(realFontDescription, fontDescriptionId);
617 using FontCacheIndex = CacheHandler::FontCacheIndex;
618 FontCacheIndex fontCacheIndex = 0u;
619 // Check if exists a pair 'fontDescriptionId, requestedPointSize' in the cache.
620 if(!mCacheHandler->FindFont(fontDescriptionId, requestedPointSize, fontCacheIndex))
622 // Retrieve the font file name path.
623 const FontDescription& description = *(mCacheHandler->mFontDescriptionCache.begin() + fontDescriptionId - 1u);
625 // Retrieve the font id. Do not cache the description as it has been already cached.
626 // Note : CacheFontPath() API call ValidateFont() + setup CharacterSet + cache the font description.
627 // So set cacheDescription=false, that we don't call CacheFontPath().
628 fontId = GetFontIdByPath(description.path, requestedPointSize, faceIndex, false);
630 if((fontId > 0u) && (fontId - 1u < mCacheHandler->mFontIdCache.size()))
632 fontCacheIndex = mCacheHandler->mFontIdCache[fontId - 1u].index;
633 mCacheHandler->mFontFaceCache[fontCacheIndex].mCharacterSet = FcCharSetCopy(mCacheHandler->mCharacterSetCache[fontDescriptionId - 1u]);
635 // Cache the pair 'fontDescriptionId, requestedPointSize' to improve the following queries.
636 mCacheHandler->CacheFontDescriptionSize(fontDescriptionId, requestedPointSize, fontCacheIndex);
641 fontId = mCacheHandler->mFontFaceCache[fontCacheIndex].mFontId + 1u;
644 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
648 FontId FontClient::Plugin::GetFontId(const BitmapFont& bitmapFont) const
650 // The font id to be returned.
652 if(!mCacheHandler->FindBitmapFont(bitmapFont.name, fontId))
654 BitmapFontCacheItem bitmapFontCacheItem(bitmapFont);
656 fontId = mCacheHandler->CacheBitmapFontCacheItem(std::move(bitmapFontCacheItem));
661 void FontClient::Plugin::GetFontMetrics(FontId fontId,
662 FontMetrics& metrics) const
664 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
665 if(fontCacheItem != nullptr)
667 fontCacheItem->GetFontMetrics(metrics, mDpiVertical);
671 GlyphIndex FontClient::Plugin::GetGlyphIndex(FontId fontId,
672 Character charcode) const
674 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
675 if(fontCacheItem != nullptr)
677 return fontCacheItem->GetGlyphIndex(charcode);
683 GlyphIndex FontClient::Plugin::GetGlyphIndex(FontId fontId,
685 Character variantSelector) const
687 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
688 if(fontCacheItem != nullptr)
690 return fontCacheItem->GetGlyphIndex(charcode, variantSelector);
696 bool FontClient::Plugin::GetGlyphMetrics(GlyphInfo* array,
699 bool horizontal) const
701 if(VECTOR_GLYPH == type)
703 return GetVectorMetrics(array, size, horizontal);
706 return GetBitmapMetrics(array, size, horizontal);
709 bool FontClient::Plugin::GetBitmapMetrics(GlyphInfo* array,
711 bool horizontal) const
713 bool success(size > 0u);
715 for(unsigned int i = 0; i < size; ++i)
717 GlyphInfo& glyph = array[i];
719 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(glyph.fontId);
720 if(fontCacheItem != nullptr)
722 success &= fontCacheItem->GetGlyphMetrics(glyph, mDpiVertical, horizontal);
724 // Check if it's an embedded image.
725 else if((0u == glyph.fontId) && (0u != glyph.index) && (glyph.index <= mCacheHandler->mEmbeddedItemCache.size()))
727 mCacheHandler->mEmbeddedItemCache[glyph.index - 1u].GetGlyphMetrics(glyph);
738 bool FontClient::Plugin::GetVectorMetrics(GlyphInfo* array,
740 bool horizontal) const
742 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
745 for(unsigned int i = 0u; i < size; ++i)
747 FontId fontId = array[i].fontId;
750 (fontId - 1u) < mCacheHandler->mFontIdCache.size())
752 FontFaceCacheItem& font = mCacheHandler->mFontFaceCache[mCacheHandler->mFontIdCache[fontId - 1u].index];
754 if(!font.mVectorFontId)
756 font.mVectorFontId = mVectorFontCache->GetFontId(font.mPath);
759 mVectorFontCache->GetGlyphMetrics(font.mVectorFontId, array[i]);
761 // Vector metrics are in EMs, convert to pixels
762 const float scale = (static_cast<float>(font.mRequestedPointSize) * FROM_266) * static_cast<float>(mDpiVertical) / POINTS_PER_INCH;
763 array[i].width *= scale;
764 array[i].height *= scale;
765 array[i].xBearing *= scale;
766 array[i].yBearing *= scale;
767 array[i].advance *= scale;
781 void FontClient::Plugin::CreateBitmap(FontId fontId, GlyphIndex glyphIndex, bool isItalicRequired, bool isBoldRequired, Dali::TextAbstraction::GlyphBufferData& data, int outlineWidth) const
783 data.isColorBitmap = false;
784 data.isColorEmoji = false;
785 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
786 if(fontCacheItem != nullptr)
788 fontCacheItem->CreateBitmap(glyphIndex, data, outlineWidth, isItalicRequired, isBoldRequired);
790 else if((0u != glyphIndex) && (glyphIndex <= mCacheHandler->mEmbeddedItemCache.size()))
792 // It's an embedded item.
793 mCacheHandler->mEmbeddedItemCache[glyphIndex - 1u].CreateBitmap(mCacheHandler->mPixelBufferCache, data);
797 PixelData FontClient::Plugin::CreateBitmap(FontId fontId, GlyphIndex glyphIndex, int outlineWidth) const
799 TextAbstraction::GlyphBufferData data;
801 CreateBitmap(fontId, glyphIndex, false, false, data, outlineWidth);
803 // If data is compressed or not owned buffer, copy this.
804 if(!data.isBufferOwned || data.compressionType != TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION)
806 uint8_t* newBuffer = (uint8_t*)malloc(data.width * data.height * Pixel::GetBytesPerPixel(data.format));
807 TextAbstraction::GlyphBufferData::Decompress(data, newBuffer);
808 if(data.isBufferOwned)
813 data.buffer = newBuffer;
814 data.isBufferOwned = true;
815 data.compressionType = TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION;
818 return PixelData::New(data.buffer,
819 data.width * data.height * Pixel::GetBytesPerPixel(data.format),
826 void FontClient::Plugin::CreateVectorBlob(FontId fontId, GlyphIndex glyphIndex, VectorBlob*& blob, unsigned int& blobLength, unsigned int& nominalWidth, unsigned int& nominalHeight) const
831 #ifdef ENABLE_VECTOR_BASED_TEXT_RENDERING
833 (fontId - 1u < mCacheHandler->mFontIdCache.size()))
835 using FontCacheIndex = CacheHandler::FontCacheIndex;
836 const FontCacheIndex fontCacheIndex = mCacheHandler->mFontIdCache[fontId - 1u].index;
837 FontFaceCacheItem& font = mCacheHandler->mFontFaceCache[fontCacheIndex];
839 if(!font.mVectorFontId)
841 font.mVectorFontId = mVectorFontCache->GetFontId(font.mPath);
844 mVectorFontCache->GetVectorBlob(font.mVectorFontId, fontCacheIndex, glyphIndex, blob, blobLength, nominalWidth, nominalHeight);
849 const GlyphInfo& FontClient::Plugin::GetEllipsisGlyph(PointSize26Dot6 requestedPointSize) const
851 using EllipsisCacheIndex = CacheHandler::EllipsisCacheIndex;
852 using EllipsisItem = CacheHandler::EllipsisItem;
853 EllipsisCacheIndex ellipsisCacheIndex = 0u;
855 if(!mCacheHandler->FindEllipsis(requestedPointSize, ellipsisCacheIndex))
857 // No glyph has been found. Create one.
860 item.requestedPointSize = requestedPointSize;
861 item.index = ellipsisCacheIndex;
863 // Find a font for the ellipsis glyph.
864 item.glyph.fontId = FindDefaultFont(ELLIPSIS_CHARACTER,
868 // Set the character index to access the glyph inside the font.
869 item.glyph.index = GetGlyphIndex(item.glyph.fontId, ELLIPSIS_CHARACTER);
871 // Get glyph informations.
872 GetBitmapMetrics(&item.glyph, 1u, true);
874 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " glyph id %d found in the cache.\n", item.glyph.index);
875 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font %d.\n", item.glyph.fontId);
877 ellipsisCacheIndex = mCacheHandler->CacheEllipsis(std::move(item));
879 return mCacheHandler->mEllipsisCache[ellipsisCacheIndex].glyph;
882 bool FontClient::Plugin::IsColorGlyph(FontId fontId, GlyphIndex glyphIndex) const
884 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
885 return fontCacheItem && fontCacheItem->IsColorGlyph(glyphIndex);
888 FT_FaceRec_* FontClient::Plugin::GetFreetypeFace(FontId fontId) const
890 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
891 if(fontCacheItem != nullptr)
893 return fontCacheItem->GetTypeface();
898 FontDescription::Type FontClient::Plugin::GetFontType(FontId fontId) const
900 const FontId index = fontId - 1u;
901 if((fontId > 0u) && (index < mCacheHandler->mFontIdCache.size()))
903 return mCacheHandler->mFontIdCache[index].type;
905 return FontDescription::INVALID;
908 bool FontClient::Plugin::AddCustomFontDirectory(const FontPath& path)
910 // nullptr as first parameter means the current configuration is used.
911 return FcConfigAppFontAddDir(nullptr, reinterpret_cast<const FcChar8*>(path.c_str()));
914 HarfBuzzFontHandle FontClient::Plugin::GetHarfBuzzFont(FontId fontId) const
916 FontCacheItemInterface* fontCacheItem = const_cast<FontCacheItemInterface*>(GetCachedFontItem(fontId));
917 if(fontCacheItem != nullptr)
919 return fontCacheItem->GetHarfBuzzFont(mDpiHorizontal, mDpiVertical); // May cache
924 GlyphIndex FontClient::Plugin::CreateEmbeddedItem(const TextAbstraction::FontClient::EmbeddedItemDescription& description, Pixel::Format& pixelFormat) const
926 EmbeddedItem embeddedItem;
928 embeddedItem.pixelBufferId = 0u;
929 embeddedItem.width = description.width;
930 embeddedItem.height = description.height;
932 pixelFormat = Pixel::A8;
934 if(!description.url.empty())
936 // Check if the url is in the cache.
937 Devel::PixelBuffer pixelBuffer;
938 if(!mCacheHandler->FindEmbeddedPixelBufferId(description.url, embeddedItem.pixelBufferId))
940 // The pixel buffer is not in the cache. Create one and cache it.
941 embeddedItem.pixelBufferId = mCacheHandler->CacheEmbeddedPixelBuffer(description.url);
944 if((embeddedItem.pixelBufferId > 0u) && (embeddedItem.pixelBufferId - 1u) < mCacheHandler->mPixelBufferCache.size())
946 // Retrieve the pixel buffer from the cache to set the pixel format.
947 pixelBuffer = mCacheHandler->mPixelBufferCache[embeddedItem.pixelBufferId - 1u].pixelBuffer;
952 // Set the size of the embedded item if it has not been set.
953 if(0u == embeddedItem.width)
955 embeddedItem.width = static_cast<unsigned int>(pixelBuffer.GetWidth());
958 if(0u == embeddedItem.height)
960 embeddedItem.height = static_cast<unsigned int>(pixelBuffer.GetHeight());
963 // Set the pixel format.
964 pixelFormat = pixelBuffer.GetPixelFormat();
968 // Find if the same embeddedItem has already been created.
969 GlyphIndex index = 0u;
970 if(!mCacheHandler->FindEmbeddedItem(embeddedItem.pixelBufferId, embeddedItem.width, embeddedItem.height, index))
972 index = mCacheHandler->CacheEmbeddedItem(std::move(embeddedItem));
977 void FontClient::Plugin::EnableAtlasLimitation(bool enabled)
979 mIsAtlasLimitationEnabled = enabled;
982 bool FontClient::Plugin::IsAtlasLimitationEnabled() const
984 return mIsAtlasLimitationEnabled;
987 Size FontClient::Plugin::GetMaximumTextAtlasSize() const
989 return TextAbstraction::FontClient::MAX_TEXT_ATLAS_SIZE;
992 Size FontClient::Plugin::GetDefaultTextAtlasSize() const
994 return TextAbstraction::FontClient::DEFAULT_TEXT_ATLAS_SIZE;
997 Size FontClient::Plugin::GetCurrentMaximumBlockSizeFitInAtlas() const
999 return mCurrentMaximumBlockSizeFitInAtlas;
1002 bool FontClient::Plugin::SetCurrentMaximumBlockSizeFitInAtlas(const Size& currentMaximumBlockSizeFitInAtlas)
1004 bool isChanged = false;
1005 const Size& maxTextAtlasSize = TextAbstraction::FontClient::MAX_TEXT_ATLAS_SIZE;
1006 const uint16_t& padding = TextAbstraction::FontClient::PADDING_TEXT_ATLAS_BLOCK;
1008 if(currentMaximumBlockSizeFitInAtlas.width <= maxTextAtlasSize.width - padding && currentMaximumBlockSizeFitInAtlas.height <= maxTextAtlasSize.height - padding)
1010 mCurrentMaximumBlockSizeFitInAtlas = currentMaximumBlockSizeFitInAtlas;
1017 uint32_t FontClient::Plugin::GetNumberOfPointsPerOneUnitOfPointSize() const
1019 return TextAbstraction::FontClient::NUMBER_OF_POINTS_PER_ONE_UNIT_OF_POINT_SIZE;
1023 FontId FontClient::Plugin::CreateFont(const FontPath& path,
1024 PointSize26Dot6 requestedPointSize,
1025 FaceIndex faceIndex,
1026 bool cacheDescription) const
1028 DALI_LOG_TRACE_METHOD(gFontClientLogFilter);
1029 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " path : [%s]\n", path.c_str());
1030 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " requestedPointSize : %d\n", requestedPointSize);
1032 DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_CREATE_FONT");
1035 #if defined(TRACE_ENABLED)
1036 if(gTraceFilter && gTraceFilter->IsTraceEnabled())
1038 DALI_LOG_DEBUG_INFO("DALI_TEXT_CREATE_FONT : FT_New_Face : %s\n", path.c_str());
1042 // Create & cache new font face
1044 int error = FT_New_Face(mFreeTypeLibrary,
1049 if(FT_Err_Ok == error)
1051 // Check if a font is scalable.
1052 const bool isScalable = (0 != (ftFace->face_flags & FT_FACE_FLAG_SCALABLE));
1053 const bool hasFixedSizedBitmaps = (0 != (ftFace->face_flags & FT_FACE_FLAG_FIXED_SIZES)) && (0 != ftFace->num_fixed_sizes);
1054 const bool hasColorTables = (0 != (ftFace->face_flags & FT_FACE_FLAG_COLOR));
1056 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " isScalable : [%s]\n", (isScalable ? "true" : "false"));
1057 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " hasFixedSizedBitmaps : [%s]\n", (hasFixedSizedBitmaps ? "true" : "false"));
1058 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " hasColorTables : [%s]\n", (hasColorTables ? "true" : "false"));
1060 // Check to see if the font contains fixed sizes?
1061 if(!isScalable && hasFixedSizedBitmaps)
1063 PointSize26Dot6 actualPointSize = 0u;
1064 int fixedSizeIndex = 0;
1065 for(; fixedSizeIndex < ftFace->num_fixed_sizes; ++fixedSizeIndex)
1067 const PointSize26Dot6 fixedSize = static_cast<PointSize26Dot6>(ftFace->available_sizes[fixedSizeIndex].size);
1068 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " size index : %d, size : %d\n", fixedSizeIndex, fixedSize);
1070 if(fixedSize >= requestedPointSize)
1072 actualPointSize = fixedSize;
1077 if(0u == actualPointSize)
1079 // The requested point size is bigger than the bigest fixed size.
1080 fixedSizeIndex = ftFace->num_fixed_sizes - 1;
1081 actualPointSize = static_cast<PointSize26Dot6>(ftFace->available_sizes[fixedSizeIndex].size);
1084 DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, " size index : %d, actual size : %d\n", fixedSizeIndex, actualPointSize);
1086 // Tell Freetype to use this size
1087 error = FT_Select_Size(ftFace, fixedSizeIndex);
1088 if(FT_Err_Ok != error)
1090 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FreeType Select_Size error: %d\n", error);
1094 FT_Size_Metrics& ftMetrics = ftFace->size->metrics;
1096 FontMetrics metrics(static_cast<float>(ftMetrics.ascender) * FROM_266,
1097 static_cast<float>(ftMetrics.descender) * FROM_266,
1098 static_cast<float>(ftMetrics.height) * FROM_266,
1099 static_cast<float>(ftFace->underline_position) * FROM_266,
1100 static_cast<float>(ftFace->underline_thickness) * FROM_266);
1102 const float fixedWidth = static_cast<float>(ftFace->available_sizes[fixedSizeIndex].width);
1103 const float fixedHeight = static_cast<float>(ftFace->available_sizes[fixedSizeIndex].height);
1105 // Create the FreeType font face item to cache.
1106 FontFaceCacheItem fontFaceCacheItem(mFreeTypeLibrary, ftFace, mCacheHandler->GetGlyphCacheManager(), path, requestedPointSize, faceIndex, metrics, fixedSizeIndex, fixedWidth, fixedHeight, hasColorTables);
1108 fontId = mCacheHandler->CacheFontFaceCacheItem(std::move(fontFaceCacheItem));
1113 if(mIsAtlasLimitationEnabled)
1115 //There is limitation on block size to fit in predefined atlas size.
1116 //If the block size cannot fit into atlas size, then the system cannot draw block.
1117 //This is workaround to avoid issue in advance
1118 //Decrementing point-size until arriving to maximum allowed block size.
1119 auto requestedPointSizeBackup = requestedPointSize;
1120 const Size& maxSizeFitInAtlas = GetCurrentMaximumBlockSizeFitInAtlas();
1121 error = SearchOnProperPointSize(ftFace, mDpiHorizontal, mDpiVertical, maxSizeFitInAtlas, requestedPointSize);
1123 if(requestedPointSize != requestedPointSizeBackup)
1125 DALI_LOG_WARNING(" The requested-point-size : %d, is reduced to point-size : %d\n", requestedPointSizeBackup, requestedPointSize);
1130 error = FT_Set_Char_Size(ftFace,
1137 if(FT_Err_Ok == error)
1139 FT_Size_Metrics& ftMetrics = ftFace->size->metrics;
1141 FontMetrics metrics(static_cast<float>(ftMetrics.ascender) * FROM_266,
1142 static_cast<float>(ftMetrics.descender) * FROM_266,
1143 static_cast<float>(ftMetrics.height) * FROM_266,
1144 static_cast<float>(ftFace->underline_position) * FROM_266,
1145 static_cast<float>(ftFace->underline_thickness) * FROM_266);
1147 // Create the FreeType font face item to cache.
1148 FontFaceCacheItem fontFaceCacheItem(mFreeTypeLibrary, ftFace, mCacheHandler->GetGlyphCacheManager(), path, requestedPointSize, faceIndex, metrics);
1150 fontId = mCacheHandler->CacheFontFaceCacheItem(std::move(fontFaceCacheItem));
1154 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " FreeType Set_Char_Size error: %d for pointSize %d\n", error, requestedPointSize);
1160 if(cacheDescription)
1162 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " Cache Font Path at font id : %d [%s]\n", fontId, path.c_str());
1163 mCacheHandler->CacheFontPath(ftFace, fontId, requestedPointSize, path);
1169 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " FreeType New_Face error: %d for [%s]\n", error, path.c_str());
1172 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, " font id : %d\n", fontId);
1176 bool FontClient::Plugin::IsScalable(const FontPath& path) const
1178 bool isScalable = false;
1180 FT_Face ftFace = nullptr;
1181 int error = FT_New_Face(mFreeTypeLibrary,
1185 if(FT_Err_Ok != error)
1187 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::IsScalable. FreeType Cannot check font: %s\n", path.c_str());
1191 isScalable = ftFace->face_flags & FT_FACE_FLAG_SCALABLE;
1196 FT_Done_Face(ftFace);
1202 bool FontClient::Plugin::IsScalable(const FontDescription& fontDescription) const
1204 // Create a font pattern.
1205 FcPattern* fontFamilyPattern = CreateFontFamilyPattern(fontDescription); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
1207 FcResult result = FcResultMatch;
1209 // match the pattern
1210 FcPattern* match = FcFontMatch(nullptr /* use default configure */, fontFamilyPattern, &result); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
1211 bool isScalable = false;
1215 // Get the path to the font file name.
1217 GetFcString(match, FC_FILE, path);
1218 isScalable = IsScalable(path);
1222 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::IsScalable. FreeType Cannot check font: [%s]\n", fontDescription.family.c_str());
1225 // Destroys the created patterns.
1226 FcPatternDestroy(match);
1227 FcPatternDestroy(fontFamilyPattern);
1232 void FontClient::Plugin::GetFixedSizes(const FontPath& path, Vector<PointSize26Dot6>& sizes) const
1234 // Empty the caller container
1237 FT_Face ftFace = nullptr;
1238 int error = FT_New_Face(mFreeTypeLibrary,
1242 if(FT_Err_Ok != error)
1244 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetFixedSizes. FreeType Cannot check font path : [%s]\n", path.c_str());
1249 // Fetch the number of fixed sizes available
1250 if(ftFace->num_fixed_sizes && ftFace->available_sizes)
1252 for(int i = 0; i < ftFace->num_fixed_sizes; ++i)
1254 sizes.PushBack(ftFace->available_sizes[i].size);
1258 FT_Done_Face(ftFace);
1262 void FontClient::Plugin::GetFixedSizes(const FontDescription& fontDescription,
1263 Vector<PointSize26Dot6>& sizes) const
1265 // Create a font pattern.
1266 FcPattern* fontFamilyPattern = CreateFontFamilyPattern(fontDescription); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
1268 FcResult result = FcResultMatch;
1270 // match the pattern
1271 FcPattern* match = FcFontMatch(nullptr /* use default configure */, fontFamilyPattern, &result); // Creates a font pattern that needs to be destroyed by calling FcPatternDestroy.
1275 // Get the path to the font file name.
1277 GetFcString(match, FC_FILE, path);
1278 GetFixedSizes(path, sizes);
1282 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetFixedSizes. FreeType Cannot check font: [%s]\n", fontDescription.family.c_str());
1285 // Destroys the created patterns.
1286 FcPatternDestroy(match);
1287 FcPatternDestroy(fontFamilyPattern);
1290 bool FontClient::Plugin::HasItalicStyle(FontId fontId) const
1292 const FontCacheItemInterface* fontCacheItem = GetCachedFontItem(fontId);
1293 if(fontCacheItem != nullptr)
1295 return fontCacheItem->HasItalicStyle();
1300 } // namespace Dali::TextAbstraction::Internal