Merge branch 'devel/master' into tizen
[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           = round(metrics.ascender * scaleFactor);
94       metrics.descender          = round(metrics.descender * scaleFactor);
95       metrics.height             = round(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
121       if(horizontal)
122       {
123         glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingY) * FROM_266;
124       }
125       else
126       {
127         glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingY) * FROM_266;
128       }
129
130       // Adjust the metrics if the fixed-size font should be down-scaled
131       const float desiredFixedSize = static_cast<float>(mRequestedPointSize) * FROM_266 / POINTS_PER_INCH * dpiVertical;
132
133       if(desiredFixedSize > 0.f)
134       {
135         const float scaleFactor = desiredFixedSize / mFixedHeightPixels;
136         glyph.width             = round(glyph.width * scaleFactor);
137         glyph.height            = round(glyph.height * scaleFactor);
138         glyph.advance           = round(glyph.advance * scaleFactor);
139         glyph.xBearing          = round(glyph.xBearing * scaleFactor);
140         glyph.yBearing          = round(glyph.yBearing * scaleFactor);
141
142         glyph.scaleFactor = scaleFactor;
143       }
144     }
145     else
146     {
147       DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GetBitmapMetrics. FreeType Bitmap Load_Glyph error %d\n", error);
148       success = false;
149     }
150   }
151   else
152 #endif
153   {
154     // FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
155     // i.e. with the SNum-3R font.
156     // @todo: add an option to use the FT_LOAD_DEFAULT if required?
157     int error = FT_Load_Glyph(ftFace, glyph.index, FT_LOAD_NO_AUTOHINT);
158
159     // Keep the width of the glyph before doing the software emboldening.
160     // It will be used to calculate a scale factor to be applied to the
161     // advance as Harfbuzz doesn't apply any SW emboldening to calculate
162     // the advance of the glyph.
163     const float width = static_cast<float>(ftFace->glyph->metrics.width) * FROM_266;
164
165     if(FT_Err_Ok == error)
166     {
167       const bool isEmboldeningRequired = glyph.isBoldRequired && !(ftFace->style_flags & FT_STYLE_FLAG_BOLD);
168       if(isEmboldeningRequired)
169       {
170         // Does the software bold.
171         FT_GlyphSlot_Embolden(ftFace->glyph);
172       }
173
174       glyph.width  = static_cast<float>(ftFace->glyph->metrics.width) * FROM_266;
175       glyph.height = static_cast<float>(ftFace->glyph->metrics.height) * FROM_266;
176       if(horizontal)
177       {
178         glyph.xBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingX) * FROM_266;
179         glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.horiBearingY) * FROM_266;
180       }
181       else
182       {
183         glyph.xBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingX) * FROM_266;
184         glyph.yBearing += static_cast<float>(ftFace->glyph->metrics.vertBearingY) * FROM_266;
185       }
186
187       if(isEmboldeningRequired && !Dali::EqualsZero(width))
188       {
189         // If the glyph is emboldened by software, the advance is multiplied by a
190         // scale factor to make it slightly bigger.
191         glyph.advance *= (glyph.width / width);
192       }
193
194       // Use the bounding box of the bitmap to correct the metrics.
195       // For some fonts i.e the SNum-3R the metrics need to be corrected,
196       // otherwise the glyphs 'dance' up and down depending on the
197       // font's point size.
198
199       FT_Glyph ftGlyph;
200       error = FT_Get_Glyph(ftFace->glyph, &ftGlyph);
201
202       FT_BBox bbox;
203       FT_Glyph_Get_CBox(ftGlyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);
204
205       const float descender = glyph.height - glyph.yBearing;
206       glyph.height          = (bbox.yMax - bbox.yMin) * FROM_266;
207       glyph.yBearing        = glyph.height - round(descender);
208
209       // Created FT_Glyph object must be released with FT_Done_Glyph
210       FT_Done_Glyph(ftGlyph);
211     }
212     else
213     {
214       success = false;
215     }
216   }
217   return success;
218 }
219
220 /**
221  * @brief Create a bitmap representation of a glyph from a face font
222  *
223  * @param[in]  glyphIndex        The index of a glyph within the specified font.
224  * @param[in]  isItalicRequired  Whether the glyph requires italic style.
225  * @param[in]  isBoldRequired    Whether the glyph requires bold style.
226  * @param[out] data              The bitmap data.
227  * @param[in]  outlineWidth      The width of the glyph outline in pixels.
228  */
229 void FontFaceCacheItem::CreateBitmap(
230   GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
231 {
232   FT_Face  ftFace = mFreeTypeFace;
233   FT_Error error;
234   // For the software italics.
235   bool isShearRequired = false;
236
237 #ifdef FREETYPE_BITMAP_SUPPORT
238   // Check to see if this is fixed size bitmap
239   if(mIsFixedSizeBitmap)
240   {
241     error = FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_COLOR);
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     error = FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_NO_AUTOHINT);
250   }
251   if(FT_Err_Ok == error)
252   {
253     if(isBoldRequired && !(ftFace->style_flags & FT_STYLE_FLAG_BOLD))
254     {
255       // Does the software bold.
256       FT_GlyphSlot_Embolden(ftFace->glyph);
257     }
258
259     if(isItalicRequired && !(ftFace->style_flags & FT_STYLE_FLAG_ITALIC))
260     {
261       // Will do the software italic.
262       isShearRequired = true;
263     }
264
265     FT_Glyph glyph;
266     error = FT_Get_Glyph(ftFace->glyph, &glyph);
267
268     // Convert to bitmap if necessary
269     if(FT_Err_Ok == error)
270     {
271       if(glyph->format != FT_GLYPH_FORMAT_BITMAP)
272       {
273         int  offsetX = 0, offsetY = 0;
274         bool isOutlineGlyph = (glyph->format == FT_GLYPH_FORMAT_OUTLINE && outlineWidth > 0);
275
276         // Create a bitmap for the outline
277         if(isOutlineGlyph)
278         {
279           // Retrieve the horizontal and vertical distance from the current pen position to the
280           // left and top border of the glyph bitmap for a normal glyph before applying the outline.
281           if(FT_Err_Ok == error)
282           {
283             FT_Glyph normalGlyph;
284             error = FT_Get_Glyph(ftFace->glyph, &normalGlyph);
285
286             error = FT_Glyph_To_Bitmap(&normalGlyph, FT_RENDER_MODE_NORMAL, 0, 1);
287             if(FT_Err_Ok == error)
288             {
289               FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(normalGlyph);
290
291               offsetX = bitmapGlyph->left;
292               offsetY = bitmapGlyph->top;
293             }
294
295             // Created FT_Glyph object must be released with FT_Done_Glyph
296             FT_Done_Glyph(normalGlyph);
297           }
298
299           // Now apply the outline
300
301           // Set up a stroker
302           FT_Stroker stroker;
303           error = FT_Stroker_New(mFreeTypeLibrary, &stroker);
304
305           if(FT_Err_Ok == error)
306           {
307             FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
308             error = FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
309
310             if(FT_Err_Ok == error)
311             {
312               FT_Stroker_Done(stroker);
313             }
314             else
315             {
316               DALI_LOG_ERROR("FT_Glyph_StrokeBorder Failed with error: %d\n", error);
317             }
318           }
319           else
320           {
321             DALI_LOG_ERROR("FT_Stroker_New Failed with error: %d\n", error);
322           }
323         }
324
325         error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
326         if(FT_Err_Ok == error)
327         {
328           FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
329
330           if(isOutlineGlyph)
331           {
332             // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
333             data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
334             data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
335           }
336
337           ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired);
338         }
339         else
340         {
341           DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
342         }
343       }
344       else
345       {
346         ConvertBitmap(data, ftFace->glyph->bitmap, isShearRequired);
347       }
348
349       data.isColorEmoji = mIsFixedSizeBitmap;
350
351       // Created FT_Glyph object must be released with FT_Done_Glyph
352       FT_Done_Glyph(glyph);
353     }
354   }
355   else
356   {
357     DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Load_Glyph Failed with error: %d\n", error);
358   }
359 }
360
361 bool FontFaceCacheItem::IsColorGlyph(GlyphIndex glyphIndex) const
362 {
363   FT_Error error = -1;
364
365 #ifdef FREETYPE_BITMAP_SUPPORT
366   // Check to see if this is fixed size bitmap
367   if(mHasColorTables)
368   {
369     error = FT_Load_Glyph(mFreeTypeFace, glyphIndex, FT_LOAD_COLOR);
370   }
371 #endif
372   return FT_Err_Ok == error;
373 }
374
375 /**
376  * Check if the character is supported by this font
377  * @param[in] character The character to test
378  */
379 bool FontFaceCacheItem::IsCharacterSupported(Character character)
380 {
381   if(nullptr == mCharacterSet)
382   {
383     // Create again the character set.
384     // It can be null if the ResetSystemDefaults() method has been called.
385
386     FontDescription description;
387     description.path   = mPath;
388     description.family = std::move(FontFamily(mFreeTypeFace->family_name));
389     description.weight = FontWeight::NONE;
390     description.width  = FontWidth::NONE;
391     description.slant  = FontSlant::NONE;
392
393     // Note FreeType doesn't give too much info to build a proper font style.
394     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_ITALIC)
395     {
396       description.slant = FontSlant::ITALIC;
397     }
398     if(mFreeTypeFace->style_flags & FT_STYLE_FLAG_BOLD)
399     {
400       description.weight = FontWeight::BOLD;
401     }
402
403     mCharacterSet = FcCharSetCopy(CreateCharacterSetFromDescription(description));
404   }
405
406   return FcCharSetHasChar(mCharacterSet, character);
407 }
408
409 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character) const
410 {
411   return FT_Get_Char_Index(mFreeTypeFace, character);
412 }
413
414 GlyphIndex FontFaceCacheItem::GetGlyphIndex(Character character, Character variantSelector) const
415 {
416   return FT_Face_GetCharVariantIndex(mFreeTypeFace, character, variantSelector);
417 }
418
419 } // namespace Dali::TextAbstraction::Internal