Reduce the number of resize for emoji
[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   mPath(path),
76   mRequestedPointSize(requestedPointSize),
77   mFaceIndex(face),
78   mMetrics(metrics),
79   mCharacterSet(nullptr),
80   mFixedSizeIndex(0),
81   mFixedWidthPixels(0.f),
82   mFixedHeightPixels(0.f),
83   mVectorFontId(0u),
84   mFontId(0u),
85   mIsFixedSizeBitmap(false),
86   mHasColorTables(false)
87 {
88 }
89
90 FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
91                                      FT_Face            ftFace,
92                                      const FontPath&    path,
93                                      PointSize26Dot6    requestedPointSize,
94                                      FaceIndex          face,
95                                      const FontMetrics& metrics,
96                                      int                fixedSizeIndex,
97                                      float              fixedWidth,
98                                      float              fixedHeight,
99                                      bool               hasColorTables)
100 : mFreeTypeLibrary(freeTypeLibrary),
101   mFreeTypeFace(ftFace),
102   mGlyphCacheManager(new GlyphCacheManager(mFreeTypeFace, GetMaxNumberOfGlyphCache())),
103   mPath(path),
104   mRequestedPointSize(requestedPointSize),
105   mFaceIndex(face),
106   mMetrics(metrics),
107   mCharacterSet(nullptr),
108   mFixedSizeIndex(fixedSizeIndex),
109   mFixedWidthPixels(fixedWidth),
110   mFixedHeightPixels(fixedHeight),
111   mVectorFontId(0u),
112   mFontId(0u),
113   mIsFixedSizeBitmap(true),
114   mHasColorTables(hasColorTables)
115 {
116 }
117
118 // Move constructor. font client plugin container may call this.
119 // Note that we make nullptr of some reference sensitive values here.
120 FontFaceCacheItem::FontFaceCacheItem(FontFaceCacheItem&& rhs)
121 : mFreeTypeLibrary(rhs.mFreeTypeLibrary)
122 {
123   mFreeTypeFace       = rhs.mFreeTypeFace;
124   mGlyphCacheManager  = rhs.mGlyphCacheManager;
125   mPath               = std::move(rhs.mPath);
126   mRequestedPointSize = rhs.mRequestedPointSize;
127   mFaceIndex          = rhs.mFaceIndex;
128   mMetrics            = rhs.mMetrics;
129   mCharacterSet       = rhs.mCharacterSet;
130   mFixedSizeIndex     = rhs.mFixedSizeIndex;
131   mFixedWidthPixels   = rhs.mFixedWidthPixels;
132   mFixedHeightPixels  = rhs.mFixedWidthPixels;
133   mVectorFontId       = rhs.mVectorFontId;
134   mFontId             = rhs.mFontId;
135   mIsFixedSizeBitmap  = rhs.mIsFixedSizeBitmap;
136   mHasColorTables     = rhs.mHasColorTables;
137
138   rhs.mGlyphCacheManager = nullptr;
139   rhs.mFreeTypeFace      = nullptr;
140 }
141
142 FontFaceCacheItem::~FontFaceCacheItem()
143 {
144   // delete glyph cache manager before free face.
145   if(mGlyphCacheManager)
146   {
147     delete mGlyphCacheManager;
148   }
149
150   // Free face.
151   if(mFreeTypeFace)
152   {
153     FT_Done_Face(mFreeTypeFace);
154   }
155 }
156
157 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
158 {
159   metrics = mMetrics;
160
161   // Adjust the metrics if the fixed-size font should be down-scaled
162   if(mIsFixedSizeBitmap)
163   {
164     const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
165
166     if(desiredFixedSize > 0.f)
167     {
168       const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
169
170       metrics.ascender           = round(metrics.ascender * scaleFactor);
171       metrics.descender          = round(metrics.descender * scaleFactor);
172       metrics.height             = round(metrics.height * scaleFactor);
173       metrics.underlinePosition  = metrics.underlinePosition * scaleFactor;
174       metrics.underlineThickness = metrics.underlineThickness * scaleFactor;
175     }
176   }
177 }
178
179 bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVertical, bool horizontal) const
180 {
181   bool success(true);
182
183   GlyphCacheManager::GlyphCacheData glyphData;
184   FT_Error                          error;
185
186 #ifdef FREETYPE_BITMAP_SUPPORT
187   // Check to see if we should be loading a Fixed Size bitmap?
188   if(mIsFixedSizeBitmap)
189   {
190     FT_Select_Size(mFreeTypeFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
191     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, glyphData, error);
192
193     if(FT_Err_Ok == error)
194     {
195       glyphInfo.width    = mFixedWidthPixels;
196       glyphInfo.height   = mFixedHeightPixels;
197       glyphInfo.advance  = mFixedWidthPixels;
198       glyphInfo.xBearing = 0.0f;
199
200       const auto& metrics = glyphData.mGlyphMetrics;
201
202       if(horizontal)
203       {
204         glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
205       }
206       else
207       {
208         glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
209       }
210
211       // Adjust the metrics if the fixed-size font should be down-scaled
212       const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
213
214       if(desiredFixedSize > 0.f)
215       {
216         const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
217         glyphInfo.width         = round(glyphInfo.width * scaleFactor);
218         glyphInfo.height        = round(glyphInfo.height * scaleFactor);
219         glyphInfo.advance       = round(glyphInfo.advance * scaleFactor);
220         glyphInfo.xBearing      = round(glyphInfo.xBearing * scaleFactor);
221         glyphInfo.yBearing      = round(glyphInfo.yBearing * scaleFactor);
222
223         glyphInfo.scaleFactor = scaleFactor;
224
225         if(scaleFactor < MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE)
226         {
227           // Resize bitmap glyph and cache it due to the performance issue.
228           // If scaleFactor is too big, cached bitmap may hold too big memory.
229           // So, we only hold small enough case.
230
231           // TODO : If dpiVertical value changed, this resize feature will be break down.
232           // Otherwise, this glyph will be resized only one times.
233           mGlyphCacheManager->ResizeBitmapGlyph(glyphInfo.index, FT_LOAD_COLOR, glyphInfo.isBoldRequired, static_cast<uint32_t>(glyphInfo.width), static_cast<uint32_t>(glyphInfo.height));
234         }
235       }
236     }
237     else
238     {
239       DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
240       success = false;
241     }
242   }
243   else
244 #endif
245   {
246     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
247     // i.e. with the SNum-3R font.
248     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
249     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, glyphInfo.isBoldRequired, glyphData, error);
250
251     // Keep the width of the glyph before doing the software emboldening.
252     // It will be used to calculate a scale factor to be applied to the
253     // advance as Harfbuzz doesn't apply any SW emboldening to calculate
254     // the advance of the glyph.
255
256     if(FT_Err_Ok == error)
257     {
258       const auto& metrics = glyphData.mGlyphMetrics;
259
260       glyphInfo.width  = static_cast<float>(metrics.width) * FROM_266;
261       glyphInfo.height = static_cast<float>(metrics.height) * FROM_266;
262       if(horizontal)
263       {
264         glyphInfo.xBearing += static_cast<float>(metrics.horiBearingX) * FROM_266;
265         glyphInfo.yBearing += static_cast<float>(metrics.horiBearingY) * FROM_266;
266       }
267       else
268       {
269         glyphInfo.xBearing += static_cast<float>(metrics.vertBearingX) * FROM_266;
270         glyphInfo.yBearing += static_cast<float>(metrics.vertBearingY) * FROM_266;
271       }
272
273       const bool isEmboldeningRequired = glyphInfo.isBoldRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_BOLD);
274       if(isEmboldeningRequired)
275       {
276         // Get dummy glyph data without embolden.
277         GlyphCacheManager::GlyphCacheData dummyData;
278         if(mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphInfo.index, FT_LOAD_NO_AUTOHINT, false, dummyData, error))
279         {
280           // If the glyph is emboldened by software, the advance is multiplied by a
281           // scale factor to make it slightly bigger.
282           const float width = static_cast<float>(dummyData.mGlyphMetrics.width) * FROM_266;
283           if(!EqualsZero(width))
284           {
285             glyphInfo.advance *= (glyphInfo.width / width);
286           }
287         }
288       }
289
290       // Use the bounding box of the bitmap to correct the metrics.
291       // For some fonts i.e the SNum-3R the metrics need to be corrected,
292       // otherwise the glyphs 'dance' up and down depending on the
293       // font's point size.
294       FT_Glyph glyph = glyphData.mGlyph;
295
296       FT_BBox bbox;
297       FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
298
299       const float descender = glyphInfo.height - glyphInfo.yBearing;
300       glyphInfo.height      = (bbox.yMax - bbox.yMin) * FROM_266;
301       glyphInfo.yBearing    = glyphInfo.height - round(descender);
302     }
303     else
304     {
305       success = false;
306     }
307   }
308   return success;
309 }
310
311 /**
312  * @brief Create a bitmap representation of a glyph from a face font
313  *
314  * @param[in]  glyphIndex        The index of a glyph within the specified font.
315  * @param[in]  isItalicRequired  Whether the glyph requires italic style.
316  * @param[in]  isBoldRequired    Whether the glyph requires bold style.
317  * @param[out] data              The bitmap data.
318  * @param[in]  outlineWidth      The width of the glyph outline in pixels.
319  */
320 void FontFaceCacheItem::CreateBitmap(
321   GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
322 {
323   GlyphCacheManager::GlyphCacheData glyphData;
324   FT_Error                          error;
325   // For the software italics.
326   bool isShearRequired = false;
327
328 #ifdef FREETYPE_BITMAP_SUPPORT
329   // Check to see if this is fixed size bitmap
330   if(mIsFixedSizeBitmap)
331   {
332     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, isBoldRequired, glyphData, error);
333   }
334   else
335 #endif
336   {
337     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
338     // i.e. with the SNum-3R font.
339     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
340     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_NO_AUTOHINT, isBoldRequired, glyphData, error);
341   }
342   if(FT_Err_Ok == error)
343   {
344     if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
345     {
346       // Will do the software italic.
347       isShearRequired = true;
348     }
349
350     // Convert to bitmap if necessary
351     if(!glyphData.mIsBitmap)
352     {
353       FT_Glyph glyph = glyphData.mGlyph;
354
355       DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
356
357       int  offsetX = 0, offsetY = 0;
358       bool isOutlineGlyph       = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
359       bool isStrokeGlyphSuccess = false;
360
361       // Create a bitmap for the outline
362       if(isOutlineGlyph)
363       {
364         // Retrieve the horizontal and vertical distance from the current pen position to the
365         // left and top border of the glyph bitmap for a normal glyph before applying the outline.
366         if(FT_Err_Ok == error)
367         {
368           // Copy new glyph, and keep original cached glyph.
369           error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
370           if(FT_Err_Ok == error)
371           {
372             FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
373
374             offsetX = bitmapGlyph->left;
375             offsetY = bitmapGlyph->top;
376
377             // Copied FT_Glyph object must be released with FT_Done_Glyph
378             FT_Done_Glyph(glyph);
379           }
380
381           // Replace as original glyph
382           glyph = glyphData.mGlyph;
383         }
384
385         // Now apply the outline
386
387         // Set up a stroker
388         FT_Stroker stroker;
389         error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
390
391         if(FT_Err_Ok == error)
392         {
393           // Copy glyph pointer for release memory.
394           FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
395           error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 0);
396
397           if(FT_Err_Ok == error)
398           {
399             FT_Stroker_Done(stroker);
400             isStrokeGlyphSuccess = true;
401           }
402           else
403           {
404             DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
405           }
406         }
407         else
408         {
409           DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
410         }
411       }
412
413       // Copy new glyph, and keep original cached glyph.
414       // If we already copy new glyph by stroke, just re-use that.
415       error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
416       if(FT_Err_Ok == error)
417       {
418         FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
419
420         if(isOutlineGlyph)
421         {
422           // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
423           data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
424           data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
425         }
426
427         ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired);
428
429         // Copied FT_Glyph object must be released with FT_Done_Glyph
430         FT_Done_Glyph(glyph);
431       }
432       else
433       {
434         DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
435       }
436     }
437     else
438     {
439       ConvertBitmap(data, *glyphData.mBitmap, isShearRequired);
440     }
441
442     data.isColorEmoji = mIsFixedSizeBitmap;
443   }
444   else
445   {
446     DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
447   }
448 }
449
450 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
451 {
452   FT_Error error = -1;
453
454 #ifdef FREETYPE_BITMAP_SUPPORT
455   // Check to see if this is fixed size bitmap
456   if(mHasColorTables)
457   {
458     GlyphCacheManager::GlyphCacheData dummyData;
459     mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, false, dummyData, error);
460   }
461 #endif
462   return FT_Err_Ok == error;
463 }
464
465 /**
466  * Check if the character is supported by this font
467  * @param[in] character The character to test
468  */
469 bool FontFaceCacheItem::IsCharacterSupported(Character character)
470 {
471   if(nullptr == mCharacterSet)
472   {
473     // Create again the character set.
474     // It can be null if the ResetSystemDefaults() method has been called.
475
476     FontDescription description;
477     description.path   = mPath;
478     description.family = std::move(FontFamily(mFreeTypeFace->family_name));
479     description.weight = FontWeight::NONE;
480     description.width  = FontWidth::NONE;
481     description.slant  = FontSlant::NONE;
482
483     // Note FreeType doesn't give too much info to build a proper font style.
484     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
485     {
486       description.slant = FontSlant::ITALIC;
487     }
488     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
489     {
490       description.weight = FontWeight::BOLD;
491     }
492
493     mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
494   }
495
496   return FcCharSetHasChar(mCharacterSet, character);
497 }
498
499 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
500 {
501   return FT_Get_Char_Index(mFreeTypeFace, character);
502 }
503
504 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character, Character variantSelector) const
505 {
506   return FT_Face_GetCharVariantIndex(mFreeTypeFace, character, variantSelector);
507 }
508
509 } // namespace Dali::TextAbstraction::Internal