Refactored font-client-plugin-impl
[platform/core/uifw/dali-adaptor.git] / dali / internal / text / text-abstraction / plugin / font-face-cache-item.cpp
1 /*
2  * Copyright (c) 2021 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 #include <dali/integration-api/debug.h>
18 #include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
19 #include <dali/internal/text/text-abstraction/plugin/font-face-cache-item.h>
20
21 #if defined(DEBUG_ENABLED)
22 extern Dali::Integration::Log::Filter* gFontClientLogFilter;
23 #endif
24
25 namespace Dali::TextAbstraction::Internal
26 {
27 const float FROM_266        = 1.0f / 64.0f;
28 const float POINTS_PER_INCH = 72.f;
29
30 FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
31                                      FT_Face            ftFace,
32                                      const FontPath&    path,
33                                      PointSize26Dot6    requestedPointSize,
34                                      FaceIndex          face,
35                                      const FontMetrics& metrics)
36 : mFreeTypeLibrary(freeTypeLibrary),
37   mFreeTypeFace(ftFace),
38   mPath(path),
39   mRequestedPointSize(requestedPointSize),
40   mFaceIndex(face),
41   mMetrics(metrics),
42   mCharacterSet(nullptr),
43   mFixedSizeIndex(0),
44   mFixedWidthPixels(0.f),
45   mFixedHeightPixels(0.f),
46   mVectorFontId(0u),
47   mFontId(0u),
48   mIsFixedSizeBitmap(false),
49   mHasColorTables(false)
50 {
51 }
52
53 FontFaceCacheItem::FontFaceCacheItem(FT_Library&        freeTypeLibrary,
54                                      FT_Face            ftFace,
55                                      const FontPath&    path,
56                                      PointSize26Dot6    requestedPointSize,
57                                      FaceIndex          face,
58                                      const FontMetrics& metrics,
59                                      int                fixedSizeIndex,
60                                      float              fixedWidth,
61                                      float              fixedHeight,
62                                      bool               hasColorTables)
63 : mFreeTypeLibrary(freeTypeLibrary),
64   mFreeTypeFace(ftFace),
65   mPath(path),
66   mRequestedPointSize(requestedPointSize),
67   mFaceIndex(face),
68   mMetrics(metrics),
69   mCharacterSet(nullptr),
70   mFixedSizeIndex(fixedSizeIndex),
71   mFixedWidthPixels(fixedWidth),
72   mFixedHeightPixels(fixedHeight),
73   mVectorFontId(0u),
74   mFontId(0u),
75   mIsFixedSizeBitmap(true),
76   mHasColorTables(hasColorTables)
77 {
78 }
79
80 void FontFaceCacheItem::GetFontMetrics(FontMetrics& metrics, unsigned int dpiVertical) const
81 {
82   metrics = mMetrics;
83
84   // Adjust the metrics if the fixed-size font should be down-scaled
85   if(mIsFixedSizeBitmap)
86   {
87     const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
88
89     if(desiredFixedSize > 0.f)
90     {
91       const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
92
93       metrics.ascender           = metrics.ascender * scaleFactor;
94       metrics.descender          = metrics.descender * scaleFactor;
95       metrics.height             = metrics.height * scaleFactor;
96       metrics.underlinePosition  = metrics.underlinePosition * scaleFactor;
97       metrics.underlineThickness = metrics.underlineThickness * scaleFactor;
98     }
99   }
100 }
101
102 bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyph, unsigned int dpiVertical, bool horizontal) const
103 {
104   bool success(true);
105
106   FT_Face ftFace = mFreeTypeFace;
107
108 #ifdef FREETYPE_BITMAP_SUPPORT
109   // Check to see if we should be loading a Fixed Size bitmap?
110   if(mIsFixedSizeBitmap)
111   {
112     FT_Select_Size(ftFace, mFixedSizeIndex); ///< @todo: needs to be investigated why it's needed to select the size again.
113     int error = FT_Load_Glyph(ftFace, glyph.index, FT_LOAD_COLOR);
114     if(FT_Err_Ok == error)
115     {
116       glyph.width    = mFixedWidthPixels;
117       glyph.height   = mFixedHeightPixels;
118       glyph.advance  = mFixedWidthPixels;
119       glyph.xBearing = 0.0f;
120       glyph.yBearing = mFixedHeightPixels;
121
122       // Adjust the metrics if the fixed-size font should be down-scaled
123       const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
124
125       if(desiredFixedSize > 0.f)
126       {
127         const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
128
129         glyph.width    = glyph.width * scaleFactor;
130         glyph.height   = glyph.height * scaleFactor;
131         glyph.advance  = glyph.advance * scaleFactor;
132         glyph.xBearing = glyph.xBearing * scaleFactor;
133         glyph.yBearing = glyph.yBearing * scaleFactor;
134
135         glyph.scaleFactor = scaleFactor;
136       }
137     }
138     else
139     {
140       DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
141       success = false;
142     }
143   }
144   else
145 #endif
146   {
147     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
148     // i.e. with the SNum-3R font.
149     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
150     int error = FT_Load_Glyph(ftFace, glyph.index, FT_LOAD_NO_AUTOHINT);
151
152     // Keep the width of the glyph before doing the software emboldening.
153     // It will be used to calculate a scale factor to be applied to the
154     // advance as Harfbuzz doesn't apply any SW emboldening to calculate
155     // the advance of the glyph.
156     const float width = static_cast<float>(ftFace->glyph->metrics.width) * FROM_266;
157
158     if(FT_Err_Ok == error)
159     {
160       const bool isEmboldeningRequired = glyph.isBoldRequired && !(ftFace->style_flags & FT_STYLE_FLAG_BOLD);
161       if(isEmboldeningRequired)
162       {
163         // Does the software bold.
164         FT_GlyphSlot_Embolden(ftFace->glyph);
165       }
166
167       glyph.width  = static_cast<float>(ftFace->glyph->metrics.width) * FROM_266;
168       glyph.height = static_cast<float>(ftFace->glyph->metrics.height) * FROM_266;
169       if(horizontal)
170       {
171         glyph.xBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingX) * FROM_266;
172         glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingY) * FROM_266;
173       }
174       else
175       {
176         glyph.xBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingX) * FROM_266;
177         glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingY) * FROM_266;
178       }
179
180       if(isEmboldeningRequired && !Dali::EqualsZero(width))
181       {
182         // If the glyph is emboldened by software, the advance is multiplied by a
183         // scale factor to make it slightly bigger.
184         glyph.advance *= (glyph.width / width);
185       }
186
187       // Use the bounding box of the bitmap to correct the metrics.
188       // For some fonts i.e the SNum-3R the metrics need to be corrected,
189       // otherwise the glyphs 'dance' up and down depending on the
190       // font's point size.
191
192       FT_Glyph ftGlyph;
193       error = FT_Get_Glyph(ftFace->glyph, &ftGlyph);
194
195       FT_BBox bbox;
196       FT_Glyph_Get_CBox(ftGlyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
197
198       const float descender = glyph.height - glyph.yBearing;
199       glyph.height          = (bbox.yMax - bbox.yMin) * FROM_266;
200       glyph.yBearing        = glyph.height - round(descender);
201
202       // Created FT_Glyph object must be released with FT_Done_Glyph
203       FT_Done_Glyph(ftGlyph);
204     }
205     else
206     {
207       success = false;
208     }
209   }
210   return success;
211 }
212
213 /**
214  * @brief Create a bitmap representation of a glyph from a face font
215  *
216  * @param[in]  glyphIndex        The index of a glyph within the specified font.
217  * @param[in]  isItalicRequired  Whether the glyph requires italic style.
218  * @param[in]  isBoldRequired    Whether the glyph requires bold style.
219  * @param[out] data              The bitmap data.
220  * @param[in]  outlineWidth      The width of the glyph outline in pixels.
221  */
222 void FontFaceCacheItem::CreateBitmap(
223   GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
224 {
225   FT_Face  ftFace = mFreeTypeFace;
226   FT_Error error;
227   // For the software italics.
228   bool isShearRequired = false;
229
230 #ifdef FREETYPE_BITMAP_SUPPORT
231   // Check to see if this is fixed size bitmap
232   if(mIsFixedSizeBitmap)
233   {
234     error = FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_COLOR);
235   }
236   else
237 #endif
238   {
239     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
240     // i.e. with the SNum-3R font.
241     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
242     error = FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_NO_AUTOHINT);
243   }
244   if(FT_Err_Ok == error)
245   {
246     if(isBoldRequired && !(ftFace->style_flags & FT_STYLE_FLAG_BOLD))
247     {
248       // Does the software bold.
249       FT_GlyphSlot_Embolden(ftFace->glyph);
250     }
251
252     if(isItalicRequired && !(ftFace->style_flags & FT_STYLE_FLAG_ITALIC))
253     {
254       // Will do the software italic.
255       isShearRequired = true;
256     }
257
258     FT_Glyph glyph;
259     error = FT_Get_Glyph(ftFace->glyph, &glyph);
260
261     // Convert to bitmap if necessary
262     if(FT_Err_Ok == error)
263     {
264       if(glyph->format != FT_GLYPH_FORMAT_BITMAP)
265       {
266         int  offsetX = 0, offsetY = 0;
267         bool isOutlineGlyph = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
268
269         // Create a bitmap for the outline
270         if(isOutlineGlyph)
271         {
272           // Retrieve the horizontal and vertical distance from the current pen position to the
273           // left and top border of the glyph bitmap for a normal glyph before applying the outline.
274           if(FT_Err_Ok == error)
275           {
276             FT_Glyph normalGlyph;
277             error = FT_Get_Glyph(ftFace->glyph, &normalGlyph);
278
279             error = FT_Glyph_To_Bitmap(&normalGlyph, FT_RENDER_MODE_NORMAL, 0, 1);
280             if(FT_Err_Ok == error)
281             {
282               FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(normalGlyph);
283
284               offsetX = bitmapGlyph->left;
285               offsetY = bitmapGlyph->top;
286             }
287
288             // Created FT_Glyph object must be released with FT_Done_Glyph
289             FT_Done_Glyph(normalGlyph);
290           }
291
292           // Now apply the outline
293
294           // Set up a stroker
295           FT_Stroker stroker;
296           error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
297
298           if(FT_Err_Ok == error)
299           {
300             FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
301             error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
302
303             if(FT_Err_Ok == error)
304             {
305               FT_Stroker_Done(stroker);
306             }
307             else
308             {
309               DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
310             }
311           }
312           else
313           {
314             DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
315           }
316         }
317
318         error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
319         if(FT_Err_Ok == error)
320         {
321           FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
322
323           if(isOutlineGlyph)
324           {
325             // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
326             data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
327             data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
328           }
329
330           ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired);
331         }
332         else
333         {
334           DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
335         }
336       }
337       else
338       {
339         ConvertBitmap(data, ftFace->glyph->bitmap, isShearRequired);
340       }
341
342       data.isColorEmoji = mIsFixedSizeBitmap;
343
344       // Created FT_Glyph object must be released with FT_Done_Glyph
345       FT_Done_Glyph(glyph);
346     }
347   }
348   else
349   {
350     DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
351   }
352 }
353
354 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
355 {
356   FT_Error error = -1;
357
358 #ifdef FREETYPE_BITMAP_SUPPORT
359   // Check to see if this is fixed size bitmap
360   if(mHasColorTables)
361   {
362     error = FT_Load_Glyph(mFreeTypeFace, glyphIndex, FT_LOAD_COLOR);
363   }
364 #endif
365   return FT_Err_Ok == error;
366 }
367
368 /**
369  * Check if the character is supported by this font
370  * @param[in] character The character to test
371  */
372 bool FontFaceCacheItem::IsCharacterSupported(Character character)
373 {
374   if(nullptr == mCharacterSet)
375   {
376     // Create again the character set.
377     // It can be null if the ResetSystemDefaults() method has been called.
378
379     FontDescription description;
380     description.path   = mPath;
381     description.family = std::move(FontFamily(mFreeTypeFace->family_name));
382     description.weight = FontWeight::NONE;
383     description.width  = FontWidth::NONE;
384     description.slant  = FontSlant::NONE;
385
386     // Note FreeType doesn't give too much info to build a proper font style.
387     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
388     {
389       description.slant = FontSlant::ITALIC;
390     }
391     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
392     {
393       description.weight = FontWeight::BOLD;
394     }
395
396     mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
397   }
398
399   return FcCharSetHasChar(mCharacterSet, character);
400 }
401
402 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
403 {
404   return FT_Get_Char_Index(mFreeTypeFace, character);
405 }
406
407 } // namespace Dali::TextAbstraction::Internal