xref: /aosp_15_r20/external/skia/gm/typeface.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2012 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "gm/gm.h"
9 #include "include/core/SkBlurTypes.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkFontStyle.h"
13 #include "include/core/SkFontTypes.h"
14 #include "include/core/SkMaskFilter.h"
15 #include "include/core/SkPaint.h"
16 #include "include/core/SkPoint.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkSize.h"
20 #include "include/core/SkString.h"
21 #include "include/core/SkTextBlob.h"
22 #include "include/core/SkTypeface.h"
23 #include "include/core/SkTypes.h"
24 #include "include/private/base/SkTemplates.h"
25 #include "src/core/SkFontPriv.h"
26 #include "tools/Resources.h"
27 #include "tools/fonts/FontToolUtils.h"
28 
29 #include <string.h>
30 #include <utility>
31 
32 using namespace skia_private;
33 
getGlyphPositions(const SkFont & font,const uint16_t glyphs[],int count,SkScalar x,SkScalar y,SkPoint pos[])34 static void getGlyphPositions(const SkFont& font, const uint16_t glyphs[],
35                              int count, SkScalar x, SkScalar y, SkPoint pos[]) {
36     AutoSTMalloc<128, SkScalar> widthStorage(count);
37     SkScalar* widths = widthStorage.get();
38     font.getWidths(glyphs, count, widths);
39 
40     for (int i = 0; i < count; ++i) {
41         pos[i].set(x, y);
42         x += widths[i];
43     }
44 }
45 
applyKerning(SkPoint pos[],const int32_t adjustments[],int count,const SkFont & font)46 static void applyKerning(SkPoint pos[], const int32_t adjustments[], int count,
47                          const SkFont& font) {
48     SkScalar scale = font.getSize() / font.getTypeface()->getUnitsPerEm();
49 
50     SkScalar globalAdj = 0;
51     for (int i = 0; i < count - 1; ++i) {
52         globalAdj += adjustments[i] * scale;
53         pos[i + 1].fX += globalAdj;
54     }
55 }
56 
drawKernText(SkCanvas * canvas,const void * text,size_t len,SkScalar x,SkScalar y,const SkFont & font,const SkPaint & paint)57 static void drawKernText(SkCanvas* canvas, const void* text, size_t len,
58                          SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) {
59     SkTypeface* face = font.getTypeface();
60     if (!face) {
61         canvas->drawSimpleText(text, len, SkTextEncoding::kUTF8, x, y, font, paint);
62         return;
63     }
64 
65     AutoSTMalloc<128, uint16_t> glyphStorage(len);
66     uint16_t* glyphs = glyphStorage.get();
67     int glyphCount = font.textToGlyphs(text, len, SkTextEncoding::kUTF8, glyphs, len);
68     if (glyphCount < 1) {
69         return;
70     }
71 
72     AutoSTMalloc<128, int32_t> adjustmentStorage(glyphCount - 1);
73     int32_t* adjustments = adjustmentStorage.get();
74     if (!face->getKerningPairAdjustments(glyphs, glyphCount, adjustments)) {
75         canvas->drawSimpleText(text, len, SkTextEncoding::kUTF8, x, y, font, paint);
76         return;
77     }
78 
79 
80     SkTextBlobBuilder builder;
81     auto rec = builder.allocRunPos(font, glyphCount);
82     memcpy(rec.glyphs, glyphs, glyphCount * sizeof(SkGlyphID));
83     getGlyphPositions(font, glyphs, glyphCount, x, y, rec.points());
84     applyKerning(rec.points(), adjustments, glyphCount, font);
85 
86     canvas->drawTextBlob(builder.make(), 0, 0, paint);
87 }
88 
89 static constexpr SkFontStyle gStyles[] = {
90     SkFontStyle::Normal(),
91     SkFontStyle::Bold(),
92     SkFontStyle::Italic(),
93     SkFontStyle::BoldItalic(),
94 };
95 
96 constexpr int gStylesCount = std::size(gStyles);
97 
98 // TODO(bungeman) delete this GM, as it is no longer useful.
99 class TypefaceStylesGM : public skiagm::GM {
100     sk_sp<SkTypeface> fFaces[gStylesCount];
101     bool fApplyKerning;
102 
103 public:
TypefaceStylesGM(bool applyKerning)104     TypefaceStylesGM(bool applyKerning) : fApplyKerning(applyKerning) {}
105 
106 protected:
onOnceBeforeDraw()107     void onOnceBeforeDraw() override {
108         for (int i = 0; i < gStylesCount; i++) {
109             fFaces[i] = ToolUtils::CreateTestTypeface(nullptr, gStyles[i]);
110         }
111     }
112 
getName() const113     SkString getName() const override {
114         SkString name("typefacestyles");
115         if (fApplyKerning) {
116             name.append("_kerning");
117         }
118         return name;
119     }
120 
getISize()121     SkISize getISize() override { return SkISize::Make(640, 480); }
122 
onDraw(SkCanvas * canvas)123     void onDraw(SkCanvas* canvas) override {
124         // Need to use a font to get dy below.
125         SkFont font = ToolUtils::DefaultFont();
126         font.setSize(30);
127 
128         const char* text = fApplyKerning ? "Type AWAY" : "Hamburgefons";
129         const size_t textLen = strlen(text);
130 
131         SkScalar x = SkIntToScalar(10);
132         SkScalar dy = font.getMetrics(nullptr);
133         SkASSERT(dy > 0);
134         SkScalar y = dy;
135 
136         if (fApplyKerning) {
137             font.setSubpixel(true);
138         } else {
139             font.setLinearMetrics(true);
140         }
141 
142         SkPaint paint;
143         for (int i = 0; i < gStylesCount; i++) {
144             SkASSERT(fFaces[i]);
145             font.setTypeface(fFaces[i]);
146             canvas->drawSimpleText(text, textLen, SkTextEncoding::kUTF8, x, y, font, paint);
147             if (fApplyKerning) {
148                 drawKernText(canvas, text, textLen, x + 240, y, font, paint);
149             }
150             y += dy;
151         }
152     }
153 
154 private:
155     using INHERITED = skiagm::GM;
156 };
157 
158 DEF_GM( return new TypefaceStylesGM(false); )
DEF_GM(return new TypefaceStylesGM (true);)159 DEF_GM( return new TypefaceStylesGM(true); )
160 
161 ////////////////////////////////////////////////////////////////////////////////
162 
163 static void draw_typeface_rendering_gm(SkCanvas* canvas, sk_sp<SkTypeface> face, SkGlyphID glyph) {
164     struct AliasType {
165         SkFont::Edging edging;
166         bool inLayer;
167     } constexpr aliasTypes[] {
168 #ifndef SK_BUILD_FOR_IOS
169         // This gm crashes on iOS when drawing an embedded bitmap when requesting aliased rendering.
170         // The crash looks like
171         //   libTrueTypeScaler.dylib`<redacted> + 80
172         //   stop reason = EXC_BAD_ACCESS (code=EXC_ARM_DA_ALIGN, address=...)
173         //   ->  0x330b19d0 <+80>: strd   r2, r3, [r5, #36]
174         //       0x330b19d4 <+84>: movs   r3, #0x0
175         //       0x330b19d6 <+86>: add    r2, sp, #0x28
176         //       0x330b19d8 <+88>: ldr    r0, [r4, #0x4]
177         // Disable testing embedded bitmaps on iOS for now.
178         // See https://bug.skia.org/5530 .
179         { SkFont::Edging::kAlias            , false },
180 #endif
181         { SkFont::Edging::kAntiAlias        , false },
182         { SkFont::Edging::kSubpixelAntiAlias, false },
183         { SkFont::Edging::kAntiAlias        , true  },
184         { SkFont::Edging::kSubpixelAntiAlias, true  },
185     };
186 
187     // The hintgasp.ttf is designed for the following sizes to be different.
188     // GASP_DOGRAY                                      0x0002   0<=ppem<=10
189     // GASP_SYMMETRIC_SMOOTHING                         0x0008   0<=ppem<=10
190     // GASP_GRIDFIT                                     0x0001  11<=ppem<=12
191     // GASP_SYMMETRIC_GRIDFIT                           0x0004  11<=ppem<=12
192     // GASP_DOGRAY|GASP_GRIDFIT                         0x0003  13<=ppem<=14
193     // GASP_SYMMETRIC_SMOOTHING|GASP_SYMMETRIC_GRIDFIT  0x000C  13<=ppem<=14
194     // (neither)                                        0x0000  15<=ppem
195     // Odd sizes have embedded bitmaps.
196     constexpr SkScalar textSizes[] = { 9, 10, 11, 12, 13, 14, 15, 16 };
197 
198     constexpr SkFontHinting hintingTypes[] = {
199         SkFontHinting::kNone,
200         SkFontHinting::kSlight,
201         SkFontHinting::kNormal,
202         SkFontHinting::kFull
203     };
204 
205     struct SubpixelType {
206         bool requested;
207         SkVector offset;
208     } constexpr subpixelTypes[] = {
209         { false, { 0.00, 0.00 } },
210         { true , { 0.00, 0.00 } },
211         { true , { 0.25, 0.00 } },
212         { true , { 0.25, 0.25 } },
213     };
214 
215     constexpr bool rotateABitTypes[] = { false, true };
216 
217     SkScalar y = 0;  // The baseline of the previous output
218     {
219         SkPaint paint;
220 
221         SkFont font(face);
222         font.setEmbeddedBitmaps(true);
223 
224         SkScalar x = 0;
225         SkScalar xMax = x;
226         SkScalar xBase = 0;
227         for (const SubpixelType subpixel : subpixelTypes) {
228             y = 0;
229             font.setSubpixel(subpixel.requested);
230 
231             for (const AliasType& alias : aliasTypes) {
232                 font.setEdging(alias.edging);
233                 SkAutoCanvasRestore acr1(canvas, false);
234                 if (alias.inLayer) {
235                     canvas->saveLayer(nullptr, &paint);
236                 }
237 
238                 for (const SkScalar& textSize : textSizes) {
239                     x = xBase + 5;
240                     font.setSize(textSize);
241 
242                     SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
243                     y += dy;
244                     for (const SkFontHinting& hinting : hintingTypes) {
245                         font.setHinting(hinting);
246 
247                         for (const bool& rotateABit : rotateABitTypes) {
248                             SkAutoCanvasRestore acr2(canvas, true);
249                             if (rotateABit) {
250                                 canvas->rotate(2, x + subpixel.offset.x(),
251                                                   y + subpixel.offset.y());
252                             }
253                             canvas->drawSimpleText(&glyph, sizeof(glyph), SkTextEncoding::kGlyphID,
254                                                    x + subpixel.offset.x(),
255                                                    y + subpixel.offset.y(), font, paint);
256 
257                             SkScalar dx = SkScalarCeilToScalar(font.measureText(
258                                     &glyph, sizeof(glyph), SkTextEncoding::kGlyphID)) + 5;
259                             x += dx;
260                             xMax = std::max(x, xMax);
261                         }
262                     }
263                 }
264                 y += 10;
265             }
266             xBase = xMax;
267         }
268     }
269 
270     constexpr struct StyleTests {
271         SkPaint::Style style;
272         SkScalar strokeWidth;
273     } styleTypes[] = {
274         { SkPaint::kFill_Style, 0.0f},
275         { SkPaint::kStroke_Style, 0.0f},
276         { SkPaint::kStroke_Style, 0.5f},
277         { SkPaint::kStrokeAndFill_Style, 1.0f},
278     };
279 
280     constexpr bool fakeBoldTypes[] = { false, true };
281 
282     {
283         SkPaint paint;
284 
285         SkFont font(face, 16);
286 
287         SkScalar x = 0;
288         for (const bool& fakeBold : fakeBoldTypes) {
289             SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
290             y += dy;
291             x = 5;
292 
293             font.setEmbolden(fakeBold);
294             for (const AliasType& alias : aliasTypes) {
295                 font.setEdging(alias.edging);
296                 SkAutoCanvasRestore acr(canvas, false);
297                 if (alias.inLayer) {
298                     canvas->saveLayer(nullptr, &paint);
299                 }
300                 for (const StyleTests& style : styleTypes) {
301                     paint.setStyle(style.style);
302                     paint.setStrokeWidth(style.strokeWidth);
303                     canvas->drawSimpleText(&glyph, sizeof(glyph), SkTextEncoding::kGlyphID,
304                                            x, y, font, paint);
305 
306                     SkScalar dx = SkScalarCeilToScalar(font.measureText(
307                             &glyph, sizeof(glyph), SkTextEncoding::kGlyphID)) + 5;
308                     x += dx;
309                 }
310             }
311             y += 10;
312         }
313     }
314 
315     constexpr struct MaskTests {
316         SkBlurStyle style;
317         SkScalar sigma;
318     } maskTypes[] = {
319         { SkBlurStyle::kNormal_SkBlurStyle, 0.0f},
320         { SkBlurStyle::kSolid_SkBlurStyle, 0.0f},
321         { SkBlurStyle::kOuter_SkBlurStyle, 0.0f},
322         { SkBlurStyle::kInner_SkBlurStyle, 0.0f},
323 
324         { SkBlurStyle::kNormal_SkBlurStyle, 0.5f},
325         { SkBlurStyle::kSolid_SkBlurStyle, 0.5f},
326         { SkBlurStyle::kOuter_SkBlurStyle, 0.5f},
327         { SkBlurStyle::kInner_SkBlurStyle, 0.5f},
328 
329         { SkBlurStyle::kNormal_SkBlurStyle, 2.0f},
330         { SkBlurStyle::kSolid_SkBlurStyle, 2.0f},
331         { SkBlurStyle::kOuter_SkBlurStyle, 2.0f},
332         { SkBlurStyle::kInner_SkBlurStyle, 2.0f},
333     };
334 
335     {
336         SkPaint paint;
337 
338         SkFont font(face, 16);
339 
340         SkScalar x = 0;
341         {
342             for (const AliasType& alias : aliasTypes) {
343                 SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
344                 y += dy;
345                 x = 5;
346 
347                 font.setEdging(alias.edging);
348                 SkAutoCanvasRestore acr(canvas, false);
349                 if (alias.inLayer) {
350                     canvas->saveLayer(nullptr, &paint);
351                 }
352                 for (const MaskTests& mask : maskTypes) {
353                     paint.setMaskFilter(SkMaskFilter::MakeBlur(mask.style, mask.sigma));
354                     canvas->drawSimpleText(&glyph, sizeof(glyph), SkTextEncoding::kGlyphID,
355                                            x, y, font, paint);
356 
357                     SkScalar dx = SkScalarCeilToScalar(font.measureText(
358                             &glyph, sizeof(glyph), SkTextEncoding::kGlyphID)) + 5;
359                     x += dx;
360                 }
361                 paint.setMaskFilter(nullptr);
362             }
363             y += 10;
364         }
365     }
366 }
367 
368 DEF_SIMPLE_GM_CAN_FAIL(typefacerendering, canvas, errMsg, 640, 840) {
369     sk_sp<SkTypeface> face = ToolUtils::CreateTypefaceFromResource("fonts/hintgasp.ttf");
370     if (!face) {
371         return skiagm::DrawResult::kSkip;
372     }
373     draw_typeface_rendering_gm(canvas, face, face->unicharToGlyph('A'));
374 
375     // Should draw nothing and not do anything undefined.
376     draw_typeface_rendering_gm(canvas, face, 0xFFFF);
377     return skiagm::DrawResult::kOk;
378 }
379 
380 // Type1 fonts don't currently work in Skia on Windows.
381 #ifndef SK_BUILD_FOR_WIN
382 
383 DEF_SIMPLE_GM_CAN_FAIL(typefacerendering_pfa, canvas, errMsg, 640, 840) {
384     sk_sp<SkTypeface> face = ToolUtils::CreateTypefaceFromResource("fonts/Roboto2-Regular.pfa");
385     if (!face) {
386        return skiagm::DrawResult::kSkip;
387     }
388     draw_typeface_rendering_gm(canvas, face, face->unicharToGlyph('O'));
389     return skiagm::DrawResult::kOk;
390 }
391 
392 DEF_SIMPLE_GM_CAN_FAIL(typefacerendering_pfb, canvas, errMsg, 640, 840) {
393     sk_sp<SkTypeface> face = ToolUtils::CreateTypefaceFromResource("fonts/Roboto2-Regular.pfb");
394     if (!face) {
395         return skiagm::DrawResult::kSkip;
396     }
397     draw_typeface_rendering_gm(canvas, face, face->unicharToGlyph('O'));
398     return skiagm::DrawResult::kOk;
399 }
400 
401 #endif
402 
403 ////////////////////////////////////////////////////////////////////////////////
404 
405 // Exercise different paint styles and embolden, and compare with strokeandfill patheffect
406 DEF_SIMPLE_GM(typeface_styling, canvas, 710, 360) {
407     sk_sp<SkTypeface> face = ToolUtils::CreateTypefaceFromResource("fonts/Roboto-Regular.ttf");
408     if (!face) {
409         face = ToolUtils::DefaultPortableTypeface();
410     }
411     SkFont font(face, 100);
412     font.setEdging(SkFont::Edging::kAntiAlias);
413 
414     uint16_t glyphs[1] = { font.unicharToGlyph('A') };
415     SkPoint pos[1] = { {0, 0} };
416 
__anon838529030102(SkPaint::Style style, float width) 417     auto draw = [&](SkPaint::Style style, float width) {
418         // Draws 3 rows:
419         //  1. normal
420         //  2. emboldened
421         //  3. normal(white) on top of emboldened (to show the delta)
422 
423         SkPaint paint;
424         paint.setStyle(style);
425         paint.setStrokeWidth(width);
426 
427         font.setEmbolden(true);
428         canvas->drawGlyphs(1, glyphs, pos, {20, 120*2}, font, paint);
429         canvas->drawGlyphs(1, glyphs, pos, {20, 120*3}, font, paint);
430 
431         font.setEmbolden(false);
432         canvas->drawGlyphs(1, glyphs, pos, {20, 120*1}, font, paint);
433         paint.setColor(SK_ColorYELLOW);
434         canvas->drawGlyphs(1, glyphs, pos, {20, 120*3}, font, paint);
435     };
436 
437     const struct {
438         SkPaint::Style  style;
439         float           width;
440     } recs[] = {
441         { SkPaint::kFill_Style,             0 },
442         { SkPaint::kStroke_Style,           0 },
443         { SkPaint::kStroke_Style,           3 },
444         { SkPaint::kStrokeAndFill_Style,    0 },
445         { SkPaint::kStrokeAndFill_Style,    3 },
446     };
447 
448     canvas->translate(0, -20);
449     for (auto r : recs) {
450         draw(r.style, r.width);
451         canvas->translate(100, 0);
452     }
453 }
454