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.
18 #include <dali/integration-api/debug.h>
21 #include <dali/devel-api/adaptor-framework/environment-variable.h>
22 #include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
23 #include <dali/internal/text/text-abstraction/plugin/font-face-cache-item.h>
25 #if defined(DEBUG_ENABLED)
26 extern Dali::Integration::Log::Filter* gFontClientLogFilter;
29 namespace Dali::TextAbstraction::Internal
33 const float FROM_266 = 1.0f / 64.0f;
34 const float POINTS_PER_INCH = 72.f;
37 * @brief Maximum rate of bitmap glyph resize.
38 * If scale factor is bigger than this value, we will not cache resized glyph.
39 * Else, resize bitmap glyph itself and cache it.
41 constexpr float MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE = 1.5f;
44 * @brief Maximum size of glyph cache per each font face.
46 constexpr std::size_t DEFAULT_GLYPH_CACHE_MAX = 128;
47 constexpr std::size_t MINIMUM_SIZE_OF_GLYPH_CACHE_MAX = 3u;
49 constexpr auto MAX_NUMBER_OF_GLYPH_CACHE_ENV = "DALI_GLYPH_CACHE_MAX";
52 * @brief Get maximum size of glyph cache size from environment.
53 * If not settuped, default as 128.
54 * @note This value fixed when we call it first time.
55 * @return The max size of glyph cache.
57 inline const size_t GetMaxNumberOfGlyphCache()
59 using Dali::EnvironmentVariable::GetEnvironmentVariable;
60 static auto numberString = GetEnvironmentVariable(MAX_NUMBER_OF_GLYPH_CACHE_ENV);
61 static auto number = numberString ? std::strtoul(numberString, nullptr, 10) : DEFAULT_GLYPH_CACHE_MAX;
62 return (number < MINIMUM_SIZE_OF_GLYPH_CACHE_MAX) ? MINIMUM_SIZE_OF_GLYPH_CACHE_MAX : number;
66 * @brief Behavior about cache the rendered glyph cache.
68 constexpr bool DEFAULT_ENABLE_CACHE_RENDERED_GLYPH = true;
69 constexpr auto ENABLE_CACHE_RENDERED_GLYPH_ENV = "DALI_ENABLE_CACHE_RENDERED_GLYPH";
72 * @brief Get whether we allow to cache rendered glyph from environment.
73 * If not settuped, default as true.
74 * @note This value fixed when we call it first time.
75 * @return True if we allow to cache rendered glyph.
77 inline const bool EnableCacheRenderedGlyph()
79 using Dali::EnvironmentVariable::GetEnvironmentVariable;
80 static auto numberString = GetEnvironmentVariable(ENABLE_CACHE_RENDERED_GLYPH_ENV);
81 static auto number = numberString ? (std::strtoul(numberString, nullptr, 10) ? true : false) : DEFAULT_ENABLE_CACHE_RENDERED_GLYPH;
86 * @brief Policy about compress the cached rendered glyph.
87 * It will be used only if CacheRenderedGlyph is enabled
89 constexpr auto DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY =
90 #if !(defined(DALI_PROFILE_UBUNTU) || defined(ANDROID) || defined(WIN32) || defined(__APPLE__))
91 GlyphCacheManager::CompressionPolicyType::MEMORY; // If tizen target
93 GlyphCacheManager::CompressionPolicyType::SPEED; // If not tizen target
95 constexpr auto RENDERED_GLYPH_COMPRESS_POLICY_ENV = "DALI_RENDERED_GLYPH_COMPRESS_POLICY";
98 * @brief Get whether we allow to cache rendered glyph from environment.
99 * If not settuped, default value used, as defined above.
100 * @note This value fixed when we call it first time.
101 * @return SPEED if value start with 's' or 'S'. MEMORY if value start with 'm' or 'M'. otherwise, use default
103 inline const GlyphCacheManager::CompressionPolicyType GetRenderedGlyphCompressPolicy()
105 using Dali::EnvironmentVariable::GetEnvironmentVariable;
106 static auto policyString = GetEnvironmentVariable(RENDERED_GLYPH_COMPRESS_POLICY_ENV);
108 static auto policy = policyString ? policyString[0] == 's' || policyString[0] == 'S' ? GlyphCacheManager::CompressionPolicyType::SPEED
109 : policyString[0] == 'm' || policyString[0] == 'M' ? GlyphCacheManager::CompressionPolicyType::MEMORY
110 : DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY
111 : DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY;
116 FontFaceCacheItem::FontFaceCacheItem(const FT_Library& freeTypeLibrary,
118 const FontPath& path,
119 PointSize26Dot6 requestedPointSize,
121 const FontMetrics& metrics)
122 : mFreeTypeLibrary(freeTypeLibrary),
123 mFreeTypeFace(ftFace),
124 mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
125 mHarfBuzzProxyFont(),
127 mRequestedPointSize(requestedPointSize),
130 mCharacterSet(nullptr),
132 mFixedWidthPixels(0.f),
133 mFixedHeightPixels(0.f),
136 mIsFixedSizeBitmap(false),
137 mHasColorTables(false)
141 FontFaceCacheItem::FontFaceCacheItem(const FT_Library& freeTypeLibrary,
143 const FontPath& path,
144 PointSize26Dot6 requestedPointSize,
146 const FontMetrics& metrics,
151 : mFreeTypeLibrary(freeTypeLibrary),
152 mFreeTypeFace(ftFace),
153 mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
154 mHarfBuzzProxyFont(),
156 mRequestedPointSize(requestedPointSize),
159 mCharacterSet(nullptr),
160 mFixedSizeIndex(fixedSizeIndex),
161 mFixedWidthPixels(fixedWidth),
162 mFixedHeightPixels(fixedHeight),
165 mIsFixedSizeBitmap(true),
166 mHasColorTables(hasColorTables)
170 // Move constructor. font client plugin container may call this.
171 // Note that we make nullptr of some reference sensitive values here.
172 FontFaceCacheItem::FontFaceCacheItem(FontFaceCacheItem&& rhs)
173 : mFreeTypeLibrary(rhs.mFreeTypeLibrary)
175 mFreeTypeFace = rhs.mFreeTypeFace;
176 mGlyphCacheManager = std::move(rhs.mGlyphCacheManager);
177 mHarfBuzzProxyFont = std::move(rhs.mHarfBuzzProxyFont);
178 mPath = std::move(rhs.mPath);
179 mRequestedPointSize = rhs.mRequestedPointSize;
180 mFaceIndex = rhs.mFaceIndex;
181 mMetrics = rhs.mMetrics;
182 mCharacterSet = rhs.mCharacterSet;
183 mFixedSizeIndex = rhs.mFixedSizeIndex;
184 mFixedWidthPixels = rhs.mFixedWidthPixels;
185 mFixedHeightPixels = rhs.mFixedWidthPixels;
186 mVectorFontId = rhs.mVectorFontId;
187 mFontId = rhs.mFontId;
188 mIsFixedSizeBitmap = rhs.mIsFixedSizeBitmap;
189 mHasColorTables = rhs.mHasColorTables;
191 rhs.mFreeTypeFace = nullptr;
194 FontFaceCacheItem::~FontFaceCacheItem()
196 // delete glyph cache manager before free face.
197 if(mGlyphCacheManager)
199 mGlyphCacheManager.reset();
202 if(mHarfBuzzProxyFont)
204 mHarfBuzzProxyFont.reset();
210 FT_Done_Face(mFreeTypeFace);
214 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
218 // Adjust the metrics if the fixed-size font should be down-scaled
219 if(mIsFixedSizeBitmap)
221 const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
223 if(desiredFixedSize > 0.f)
225 const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
227 metrics.ascender = round(metrics.ascender * scaleFactor);
228 metrics.descender = round(metrics.descender * scaleFactor);
229 metrics.height = round(metrics.height * scaleFactor);
230 metrics.underlinePosition = metrics.underlinePosition * scaleFactor;
231 metrics.underlineThickness = metrics.underlineThickness * scaleFactor;
236 bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
240 GlyphCacheManager::GlyphCacheData glyphData;
243 #ifdef FREETYPE_BITMAP_SUPPORT
244 // Check to see if we should be loading a Fixed Size bitmap?
245 if(mIsFixedSizeBitmap)
247 FT_Select_Size(mFreeTypeFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
248 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, glyphData, error);
250 if(FT_Err_Ok == error)
252 glyphInfo.width = mFixedWidthPixels;
253 glyphInfo.height = mFixedHeightPixels;
254 glyphInfo.advance = mFixedWidthPixels;
255 glyphInfo.xBearing = 0.0f;
257 const auto& metrics = glyphData.mGlyphMetrics;
261 glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
265 glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
268 // Adjust the metrics if the fixed-size font should be down-scaled
269 const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
271 if(desiredFixedSize > 0.f)
273 const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
274 glyphInfo.width = round(glyphInfo.width * scaleFactor);
275 glyphInfo.height = round(glyphInfo.height * scaleFactor);
276 glyphInfo.advance = round(glyphInfo.advance * scaleFactor);
277 glyphInfo.xBearing = round(glyphInfo.xBearing * scaleFactor);
278 glyphInfo.yBearing = round(glyphInfo.yBearing * scaleFactor);
280 glyphInfo.scaleFactor = scaleFactor;
282 if(scaleFactor < MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE)
284 // Resize bitmap glyph and cache it due to the performance issue.
285 // If scaleFactor is too big, cached bitmap may hold too big memory.
286 // So, we only hold small enough case.
288 // TODO : If dpiVertical value changed, this resize feature will be break down.
289 // Otherwise, this glyph will be resized only one times.
290 mGlyphCacheManager->ResizeBitmapGlyph(glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, static_cast<uint32_t>(glyphInfo.width), static_cast<uint32_t>(glyphInfo.height));
296 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
303 // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
304 // i.e. with the SNum-3R font.
305 // @todo: add an option to use the FT_LOAD_DEFAULT if required?
306 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, glyphInfo.isBoldRequired, glyphData, error);
308 // Keep the width of the glyph before doing the software emboldening.
309 // It will be used to calculate a scale factor to be applied to the
310 // advance as Harfbuzz doesn't apply any SW emboldening to calculate
311 // the advance of the glyph.
313 if(FT_Err_Ok == error)
315 const auto& metrics = glyphData.mGlyphMetrics;
317 glyphInfo.width = static_cast<float>(metrics.width) * FROM_266;
318 glyphInfo.height = static_cast<float>(metrics.height) * FROM_266;
321 glyphInfo.xBearing += static_cast<float>(metrics.horiBearingX) * FROM_266;
322 glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
326 glyphInfo.xBearing += static_cast<float>(metrics.vertBearingX) * FROM_266;
327 glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
330 const bool isEmboldeningRequired = glyphInfo.isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
331 if(isEmboldeningRequired)
333 // Get dummy glyph data without embolden.
334 GlyphCacheManager::GlyphCacheData dummyData;
335 if(mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, false, dummyData, error))
337 // If the glyph is emboldened by software, the advance is multiplied by a
338 // scale factor to make it slightly bigger.
339 const float width = static_cast<float>(dummyData.mGlyphMetrics.width) * FROM_266;
340 if(!EqualsZero(width))
342 glyphInfo.advance *= (glyphInfo.width / width);
347 // Use the bounding box of the bitmap to correct the metrics.
348 // For some fonts i.e the SNum-3R the metrics need to be corrected,
349 // otherwise the glyphs 'dance' up and down depending on the
350 // font's point size.
351 FT_Glyph glyph = glyphData.mGlyph;
354 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
356 const float descender = glyphInfo.height - glyphInfo.yBearing;
357 glyphInfo.height = (bbox.yMax - bbox.yMin) * FROM_266;
358 glyphInfo.yBearing = glyphInfo.height - round(descender);
369 * @brief Create a bitmap representation of a glyph from a face font
371 * @param[in] glyphIndex The index of a glyph within the specified font.
372 * @param[out] data The bitmap data.
373 * @param[in] outlineWidth The width of the glyph outline in pixels.
374 * @param[in] isItalicRequired Whether the glyph requires italic style.
375 * @param[in] isBoldRequired Whether the glyph requires bold style.
377 void FontFaceCacheItem::CreateBitmap(
378 GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
380 GlyphCacheManager::GlyphCacheData glyphData;
383 // For the software italics.
384 bool isShearRequired = false;
386 #ifdef FREETYPE_BITMAP_SUPPORT
387 // Check to see if this is fixed size bitmap
388 if(mIsFixedSizeBitmap)
390 loadFlag = FT_LOAD_COLOR;
395 // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
396 // i.e. with the SNum-3R font.
397 // @todo: add an option to use the FT_LOAD_DEFAULT if required?
398 loadFlag = FT_LOAD_NO_AUTOHINT;
400 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, loadFlag, isBoldRequired, glyphData, error);
402 if(FT_Err_Ok == error)
404 if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
406 // Will do the software italic.
407 isShearRequired = true;
410 if(!glyphData.mIsBitmap)
412 // Convert to bitmap if necessary
413 FT_Glyph glyph = glyphData.mGlyph;
415 DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
417 int offsetX = 0, offsetY = 0;
418 bool isOutlineGlyph = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
419 bool isStrokeGlyphSuccess = false;
421 // Create a bitmap for the outline
424 // Retrieve the horizontal and vertical distance from the current pen position to the
425 // left and top border of the glyph bitmap for a normal glyph before applying the outline.
426 if(FT_Err_Ok == error)
428 // Copy new glyph, and keep original cached glyph.
429 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
430 if(FT_Err_Ok == error)
432 FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
434 offsetX = bitmapGlyph->left;
435 offsetY = bitmapGlyph->top;
437 // Copied FT_Glyph object must be released with FT_Done_Glyph
438 FT_Done_Glyph(glyph);
441 // Replace as original glyph
442 glyph = glyphData.mGlyph;
445 // Now apply the outline
449 error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
451 if(FT_Err_Ok == error)
453 // Copy glyph pointer for release memory.
454 FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
455 error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 0);
457 if(FT_Err_Ok == error)
459 FT_Stroker_Done(stroker);
460 isStrokeGlyphSuccess = true;
464 DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
469 DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
473 const bool ableUseCachedRenderedGlyph = EnableCacheRenderedGlyph() && !isOutlineGlyph && !isShearRequired;
475 // If we cache rendered glyph, and if we can use it, use cached thing first.
476 if(ableUseCachedRenderedGlyph && glyphData.mRenderedBuffer)
478 data.buffer = glyphData.mRenderedBuffer->buffer;
479 data.width = glyphData.mRenderedBuffer->width;
480 data.height = glyphData.mRenderedBuffer->height;
481 data.format = glyphData.mRenderedBuffer->format;
482 data.compressionType = glyphData.mRenderedBuffer->compressionType;
483 data.isBufferOwned = false;
487 // Copy new glyph, and keep original cached glyph.
488 // If we already copy new glyph by stroke, just re-use that.
489 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
490 if(FT_Err_Ok == error)
492 FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
496 // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
497 data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
498 data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
501 // If we can cache this bitmapGlyph, store it.
502 // Note : We will call this API once per each glyph.
503 if(ableUseCachedRenderedGlyph)
505 mGlyphCacheManager->CacheRenderedGlyphBuffer(glyphIndex, loadFlag, isBoldRequired, bitmapGlyph->bitmap, GetRenderedGlyphCompressPolicy());
507 GlyphCacheManager::GlyphCacheData dummyData;
508 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, loadFlag, isBoldRequired, dummyData, error);
510 if(DALI_LIKELY(FT_Err_Ok == error && dummyData.mRenderedBuffer))
512 data.buffer = dummyData.mRenderedBuffer->buffer;
513 data.width = dummyData.mRenderedBuffer->width;
514 data.height = dummyData.mRenderedBuffer->height;
515 data.format = dummyData.mRenderedBuffer->format;
516 data.compressionType = dummyData.mRenderedBuffer->compressionType;
517 data.isBufferOwned = false;
521 // Something problem during cache or get rendered glyph buffer.
522 // Move bitmap buffer into data.buffer
523 ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
528 // Move bitmap buffer into data.buffer
529 ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
532 // Copied FT_Glyph object must be released with FT_Done_Glyph
533 FT_Done_Glyph(glyph);
537 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
543 ConvertBitmap(data, *glyphData.mBitmap, isShearRequired);
546 data.isColorEmoji = mIsFixedSizeBitmap;
550 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
554 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
558 #ifdef FREETYPE_BITMAP_SUPPORT
559 // Check to see if this is fixed size bitmap
562 GlyphCacheManager::GlyphCacheData dummyData;
563 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, false, dummyData, error);
566 return FT_Err_Ok == error;
570 * Check if the character is supported by this font
571 * @param[in] character The character to test
573 bool FontFaceCacheItem::IsCharacterSupported(Character character)
575 if(nullptr == mCharacterSet)
577 // Create again the character set.
578 // It can be null if the ResetSystemDefaults() method has been called.
580 FontDescription description;
581 description.path = mPath;
582 description.family = std::move(FontFamily(mFreeTypeFace->family_name));
583 description.weight = FontWeight::NONE;
584 description.width = FontWidth::NONE;
585 description.slant = FontSlant::NONE;
587 // Note FreeType doesn't give too much info to build a proper font style.
588 if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
590 description.slant = FontSlant::ITALIC;
592 if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
594 description.weight = FontWeight::BOLD;
597 mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
600 return FcCharSetHasChar(mCharacterSet, character);
603 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
605 return FT_Get_Char_Index(mFreeTypeFace, character);
608 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character, Character variantSelector) const
610 return FT_Face_GetCharVariantIndex(mFreeTypeFace, character, variantSelector);
613 HarfBuzzFontHandle FontFaceCacheItem::GetHarfBuzzFont(const uint32_t& horizontalDpi, const uint32_t& verticalDpi)
615 // Create new harfbuzz font only first time or DPI changed.
616 if(DALI_UNLIKELY(!mHarfBuzzProxyFont || mHarfBuzzProxyFont->mHorizontalDpi != horizontalDpi || mHarfBuzzProxyFont->mVerticalDpi != verticalDpi))
618 mHarfBuzzProxyFont.reset(new HarfBuzzProxyFont(mFreeTypeFace, mRequestedPointSize, horizontalDpi, verticalDpi, mGlyphCacheManager.get()));
620 return mHarfBuzzProxyFont->GetHarfBuzzFont();
623 } // namespace Dali::TextAbstraction::Internal