Merge "Harfbuzz Shape optimize" into devel/master
[platform/core/uifw/dali-adaptor.git] / dali / internal / text / text-abstraction / plugin / font-face-cache-item.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 // EXTERNAL HEADERS
18 #include <dali/integration-api/debug.h>
19
20 // INTERNAL HEADERS
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>
24
25 #if defined(DEBUG_ENABLED)
26 extern Dali::Integration::Log::Filter* gFontClientLogFilter;
27 #endif
28
29 namespace Dali::TextAbstraction::Internal
30 {
31 namespace
32 {
33 const float FROM_266        = 1.0f / 64.0f;
34 const float POINTS_PER_INCH = 72.f;
35
36 /**
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.
40  */
41 constexpr float MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE = 1.5f;
42
43 /**
44  * @brief Maximum size of glyph cache per each font face.
45  */
46 constexpr std::size_t DEFAULT_GLYPH_CACHE_MAX         = 128;
47 constexpr std::size_t MINIMUM_SIZE_OF_GLYPH_CACHE_MAX = 2u;
48
49 constexpr auto MAX_NUMBER_OF_GLYPH_CACHE_ENV = "DALI_GLYPH_CACHE_MAX";
50
51 /**
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.
56  */
57 size_t GetMaxNumberOfGlyphCache()
58 {
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;
63 }
64 } // namespace
65
66 FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
67                                      FT_Face            ftFace,
68                                      const FontPath&    path,
69                                      PointSize26Dot6    requestedPointSize,
70                                      FaceIndex          face,
71                                      const FontMetrics& metrics)
72 : mFreeTypeLibrary(freeTypeLibrary),
73   mFreeTypeFace(ftFace),
74   mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
75   mHarfBuzzProxyFont(),
76   mPath(path),
77   mRequestedPointSize(requestedPointSize),
78   mFaceIndex(face),
79   mMetrics(metrics),
80   mCharacterSet(nullptr),
81   mFixedSizeIndex(0),
82   mFixedWidthPixels(0.f),
83   mFixedHeightPixels(0.f),
84   mVectorFontId(0u),
85   mFontId(0u),
86   mIsFixedSizeBitmap(false),
87   mHasColorTables(false)
88 {
89 }
90
91 FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
92                                      FT_Face            ftFace,
93                                      const FontPath&    path,
94                                      PointSize26Dot6    requestedPointSize,
95                                      FaceIndex          face,
96                                      const FontMetrics& metrics,
97                                      int                fixedSizeIndex,
98                                      float              fixedWidth,
99                                      float              fixedHeight,
100                                      bool               hasColorTables)
101 : mFreeTypeLibrary(freeTypeLibrary),
102   mFreeTypeFace(ftFace),
103   mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
104   mHarfBuzzProxyFont(),
105   mPath(path),
106   mRequestedPointSize(requestedPointSize),
107   mFaceIndex(face),
108   mMetrics(metrics),
109   mCharacterSet(nullptr),
110   mFixedSizeIndex(fixedSizeIndex),
111   mFixedWidthPixels(fixedWidth),
112   mFixedHeightPixels(fixedHeight),
113   mVectorFontId(0u),
114   mFontId(0u),
115   mIsFixedSizeBitmap(true),
116   mHasColorTables(hasColorTables)
117 {
118 }
119
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)
124 {
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;
140
141   rhs.mFreeTypeFace = nullptr;
142 }
143
144 FontFaceCacheItem::~FontFaceCacheItem()
145 {
146   // delete glyph cache manager before free face.
147   if(mGlyphCacheManager)
148   {
149     mGlyphCacheManager.reset();
150   }
151
152   if(mHarfBuzzProxyFont)
153   {
154     mHarfBuzzProxyFont.reset();
155   }
156
157   // Free face.
158   if(mFreeTypeFace)
159   {
160     FT_Done_Face(mFreeTypeFace);
161   }
162 }
163
164 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
165 {
166   metrics = mMetrics;
167
168   // Adjust the metrics if the fixed-size font should be down-scaled
169   if(mIsFixedSizeBitmap)
170   {
171     const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
172
173     if(desiredFixedSize > 0.f)
174     {
175       const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
176
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;
182     }
183   }
184 }
185
186 bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
187 {
188   bool success(true);
189
190   GlyphCacheManager::GlyphCacheData glyphData;
191   FT_Error                          error;
192
193 #ifdef FREETYPE_BITMAP_SUPPORT
194   // Check to see if we should be loading a Fixed Size bitmap?
195   if(mIsFixedSizeBitmap)
196   {
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);
199
200     if(FT_Err_Ok == error)
201     {
202       glyphInfo.width    = mFixedWidthPixels;
203       glyphInfo.height   = mFixedHeightPixels;
204       glyphInfo.advance  = mFixedWidthPixels;
205       glyphInfo.xBearing = 0.0f;
206
207       const auto& metrics = glyphData.mGlyphMetrics;
208
209       if(horizontal)
210       {
211         glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
212       }
213       else
214       {
215         glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
216       }
217
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;
220
221       if(desiredFixedSize > 0.f)
222       {
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);
229
230         glyphInfo.scaleFactor = scaleFactor;
231
232         if(scaleFactor < MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE)
233         {
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.
237
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));
241         }
242       }
243     }
244     else
245     {
246       DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
247       success = false;
248     }
249   }
250   else
251 #endif
252   {
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);
257
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.
262
263     if(FT_Err_Ok == error)
264     {
265       const auto& metrics = glyphData.mGlyphMetrics;
266
267       glyphInfo.width  = static_cast<float>(metrics.width) * FROM_266;
268       glyphInfo.height = static_cast<float>(metrics.height) * FROM_266;
269       if(horizontal)
270       {
271         glyphInfo.xBearing += static_cast<float>(metrics.horiBearingX) * FROM_266;
272         glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
273       }
274       else
275       {
276         glyphInfo.xBearing += static_cast<float>(metrics.vertBearingX) * FROM_266;
277         glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
278       }
279
280       const bool isEmboldeningRequired = glyphInfo.isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
281       if(isEmboldeningRequired)
282       {
283         // Get dummy glyph data without embolden.
284         GlyphCacheManager::GlyphCacheData dummyData;
285         if(mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, false, dummyData, error))
286         {
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))
291           {
292             glyphInfo.advance *= (glyphInfo.width / width);
293           }
294         }
295       }
296
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;
302
303       FT_BBox bbox;
304       FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
305
306       const float descender = glyphInfo.height - glyphInfo.yBearing;
307       glyphInfo.height      = (bbox.yMax - bbox.yMin) * FROM_266;
308       glyphInfo.yBearing    = glyphInfo.height - round(descender);
309     }
310     else
311     {
312       success = false;
313     }
314   }
315   return success;
316 }
317
318 /**
319  * @brief Create a bitmap representation of a glyph from a face font
320  *
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.
326  */
327 void FontFaceCacheItem::CreateBitmap(
328   GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
329 {
330   GlyphCacheManager::GlyphCacheData glyphData;
331   FT_Error                          error;
332   // For the software italics.
333   bool isShearRequired = false;
334
335 #ifdef FREETYPE_BITMAP_SUPPORT
336   // Check to see if this is fixed size bitmap
337   if(mIsFixedSizeBitmap)
338   {
339     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, isBoldRequired, glyphData, error);
340   }
341   else
342 #endif
343   {
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);
348   }
349   if(FT_Err_Ok == error)
350   {
351     if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
352     {
353       // Will do the software italic.
354       isShearRequired = true;
355     }
356
357     // Convert to bitmap if necessary
358     if(!glyphData.mIsBitmap)
359     {
360       FT_Glyph glyph = glyphData.mGlyph;
361
362       DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
363
364       int  offsetX = 0, offsetY = 0;
365       bool isOutlineGlyph       = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
366       bool isStrokeGlyphSuccess = false;
367
368       // Create a bitmap for the outline
369       if(isOutlineGlyph)
370       {
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)
374         {
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)
378           {
379             FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
380
381             offsetX = bitmapGlyph->left;
382             offsetY = bitmapGlyph->top;
383
384             // Copied FT_Glyph object must be released with FT_Done_Glyph
385             FT_Done_Glyph(glyph);
386           }
387
388           // Replace as original glyph
389           glyph = glyphData.mGlyph;
390         }
391
392         // Now apply the outline
393
394         // Set up a stroker
395         FT_Stroker stroker;
396         error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
397
398         if(FT_Err_Ok == error)
399         {
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);
403
404           if(FT_Err_Ok == error)
405           {
406             FT_Stroker_Done(stroker);
407             isStrokeGlyphSuccess = true;
408           }
409           else
410           {
411             DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
412           }
413         }
414         else
415         {
416           DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
417         }
418       }
419
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)
424       {
425         FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
426
427         if(isOutlineGlyph)
428         {
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;
432         }
433
434         // Move bitmap buffer into data.buffer
435         ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
436
437         // Copied FT_Glyph object must be released with FT_Done_Glyph
438         FT_Done_Glyph(glyph);
439       }
440       else
441       {
442         DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
443       }
444     }
445     else
446     {
447       ConvertBitmap(data, *glyphData.mBitmap, isShearRequired);
448     }
449
450     data.isColorEmoji = mIsFixedSizeBitmap;
451   }
452   else
453   {
454     DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
455   }
456 }
457
458 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
459 {
460   FT_Error error = -1;
461
462 #ifdef FREETYPE_BITMAP_SUPPORT
463   // Check to see if this is fixed size bitmap
464   if(mHasColorTables)
465   {
466     GlyphCacheManager::GlyphCacheData dummyData;
467     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, false, dummyData, error);
468   }
469 #endif
470   return FT_Err_Ok == error;
471 }
472
473 /**
474  * Check if the character is supported by this font
475  * @param[in] character The character to test
476  */
477 bool FontFaceCacheItem::IsCharacterSupported(Character character)
478 {
479   if(nullptr == mCharacterSet)
480   {
481     // Create again the character set.
482     // It can be null if the ResetSystemDefaults() method has been called.
483
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;
490
491     // Note FreeType doesn't give too much info to build a proper font style.
492     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
493     {
494       description.slant = FontSlant::ITALIC;
495     }
496     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
497     {
498       description.weight = FontWeight::BOLD;
499     }
500
501     mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
502   }
503
504   return FcCharSetHasChar(mCharacterSet, character);
505 }
506
507 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
508 {
509   return FT_Get_Char_Index(mFreeTypeFace, character);
510 }
511
512 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character, Character variantSelector) const
513 {
514   return FT_Face_GetCharVariantIndex(mFreeTypeFace, character, variantSelector);
515 }
516
517 HarfBuzzFontHandle FontFaceCacheItem::GetHarfBuzzFont(const uint32_t& horizontalDpi, const uint32_t& verticalDpi)
518 {
519   // Create new harfbuzz font only first time or DPI changed.
520   if(DALI_UNLIKELY(!mHarfBuzzProxyFont || mHarfBuzzProxyFont->mHorizontalDpi != horizontalDpi || mHarfBuzzProxyFont->mVerticalDpi != verticalDpi))
521   {
522     mHarfBuzzProxyFont.reset(new HarfBuzzProxyFont(mFreeTypeFace, mRequestedPointSize, horizontalDpi, verticalDpi, mGlyphCacheManager.get()));
523   }
524   return mHarfBuzzProxyFont->GetHarfBuzzFont();
525 }
526
527 } // namespace Dali::TextAbstraction::Internal