2 * Copyright (c) 2023 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 Behavior about cache the rendered glyph cache.
46 constexpr bool DEFAULT_ENABLE_CACHE_RENDERED_GLYPH = true;
47 constexpr auto ENABLE_CACHE_RENDERED_GLYPH_ENV = "DALI_ENABLE_CACHE_RENDERED_GLYPH";
50 * @brief Get whether we allow to cache rendered glyph from environment.
51 * If not settuped, default as true.
52 * @note This value fixed when we call it first time.
53 * @return True if we allow to cache rendered glyph.
55 inline bool EnableCacheRenderedGlyph()
57 using Dali::EnvironmentVariable::GetEnvironmentVariable;
58 static auto numberString = GetEnvironmentVariable(ENABLE_CACHE_RENDERED_GLYPH_ENV);
59 static auto number = numberString ? (std::strtoul(numberString, nullptr, 10) ? true : false) : DEFAULT_ENABLE_CACHE_RENDERED_GLYPH;
64 * @brief Policy about compress the cached rendered glyph.
65 * It will be used only if CacheRenderedGlyph is enabled
67 constexpr auto DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY =
68 #if !(defined(DALI_PROFILE_UBUNTU) || defined(ANDROID) || defined(WIN32) || defined(__APPLE__))
69 GlyphCacheManager::CompressionPolicyType::MEMORY; // If tizen target
71 GlyphCacheManager::CompressionPolicyType::SPEED; // If not tizen target
73 constexpr auto RENDERED_GLYPH_COMPRESS_POLICY_ENV = "DALI_RENDERED_GLYPH_COMPRESS_POLICY";
76 * @brief Get whether we allow to cache rendered glyph from environment.
77 * If not settuped, default value used, as defined above.
78 * @note This value fixed when we call it first time.
79 * @return SPEED if value start with 's' or 'S'. MEMORY if value start with 'm' or 'M'. otherwise, use default
81 inline GlyphCacheManager::CompressionPolicyType GetRenderedGlyphCompressPolicy()
83 using Dali::EnvironmentVariable::GetEnvironmentVariable;
84 static auto policyString = GetEnvironmentVariable(RENDERED_GLYPH_COMPRESS_POLICY_ENV);
86 static auto policy = policyString ? policyString[0] == 's' || policyString[0] == 'S' ? GlyphCacheManager::CompressionPolicyType::SPEED
87 : policyString[0] == 'm' || policyString[0] == 'M' ? GlyphCacheManager::CompressionPolicyType::MEMORY
88 : DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY
89 : DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY;
94 FontFaceCacheItem::FontFaceCacheItem(const FT_Library& freeTypeLibrary,
96 GlyphCacheManager* glyphCacheManager,
98 PointSize26Dot6 requestedPointSize,
100 const FontMetrics& metrics)
101 : mFreeTypeLibrary(freeTypeLibrary),
102 mFreeTypeFace(ftFace),
103 mGlyphCacheManager(glyphCacheManager),
104 mHarfBuzzProxyFont(),
106 mRequestedPointSize(requestedPointSize),
109 mCharacterSet(nullptr),
111 mFixedWidthPixels(0.f),
112 mFixedHeightPixels(0.f),
115 mIsFixedSizeBitmap(false),
116 mHasColorTables(false)
120 FontFaceCacheItem::FontFaceCacheItem(const FT_Library& freeTypeLibrary,
122 GlyphCacheManager* glyphCacheManager,
123 const FontPath& path,
124 PointSize26Dot6 requestedPointSize,
126 const FontMetrics& metrics,
131 : mFreeTypeLibrary(freeTypeLibrary),
132 mFreeTypeFace(ftFace),
133 mGlyphCacheManager(glyphCacheManager),
134 mHarfBuzzProxyFont(),
136 mRequestedPointSize(requestedPointSize),
139 mCharacterSet(nullptr),
140 mFixedSizeIndex(fixedSizeIndex),
141 mFixedWidthPixels(fixedWidth),
142 mFixedHeightPixels(fixedHeight),
145 mIsFixedSizeBitmap(true),
146 mHasColorTables(hasColorTables)
150 // Move constructor. font client plugin container may call this.
151 // Note that we make nullptr of some reference sensitive values here.
152 FontFaceCacheItem::FontFaceCacheItem(FontFaceCacheItem&& rhs)
153 : mFreeTypeLibrary(rhs.mFreeTypeLibrary)
155 mFreeTypeFace = rhs.mFreeTypeFace;
156 mGlyphCacheManager = rhs.mGlyphCacheManager;
157 mHarfBuzzProxyFont = std::move(rhs.mHarfBuzzProxyFont);
158 mPath = std::move(rhs.mPath);
159 mRequestedPointSize = rhs.mRequestedPointSize;
160 mFaceIndex = rhs.mFaceIndex;
161 mMetrics = rhs.mMetrics;
162 mCharacterSet = rhs.mCharacterSet;
163 mFixedSizeIndex = rhs.mFixedSizeIndex;
164 mFixedWidthPixels = rhs.mFixedWidthPixels;
165 mFixedHeightPixels = rhs.mFixedWidthPixels;
166 mVectorFontId = rhs.mVectorFontId;
167 mFontId = rhs.mFontId;
168 mIsFixedSizeBitmap = rhs.mIsFixedSizeBitmap;
169 mHasColorTables = rhs.mHasColorTables;
171 rhs.mFreeTypeFace = nullptr;
172 rhs.mGlyphCacheManager = nullptr;
175 FontFaceCacheItem::~FontFaceCacheItem()
177 // delete cached glyph informations before free face.
178 if(mGlyphCacheManager)
180 mGlyphCacheManager->RemoveGlyphFromFace(mFreeTypeFace);
183 // delete harfbuzz proxy font before free face.
184 if(mHarfBuzzProxyFont)
186 mHarfBuzzProxyFont.reset();
192 FT_Done_Face(mFreeTypeFace);
196 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
200 // Adjust the metrics if the fixed-size font should be down-scaled
201 if(mIsFixedSizeBitmap)
203 const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
205 if(desiredFixedSize > 0.f)
207 const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
209 metrics.ascender = round(metrics.ascender * scaleFactor);
210 metrics.descender = round(metrics.descender * scaleFactor);
211 metrics.height = round(metrics.height * scaleFactor);
212 metrics.underlinePosition = metrics.underlinePosition * scaleFactor;
213 metrics.underlineThickness = metrics.underlineThickness * scaleFactor;
218 bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
222 GlyphCacheManager::GlyphCacheDataPtr glyphDataPtr;
225 #ifdef FREETYPE_BITMAP_SUPPORT
226 // Check to see if we should be loading a Fixed Size bitmap?
227 if(mIsFixedSizeBitmap)
229 FT_Select_Size(mFreeTypeFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
230 mGlyphCacheManager->GetGlyphCacheDataFromIndex(mFreeTypeFace, glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, glyphDataPtr, error);
232 if(FT_Err_Ok == error)
234 GlyphCacheManager::GlyphCacheData& glyphData = *glyphDataPtr.get();
236 glyphInfo.width = mFixedWidthPixels;
237 glyphInfo.height = mFixedHeightPixels;
238 glyphInfo.advance = mFixedWidthPixels;
239 glyphInfo.xBearing = 0.0f;
241 const auto& metrics = glyphData.mGlyphMetrics;
245 glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
249 glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
252 // Adjust the metrics if the fixed-size font should be down-scaled
253 const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
255 if(desiredFixedSize > 0.f)
257 const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
258 glyphInfo.width = round(glyphInfo.width * scaleFactor);
259 glyphInfo.height = round(glyphInfo.height * scaleFactor);
260 glyphInfo.advance = round(glyphInfo.advance * scaleFactor);
261 glyphInfo.xBearing = round(glyphInfo.xBearing * scaleFactor);
262 glyphInfo.yBearing = round(glyphInfo.yBearing * scaleFactor);
264 glyphInfo.scaleFactor = scaleFactor;
266 if(scaleFactor < MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE)
268 // Resize bitmap glyph and cache it due to the performance issue.
269 // If scaleFactor is too big, cached bitmap may hold too big memory.
270 // So, we only hold small enough case.
272 // TODO : If dpiVertical value changed, this resize feature will be break down.
273 // Otherwise, this glyph will be resized only one times.
274 mGlyphCacheManager->ResizeBitmapGlyph(mFreeTypeFace, glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, static_cast<uint32_t>(glyphInfo.width), static_cast<uint32_t>(glyphInfo.height));
280 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
287 // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
288 // i.e. with the SNum-3R font.
289 // @todo: add an option to use the FT_LOAD_DEFAULT if required?
290 mGlyphCacheManager->GetGlyphCacheDataFromIndex(mFreeTypeFace, glyphInfo.index, FT_LOAD_NO_AUTOHINT, glyphInfo.isBoldRequired, glyphDataPtr, error);
292 // Keep the width of the glyph before doing the software emboldening.
293 // It will be used to calculate a scale factor to be applied to the
294 // advance as Harfbuzz doesn't apply any SW emboldening to calculate
295 // the advance of the glyph.
297 if(FT_Err_Ok == error)
299 GlyphCacheManager::GlyphCacheData& glyphData = *glyphDataPtr.get();
301 const auto& metrics = glyphData.mGlyphMetrics;
303 glyphInfo.width = static_cast<float>(metrics.width) * FROM_266;
304 glyphInfo.height = static_cast<float>(metrics.height) * FROM_266;
307 glyphInfo.xBearing += static_cast<float>(metrics.horiBearingX) * FROM_266;
308 glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
312 glyphInfo.xBearing += static_cast<float>(metrics.vertBearingX) * FROM_266;
313 glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
315 glyphInfo.advance = round(glyphInfo.advance);
317 const bool isEmboldeningRequired = glyphInfo.isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
318 if(isEmboldeningRequired)
320 // Get dummy glyph data without embolden.
321 GlyphCacheManager::GlyphCacheDataPtr dummyDataPtr;
322 if(mGlyphCacheManager->GetGlyphCacheDataFromIndex(mFreeTypeFace, glyphInfo.index, FT_LOAD_NO_AUTOHINT, false, dummyDataPtr, error))
324 // If the glyph is emboldened by software, the advance is multiplied by a
325 // scale factor to make it slightly bigger.
326 const float width = static_cast<float>(dummyDataPtr->mGlyphMetrics.width) * FROM_266;
327 if(!EqualsZero(width))
329 glyphInfo.advance *= (glyphInfo.width / width);
334 // Use the bounding box of the bitmap to correct the metrics.
335 // For some fonts i.e the SNum-3R the metrics need to be corrected,
336 // otherwise the glyphs 'dance' up and down depending on the
337 // font's point size.
338 FT_Glyph glyph = glyphData.mGlyph;
341 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
343 const float descender = glyphInfo.height - glyphInfo.yBearing;
344 glyphInfo.height = (bbox.yMax - bbox.yMin) * FROM_266;
345 glyphInfo.yBearing = glyphInfo.height - round(descender);
356 * @brief Create a bitmap representation of a glyph from a face font
358 * @param[in] glyphIndex The index of a glyph within the specified font.
359 * @param[out] data The bitmap data.
360 * @param[in] outlineWidth The width of the glyph outline in pixels.
361 * @param[in] isItalicRequired Whether the glyph requires italic style.
362 * @param[in] isBoldRequired Whether the glyph requires bold style.
364 void FontFaceCacheItem::CreateBitmap(
365 GlyphIndex glyphIndex, Dali::TextAbstraction::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
367 GlyphCacheManager::GlyphCacheDataPtr glyphDataPtr;
370 // For the software italics.
371 bool isShearRequired = false;
373 #ifdef FREETYPE_BITMAP_SUPPORT
374 // Check to see if this is fixed size bitmap
375 if(mIsFixedSizeBitmap)
377 loadFlag = FT_LOAD_COLOR;
382 // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
383 // i.e. with the SNum-3R font.
384 // @todo: add an option to use the FT_LOAD_DEFAULT if required?
385 loadFlag = FT_LOAD_NO_AUTOHINT;
387 mGlyphCacheManager->GetGlyphCacheDataFromIndex(mFreeTypeFace, glyphIndex, loadFlag, isBoldRequired, glyphDataPtr, error);
389 if(FT_Err_Ok == error)
391 GlyphCacheManager::GlyphCacheData& glyphData = *glyphDataPtr.get();
393 if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
395 // Will do the software italic.
396 isShearRequired = true;
399 if(!glyphData.mIsBitmap)
401 // Convert to bitmap if necessary
402 FT_Glyph glyph = glyphData.mGlyph;
404 DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
406 int offsetX = 0, offsetY = 0;
407 bool isOutlineGlyph = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
408 bool isStrokeGlyphSuccess = false;
410 // Create a bitmap for the outline
413 // Retrieve the horizontal and vertical distance from the current pen position to the
414 // left and top border of the glyph bitmap for a normal glyph before applying the outline.
415 if(FT_Err_Ok == error)
417 // Copy new glyph, and keep original cached glyph.
418 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
419 if(FT_Err_Ok == error)
421 FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
423 offsetX = bitmapGlyph->left;
424 offsetY = bitmapGlyph->top;
426 // Copied FT_Glyph object must be released with FT_Done_Glyph
427 FT_Done_Glyph(glyph);
430 // Replace as original glyph
431 glyph = glyphData.mGlyph;
434 // Now apply the outline
438 error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
440 if(FT_Err_Ok == error)
442 // Copy glyph pointer for release memory.
443 FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
444 error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 0);
446 if(FT_Err_Ok == error)
448 FT_Stroker_Done(stroker);
449 isStrokeGlyphSuccess = true;
453 DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
458 DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
462 const bool ableUseCachedRenderedGlyph = EnableCacheRenderedGlyph() && !isOutlineGlyph && !isShearRequired;
464 // If we cache rendered glyph, and if we can use it, use cached thing first.
465 if(ableUseCachedRenderedGlyph && glyphData.mRenderedBuffer)
467 data.buffer = glyphData.mRenderedBuffer->buffer;
468 data.width = glyphData.mRenderedBuffer->width;
469 data.height = glyphData.mRenderedBuffer->height;
470 data.format = glyphData.mRenderedBuffer->format;
471 data.compressionType = glyphData.mRenderedBuffer->compressionType;
472 data.isBufferOwned = false;
476 // Copy new glyph, and keep original cached glyph.
477 // If we already copy new glyph by stroke, just re-use that.
478 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
479 if(FT_Err_Ok == error)
481 FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
485 // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
486 data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
487 data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
490 // If we can cache this bitmapGlyph, store it.
491 // Note : We will call this API once per each glyph.
492 if(ableUseCachedRenderedGlyph)
494 mGlyphCacheManager->CacheRenderedGlyphBuffer(mFreeTypeFace, glyphIndex, loadFlag, isBoldRequired, bitmapGlyph->bitmap, GetRenderedGlyphCompressPolicy());
496 GlyphCacheManager::GlyphCacheDataPtr dummyDataPtr;
497 mGlyphCacheManager->GetGlyphCacheDataFromIndex(mFreeTypeFace, glyphIndex, loadFlag, isBoldRequired, dummyDataPtr, error);
499 if(DALI_LIKELY(FT_Err_Ok == error && dummyDataPtr->mRenderedBuffer))
501 data.buffer = dummyDataPtr->mRenderedBuffer->buffer;
502 data.width = dummyDataPtr->mRenderedBuffer->width;
503 data.height = dummyDataPtr->mRenderedBuffer->height;
504 data.format = dummyDataPtr->mRenderedBuffer->format;
505 data.compressionType = dummyDataPtr->mRenderedBuffer->compressionType;
506 data.isBufferOwned = false;
510 // Something problem during cache or get rendered glyph buffer.
511 // Move bitmap buffer into data.buffer
512 ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
517 // Move bitmap buffer into data.buffer
518 ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
521 // Copied FT_Glyph object must be released with FT_Done_Glyph
522 FT_Done_Glyph(glyph);
526 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
532 ConvertBitmap(data, *glyphData.mBitmap, isShearRequired);
535 data.isColorEmoji = mIsFixedSizeBitmap;
539 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
543 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
547 #ifdef FREETYPE_BITMAP_SUPPORT
548 // Check to see if this is fixed size bitmap
551 GlyphCacheManager::GlyphCacheDataPtr dummyDataPtr;
552 mGlyphCacheManager->GetGlyphCacheDataFromIndex(mFreeTypeFace, glyphIndex, FT_LOAD_COLOR, false, dummyDataPtr, error);
555 return FT_Err_Ok == error;
559 * Check if the character is supported by this font
560 * @param[in] character The character to test
562 bool FontFaceCacheItem::IsCharacterSupported(Character character)
564 if(nullptr == mCharacterSet)
566 // Create again the character set.
567 // It can be null if the ResetSystemDefaults() method has been called.
569 FontDescription description;
570 description.path = mPath;
571 description.family = std::move(FontFamily(mFreeTypeFace->family_name));
572 description.weight = FontWeight::NONE;
573 description.width = FontWidth::NONE;
574 description.slant = FontSlant::NONE;
576 // Note FreeType doesn't give too much info to build a proper font style.
577 if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
579 description.slant = FontSlant::ITALIC;
581 if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
583 description.weight = FontWeight::BOLD;
586 mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
589 return FcCharSetHasChar(mCharacterSet, character);
592 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
594 return FT_Get_Char_Index(mFreeTypeFace, character);
597 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character, Character variantSelector) const
599 return FT_Face_GetCharVariantIndex(mFreeTypeFace, character, variantSelector);
602 HarfBuzzFontHandle FontFaceCacheItem::GetHarfBuzzFont(const uint32_t& horizontalDpi, const uint32_t& verticalDpi)
604 // Create new harfbuzz font only first time or DPI changed.
605 if(DALI_UNLIKELY(!mHarfBuzzProxyFont || mHarfBuzzProxyFont->mHorizontalDpi != horizontalDpi || mHarfBuzzProxyFont->mVerticalDpi != verticalDpi))
607 mHarfBuzzProxyFont.reset(new HarfBuzzProxyFont(mFreeTypeFace, mRequestedPointSize, horizontalDpi, verticalDpi, mGlyphCacheManager));
609 return mHarfBuzzProxyFont->GetHarfBuzzFont();
612 } // namespace Dali::TextAbstraction::Internal