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