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 = 2u;
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 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 FontFaceCacheItem::FontFaceCacheItem(FT_Library& freeTypeLibrary,
69 PointSize26Dot6 requestedPointSize,
71 const FontMetrics& metrics)
72 : mFreeTypeLibrary(freeTypeLibrary),
73 mFreeTypeFace(ftFace),
74 mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
77 mRequestedPointSize(requestedPointSize),
80 mCharacterSet(nullptr),
82 mFixedWidthPixels(0.f),
83 mFixedHeightPixels(0.f),
86 mIsFixedSizeBitmap(false),
87 mHasColorTables(false)
91 FontFaceCacheItem::FontFaceCacheItem(FT_Library& freeTypeLibrary,
94 PointSize26Dot6 requestedPointSize,
96 const FontMetrics& metrics,
101 : mFreeTypeLibrary(freeTypeLibrary),
102 mFreeTypeFace(ftFace),
103 mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
104 mHarfBuzzProxyFont(),
106 mRequestedPointSize(requestedPointSize),
109 mCharacterSet(nullptr),
110 mFixedSizeIndex(fixedSizeIndex),
111 mFixedWidthPixels(fixedWidth),
112 mFixedHeightPixels(fixedHeight),
115 mIsFixedSizeBitmap(true),
116 mHasColorTables(hasColorTables)
120 // Move constructor. font client plugin container may call this.
121 // Note that we make nullptr of some reference sensitive values here.
122 FontFaceCacheItem::FontFaceCacheItem(FontFaceCacheItem&& rhs)
123 : mFreeTypeLibrary(rhs.mFreeTypeLibrary)
125 mFreeTypeFace = rhs.mFreeTypeFace;
126 mGlyphCacheManager = std::move(rhs.mGlyphCacheManager);
127 mHarfBuzzProxyFont = std::move(rhs.mHarfBuzzProxyFont);
128 mPath = std::move(rhs.mPath);
129 mRequestedPointSize = rhs.mRequestedPointSize;
130 mFaceIndex = rhs.mFaceIndex;
131 mMetrics = rhs.mMetrics;
132 mCharacterSet = rhs.mCharacterSet;
133 mFixedSizeIndex = rhs.mFixedSizeIndex;
134 mFixedWidthPixels = rhs.mFixedWidthPixels;
135 mFixedHeightPixels = rhs.mFixedWidthPixels;
136 mVectorFontId = rhs.mVectorFontId;
137 mFontId = rhs.mFontId;
138 mIsFixedSizeBitmap = rhs.mIsFixedSizeBitmap;
139 mHasColorTables = rhs.mHasColorTables;
141 rhs.mFreeTypeFace = nullptr;
144 FontFaceCacheItem::~FontFaceCacheItem()
146 // delete glyph cache manager before free face.
147 if(mGlyphCacheManager)
149 mGlyphCacheManager.reset();
152 if(mHarfBuzzProxyFont)
154 mHarfBuzzProxyFont.reset();
160 FT_Done_Face(mFreeTypeFace);
164 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
168 // Adjust the metrics if the fixed-size font should be down-scaled
169 if(mIsFixedSizeBitmap)
171 const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
173 if(desiredFixedSize > 0.f)
175 const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
177 metrics.ascender = round(metrics.ascender * scaleFactor);
178 metrics.descender = round(metrics.descender * scaleFactor);
179 metrics.height = round(metrics.height * scaleFactor);
180 metrics.underlinePosition = metrics.underlinePosition * scaleFactor;
181 metrics.underlineThickness = metrics.underlineThickness * scaleFactor;
186 bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
190 GlyphCacheManager::GlyphCacheData glyphData;
193 #ifdef FREETYPE_BITMAP_SUPPORT
194 // Check to see if we should be loading a Fixed Size bitmap?
195 if(mIsFixedSizeBitmap)
197 FT_Select_Size(mFreeTypeFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
198 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, glyphData, error);
200 if(FT_Err_Ok == error)
202 glyphInfo.width = mFixedWidthPixels;
203 glyphInfo.height = mFixedHeightPixels;
204 glyphInfo.advance = mFixedWidthPixels;
205 glyphInfo.xBearing = 0.0f;
207 const auto& metrics = glyphData.mGlyphMetrics;
211 glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
215 glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
218 // Adjust the metrics if the fixed-size font should be down-scaled
219 const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
221 if(desiredFixedSize > 0.f)
223 const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
224 glyphInfo.width = round(glyphInfo.width * scaleFactor);
225 glyphInfo.height = round(glyphInfo.height * scaleFactor);
226 glyphInfo.advance = round(glyphInfo.advance * scaleFactor);
227 glyphInfo.xBearing = round(glyphInfo.xBearing * scaleFactor);
228 glyphInfo.yBearing = round(glyphInfo.yBearing * scaleFactor);
230 glyphInfo.scaleFactor = scaleFactor;
232 if(scaleFactor < MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE)
234 // Resize bitmap glyph and cache it due to the performance issue.
235 // If scaleFactor is too big, cached bitmap may hold too big memory.
236 // So, we only hold small enough case.
238 // TODO : If dpiVertical value changed, this resize feature will be break down.
239 // Otherwise, this glyph will be resized only one times.
240 mGlyphCacheManager->ResizeBitmapGlyph(glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, static_cast<uint32_t>(glyphInfo.width), static_cast<uint32_t>(glyphInfo.height));
246 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
253 // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
254 // i.e. with the SNum-3R font.
255 // @todo: add an option to use the FT_LOAD_DEFAULT if required?
256 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, glyphInfo.isBoldRequired, glyphData, error);
258 // Keep the width of the glyph before doing the software emboldening.
259 // It will be used to calculate a scale factor to be applied to the
260 // advance as Harfbuzz doesn't apply any SW emboldening to calculate
261 // the advance of the glyph.
263 if(FT_Err_Ok == error)
265 const auto& metrics = glyphData.mGlyphMetrics;
267 glyphInfo.width = static_cast<float>(metrics.width) * FROM_266;
268 glyphInfo.height = static_cast<float>(metrics.height) * FROM_266;
271 glyphInfo.xBearing += static_cast<float>(metrics.horiBearingX) * FROM_266;
272 glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
276 glyphInfo.xBearing += static_cast<float>(metrics.vertBearingX) * FROM_266;
277 glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
280 const bool isEmboldeningRequired = glyphInfo.isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
281 if(isEmboldeningRequired)
283 // Get dummy glyph data without embolden.
284 GlyphCacheManager::GlyphCacheData dummyData;
285 if(mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, false, dummyData, error))
287 // If the glyph is emboldened by software, the advance is multiplied by a
288 // scale factor to make it slightly bigger.
289 const float width = static_cast<float>(dummyData.mGlyphMetrics.width) * FROM_266;
290 if(!EqualsZero(width))
292 glyphInfo.advance *= (glyphInfo.width / width);
297 // Use the bounding box of the bitmap to correct the metrics.
298 // For some fonts i.e the SNum-3R the metrics need to be corrected,
299 // otherwise the glyphs 'dance' up and down depending on the
300 // font's point size.
301 FT_Glyph glyph = glyphData.mGlyph;
304 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
306 const float descender = glyphInfo.height - glyphInfo.yBearing;
307 glyphInfo.height = (bbox.yMax - bbox.yMin) * FROM_266;
308 glyphInfo.yBearing = glyphInfo.height - round(descender);
319 * @brief Create a bitmap representation of a glyph from a face font
321 * @param[in] glyphIndex The index of a glyph within the specified font.
322 * @param[in] isItalicRequired Whether the glyph requires italic style.
323 * @param[in] isBoldRequired Whether the glyph requires bold style.
324 * @param[out] data The bitmap data.
325 * @param[in] outlineWidth The width of the glyph outline in pixels.
327 void FontFaceCacheItem::CreateBitmap(
328 GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
330 GlyphCacheManager::GlyphCacheData glyphData;
332 // For the software italics.
333 bool isShearRequired = false;
335 #ifdef FREETYPE_BITMAP_SUPPORT
336 // Check to see if this is fixed size bitmap
337 if(mIsFixedSizeBitmap)
339 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, isBoldRequired, glyphData, error);
344 // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
345 // i.e. with the SNum-3R font.
346 // @todo: add an option to use the FT_LOAD_DEFAULT if required?
347 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_NO_AUTOHINT, isBoldRequired, glyphData, error);
349 if(FT_Err_Ok == error)
351 if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
353 // Will do the software italic.
354 isShearRequired = true;
357 // Convert to bitmap if necessary
358 if(!glyphData.mIsBitmap)
360 FT_Glyph glyph = glyphData.mGlyph;
362 DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
364 int offsetX = 0, offsetY = 0;
365 bool isOutlineGlyph = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
366 bool isStrokeGlyphSuccess = false;
368 // Create a bitmap for the outline
371 // Retrieve the horizontal and vertical distance from the current pen position to the
372 // left and top border of the glyph bitmap for a normal glyph before applying the outline.
373 if(FT_Err_Ok == error)
375 // Copy new glyph, and keep original cached glyph.
376 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
377 if(FT_Err_Ok == error)
379 FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
381 offsetX = bitmapGlyph->left;
382 offsetY = bitmapGlyph->top;
384 // Copied FT_Glyph object must be released with FT_Done_Glyph
385 FT_Done_Glyph(glyph);
388 // Replace as original glyph
389 glyph = glyphData.mGlyph;
392 // Now apply the outline
396 error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
398 if(FT_Err_Ok == error)
400 // Copy glyph pointer for release memory.
401 FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
402 error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 0);
404 if(FT_Err_Ok == error)
406 FT_Stroker_Done(stroker);
407 isStrokeGlyphSuccess = true;
411 DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
416 DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
420 // Copy new glyph, and keep original cached glyph.
421 // If we already copy new glyph by stroke, just re-use that.
422 error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
423 if(FT_Err_Ok == error)
425 FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
429 // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
430 data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
431 data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
434 // Move bitmap buffer into data.buffer
435 ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
437 // Copied FT_Glyph object must be released with FT_Done_Glyph
438 FT_Done_Glyph(glyph);
442 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
447 ConvertBitmap(data, *glyphData.mBitmap, isShearRequired);
450 data.isColorEmoji = mIsFixedSizeBitmap;
454 DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
458 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
462 #ifdef FREETYPE_BITMAP_SUPPORT
463 // Check to see if this is fixed size bitmap
466 GlyphCacheManager::GlyphCacheData dummyData;
467 mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, false, dummyData, error);
470 return FT_Err_Ok == error;
474 * Check if the character is supported by this font
475 * @param[in] character The character to test
477 bool FontFaceCacheItem::IsCharacterSupported(Character character)
479 if(nullptr == mCharacterSet)
481 // Create again the character set.
482 // It can be null if the ResetSystemDefaults() method has been called.
484 FontDescription description;
485 description.path = mPath;
486 description.family = std::move(FontFamily(mFreeTypeFace->family_name));
487 description.weight = FontWeight::NONE;
488 description.width = FontWidth::NONE;
489 description.slant = FontSlant::NONE;
491 // Note FreeType doesn't give too much info to build a proper font style.
492 if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
494 description.slant = FontSlant::ITALIC;
496 if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
498 description.weight = FontWeight::BOLD;
501 mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
504 return FcCharSetHasChar(mCharacterSet, character);
507 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
509 return FT_Get_Char_Index(mFreeTypeFace, character);
512 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character, Character variantSelector) const
514 return FT_Face_GetCharVariantIndex(mFreeTypeFace, character, variantSelector);
517 HarfBuzzFontHandle FontFaceCacheItem::GetHarfBuzzFont(const uint32_t& horizontalDpi, const uint32_t& verticalDpi)
519 // Create new harfbuzz font only first time or DPI changed.
520 if(DALI_UNLIKELY(!mHarfBuzzProxyFont || mHarfBuzzProxyFont->mHorizontalDpi != horizontalDpi || mHarfBuzzProxyFont->mVerticalDpi != verticalDpi))
522 mHarfBuzzProxyFont.reset(new HarfBuzzProxyFont(mFreeTypeFace, mRequestedPointSize, horizontalDpi, verticalDpi, mGlyphCacheManager.get()));
524 return mHarfBuzzProxyFont->GetHarfBuzzFont();
527 } // namespace Dali::TextAbstraction::Internal