1 /*
2 * Copyright 2021 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/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkFontMetrics.h"
13 #include "include/core/SkGraphics.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkRefCnt.h"
16 #include "include/core/SkScalar.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkString.h"
19 #include "include/core/SkTypeface.h"
20 #include "tools/Resources.h"
21 #include "tools/ToolUtils.h"
22
23 #include <string.h>
24 #include <initializer_list>
25
26 namespace skiagm {
27
28 namespace {
29 const SkScalar kTextSizes[] = {12, 18, 30, 120};
30 const char kTestFontName[] = "fonts/test_glyphs-glyf_colr_1.ttf";
31 const char kTestFontNameVariable[] = "fonts/test_glyphs-glyf_colr_1_variable.ttf";
32 const SkScalar xWidth = 1200;
33 const SkScalar xTranslate = 200;
34 }
35
36 class ColrV1GM : public GM {
37 public:
ColrV1GM(const char * testName,SkSpan<const uint32_t> codepoints,SkScalar skewX,SkScalar rotateDeg,std::initializer_list<SkFontArguments::VariationPosition::Coordinate> specifiedVariations)38 ColrV1GM(const char* testName,
39 SkSpan<const uint32_t> codepoints,
40 SkScalar skewX,
41 SkScalar rotateDeg,
42 std::initializer_list<SkFontArguments::VariationPosition::Coordinate>
43 specifiedVariations)
44 : fTestName(testName)
45 , fCodepoints(codepoints)
46 , fSkewX(skewX)
47 , fRotateDeg(rotateDeg) {
48 fVariationPosition.coordinateCount = specifiedVariations.size();
49 fCoordinates = std::make_unique<SkFontArguments::VariationPosition::Coordinate[]>(
50 specifiedVariations.size());
51 for (size_t i = 0; i < specifiedVariations.size(); ++i) {
52 fCoordinates[i] = std::data(specifiedVariations)[i];
53 }
54
55 fVariationPosition.coordinates = fCoordinates.get();
56 }
57
58 protected:
onOnceBeforeDraw()59 void onOnceBeforeDraw() override {
60 if (fVariationPosition.coordinateCount) {
61 fTypeface = MakeResourceAsTypeface(kTestFontNameVariable);
62 } else {
63 fTypeface = MakeResourceAsTypeface(kTestFontName);
64 }
65 fVariationSliders = ToolUtils::VariationSliders(fTypeface.get(), fVariationPosition);
66 }
67
onShortName()68 SkString onShortName() override {
69 SkASSERT(!fTestName.isEmpty());
70 SkString gm_name = SkStringPrintf("colrv1_%s", fTestName.c_str());
71
72 if (fSkewX) {
73 gm_name.append(SkStringPrintf("_skew_%.2f", fSkewX));
74 }
75
76 if (fRotateDeg) {
77 gm_name.append(SkStringPrintf("_rotate_%.2f", fRotateDeg));
78 }
79
80 for (int i = 0; i < fVariationPosition.coordinateCount; ++i) {
81 SkString tagName = ToolUtils::VariationSliders::tagToString(
82 fVariationPosition.coordinates[i].axis);
83 gm_name.append(SkStringPrintf(
84 "_%s_%.2f", tagName.c_str(), fVariationPosition.coordinates[i].value));
85 }
86
87 return gm_name;
88 }
89
onGetControls(SkMetaData * controls)90 bool onGetControls(SkMetaData* controls) override {
91 return fVariationSliders.writeControls(controls);
92 }
93
onSetControls(const SkMetaData & controls)94 void onSetControls(const SkMetaData& controls) override {
95 return fVariationSliders.readControls(controls);
96 }
97
onISize()98 SkISize onISize() override {
99 // Sweep tests get a slightly wider canvas so that glyphs from one group fit in one row.
100 if (fTestName.equals("sweep_varsweep")) {
101 return SkISize::Make(xWidth + 500, xWidth);
102 }
103 return SkISize::Make(xWidth, xWidth);
104 }
105
makeVariedTypeface()106 sk_sp<SkTypeface> makeVariedTypeface() {
107 if (!fTypeface) {
108 return nullptr;
109 }
110 SkSpan<const SkFontArguments::VariationPosition::Coordinate> coords =
111 fVariationSliders.getCoordinates();
112 SkFontArguments::VariationPosition varPos = {coords.data(),
113 static_cast<int>(coords.size())};
114 SkFontArguments args;
115 args.setVariationDesignPosition(varPos);
116 return fTypeface->makeClone(args);
117 }
118
onDraw(SkCanvas * canvas,SkString * errorMsg)119 DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
120 canvas->drawColor(SK_ColorWHITE);
121 SkPaint paint;
122
123 canvas->translate(xTranslate, 20);
124
125 if (!fTypeface) {
126 *errorMsg = "Did not recognize COLR v1 font format.";
127 return DrawResult::kSkip;
128 }
129
130 canvas->rotate(fRotateDeg);
131 canvas->skew(fSkewX, 0);
132
133 SkFont font(makeVariedTypeface());
134
135 SkFontMetrics metrics;
136 SkScalar y = 0;
137 std::vector<SkColor> paint_colors = {
138 SK_ColorBLACK, SK_ColorGREEN, SK_ColorRED, SK_ColorBLUE};
139 auto paint_color_iterator = paint_colors.begin();
140 for (SkScalar textSize : kTextSizes) {
141 font.setSize(textSize);
142 font.getMetrics(&metrics);
143 SkScalar y_shift = -(metrics.fAscent + metrics.fDescent + metrics.fLeading) * 1.2;
144 y += y_shift;
145 paint.setColor(*paint_color_iterator);
146 int x = 0;
147 // Perform simple line breaking to fit more glyphs into the GM canvas.
148 for (size_t i = 0; i < fCodepoints.size(); ++i) {
149 canvas->drawSimpleText(&fCodepoints[i],
150 sizeof(uint32_t),
151 SkTextEncoding::kUTF32,
152 x,
153 y,
154 font,
155 paint);
156 SkScalar glyphAdvance = font.measureText(
157 &fCodepoints[i], sizeof(uint32_t), SkTextEncoding::kUTF32, nullptr);
158 if (x + glyphAdvance < onISize().width() - xTranslate) {
159 x += glyphAdvance + glyphAdvance * 0.05f;
160 } else {
161 y += y_shift;
162 x = 0;
163 }
164 }
165 paint_color_iterator++;
166 }
167 return DrawResult::kOk;
168 }
169
170 private:
171 using INHERITED = GM;
172
173 SkString fTestName;
174 sk_sp<SkTypeface> fTypeface;
175 SkSpan<const uint32_t> fCodepoints;
176 SkScalar fSkewX;
177 SkScalar fRotateDeg;
178 std::unique_ptr<SkFontArguments::VariationPosition::Coordinate[]> fCoordinates;
179 SkFontArguments::VariationPosition fVariationPosition;
180 ToolUtils::VariationSliders fVariationSliders;
181 };
182
183 // Generated using test glyphs generator script from https://github.com/googlefonts/color-fonts:
184 // $ python3 config/test_glyphs-glyf_colr_1.py -vvv --generate-descriptions fonts/
185 // Regenerate descriptions and paste the generated arrays here when updating the test font.
186 namespace ColrV1TestDefinitions {
187 const uint32_t gradient_stops_repeat[] = {0xf0100, 0xf0101, 0xf0102, 0xf0103};
188 const uint32_t sweep_varsweep[] = {
189 0xf0200, 0xf0201, 0xf0202, 0xf0203, 0xf0204, 0xf0205, 0xf0206, 0xf0207, 0xf0208,
190 0xf0209, 0xf020a, 0xf020b, 0xf020c, 0xf020d, 0xf020e, 0xf020f, 0xf0210, 0xf0211,
191 0xf0212, 0xf0213, 0xf0214, 0xf0215, 0xf0216, 0xf0217, 0xf0218, 0xf0219, 0xf021a,
192 0xf021b, 0xf021c, 0xf021d, 0xf021e, 0xf021f, 0xf0220, 0xf0221, 0xf0222, 0xf0223,
193 0xf0224, 0xf0225, 0xf0226, 0xf0227, 0xf0228, 0xf0229, 0xf022a, 0xf022b, 0xf022c,
194 0xf022d, 0xf022e, 0xf022f, 0xf0230, 0xf0231, 0xf0232, 0xf0233, 0xf0234, 0xf0235,
195 0xf0236, 0xf0237, 0xf0238, 0xf0239, 0xf023a, 0xf023b, 0xf023c, 0xf023d, 0xf023e,
196 0xf023f, 0xf0240, 0xf0241, 0xf0242, 0xf0243, 0xf0244, 0xf0245, 0xf0246, 0xf0247};
197 const uint32_t paint_scale[] = {0xf0300, 0xf0301, 0xf0302, 0xf0303, 0xf0304, 0xf0305};
198 const uint32_t extend_mode[] = {
199 0xf0500, 0xf0501, 0xf0502, 0xf0503, 0xf0504, 0xf0505, 0xf0506, 0xf0507, 0xf0508};
200 const uint32_t paint_rotate[] = {0xf0600, 0xf0601, 0xf0602, 0xf0603};
201 const uint32_t paint_skew[] = {0xf0700, 0xf0701, 0xf0702, 0xf0703, 0xf0704, 0xf0705};
202 const uint32_t paint_transform[] = {0xf0800, 0xf0801, 0xf0802, 0xf0803};
203 const uint32_t paint_translate[] = {0xf0900, 0xf0901, 0xf0902, 0xf0903, 0xf0904, 0xf0905, 0xf0906};
204 const uint32_t composite_mode[] = {0xf0a00, 0xf0a01, 0xf0a02, 0xf0a03, 0xf0a04, 0xf0a05, 0xf0a06,
205 0xf0a07, 0xf0a08, 0xf0a09, 0xf0a0a, 0xf0a0b, 0xf0a0c, 0xf0a0d,
206 0xf0a0e, 0xf0a0f, 0xf0a10, 0xf0a11, 0xf0a12, 0xf0a13, 0xf0a14,
207 0xf0a15, 0xf0a16, 0xf0a17, 0xf0a18, 0xf0a19, 0xf0a1a, 0xf0a1b};
208 const uint32_t foreground_color[] = {
209 0xf0b00, 0xf0b01, 0xf0b02, 0xf0b03, 0xf0b04, 0xf0b05, 0xf0b06, 0xf0b07};
210 const uint32_t clipbox[] = {0xf0c00, 0xf0c01, 0xf0c02, 0xf0c03, 0xf0c04};
211 const uint32_t gradient_p2_skewed[] = {0xf0d00};
212 const uint32_t variable_alpha[] = {0xf1000};
213 }; // namespace ColrV1TestDefinitions
214
215 namespace {
F(const char * name,SkSpan<const uint32_t> codepoints,SkScalar skewX,SkScalar rotateDeg,std::initializer_list<SkFontArguments::VariationPosition::Coordinate> variations)216 std::unique_ptr<ColrV1GM> F(
217 const char* name,
218 SkSpan<const uint32_t> codepoints,
219 SkScalar skewX,
220 SkScalar rotateDeg,
221 std::initializer_list<SkFontArguments::VariationPosition::Coordinate> variations)
222 {
223 return std::make_unique<ColrV1GM>(name, codepoints, skewX, rotateDeg, variations);
224 }
225
operator ""_t(const char * tagName,size_t size)226 SkFourByteTag constexpr operator "" _t(const char* tagName, size_t size) {
227 SkASSERT(size == 4);
228 return SkSetFourByteTag(tagName[0], tagName[1], tagName[2], tagName[3]);
229 }
230 }
231 #define C(TEST_CATEGORY) #TEST_CATEGORY, ColrV1TestDefinitions::TEST_CATEGORY
232 DEF_GM(return F(C(clipbox), 0.0f, 0.0f, {}))
233 DEF_GM(return F(C(clipbox), 0.0f, 0.0f, {{"CLIO"_t, 200.f}}))
234 DEF_GM(return F(C(composite_mode), 0.0f, 0.0f, {}))
235 DEF_GM(return F(C(composite_mode), -0.5f, 0.0f, {}))
236 DEF_GM(return F(C(composite_mode), -0.5f, 20.0f, {}))
237 DEF_GM(return F(C(composite_mode), 0.0f, 20.0f, {}))
238 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {}))
239 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL1"_t, -0.25f}, {"COL3"_t, 0.25f}}))
240 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL1"_t, 0.5f}, {"COL3"_t, -0.5f}}))
241 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL3"_t, 0.5f}}))
242 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL3"_t, 1.f}}))
243 // Radial gradient tests where radii become negative
244 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL1"_t, -1.5f}}))
245 // Both radii negative and equal, nothing should render.
246 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"GRR0"_t, -200.f}, {"GRR1"_t, -300.f}}))
247 // Small cones opening to the right.
248 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"GRX0"_t, -1000.f}, {"GRX1"_t, -1000.f}, {"GRR0"_t, -1000.f}, {"GRR1"_t, -900.f}}))
249 // Small cones opening to the left.
250 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"GRX0"_t, 1000.f}, {"GRX1"_t, -1000.f}, {"GRR0"_t, -1000.f}, {"GRR1"_t, 200.f}}))
251 // Pad cone should appear green.
252 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"GRR0"_t, -50.f}, {"COL3"_t, -2.f}, {"COL2"_t, -2.f}, {"COL1"_t, -0.9f}}))
253 // Pad cone should appear red.
254 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"GRR0"_t, -50.f}, {"COL3"_t, -2.f}, {"COL2"_t, -2.f}, {"COL1"_t, -1.1f}}))
255 // Hard boundary for pad mode, should appear on the right inside the glyph for linear and radial.
256 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL3"_t, 1.f}, {"COL2"_t, 1.5f}, {"COL1"_t, 2.f}}))
257 // Extend mode with rotation or skew below.
258 DEF_GM(return F(C(extend_mode), -0.5f, 0.0f, {}))
259 DEF_GM(return F(C(extend_mode), -0.5f, 20.0f, {}))
260 DEF_GM(return F(C(extend_mode), 0.0f, 20.0f, {}))
261 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"COL2"_t, -0.3f}}))
262 DEF_GM(return F(C(extend_mode), 0.0f, 0.0f, {{"GRR0"_t, 430.f}, {"GRR1"_t, 40.f}}))
263 DEF_GM(return F(C(foreground_color), 0.0f, 0.0f, {}))
264 DEF_GM(return F(C(gradient_p2_skewed), 0.0f, 0.0f, {}))
265 DEF_GM(return F(C(gradient_stops_repeat), 0.0f, 0.0f, {}))
266 DEF_GM(return F(C(gradient_stops_repeat), -0.5f, 0.0f, {}))
267 DEF_GM(return F(C(gradient_stops_repeat), -0.5f, 20.0f, {}))
268 DEF_GM(return F(C(gradient_stops_repeat), 0.0f, 20.0f, {}))
269 DEF_GM(return F(C(paint_rotate), 0.0f, 0.0f, {}))
270 DEF_GM(return F(C(paint_rotate), 0.0f, 0.0f, {{"ROTA"_t, 40.f}}))
271 DEF_GM(return F(C(paint_rotate), 0.0f, 0.0f, {{"ROTX"_t, -250.f}, {"ROTY"_t, -250.f}}))
272 DEF_GM(return F(C(paint_scale), 0.0f, 0.0f, {}))
273 DEF_GM(return F(C(paint_scale), 0.0f, 0.0f, {{"SCOX"_t, 200.f}, {"SCOY"_t, 200.f}}))
274 DEF_GM(return F(C(paint_scale), 0.0f, 0.0f, {{"SCSX"_t, 0.25f}, {"SCOY"_t, 0.25f}}))
275 DEF_GM(return F(C(paint_scale), 0.0f, 0.0f, {{"SCSX"_t, -1.f}, {"SCOY"_t, -1.f}}))
276 DEF_GM(return F(C(paint_scale), 0.0f, 0.0f, {}))
277 DEF_GM(return F(C(paint_scale), 0.0f, 0.0f, {}))
278 DEF_GM(return F(C(paint_skew), 0.0f, 0.0f, {}))
279 DEF_GM(return F(C(paint_skew), 0.0f, 0.0f, {{"SKXA"_t, 20.f}}))
280 DEF_GM(return F(C(paint_skew), 0.0f, 0.0f, {{"SKYA"_t, 20.f}}))
281 DEF_GM(return F(C(paint_skew), 0.0f, 0.0f, {{"SKCX"_t, 200.f},{"SKCY"_t, 200.f}}))
282 DEF_GM(return F(C(paint_transform), 0.0f, 0.0f, {}))
283 DEF_GM(return F(C(paint_translate), 0.0f, 0.0f, {}))
284 DEF_GM(return F(C(paint_translate), 0.0f, 0.0f, {{"TLDX"_t, 100.f}, {"TLDY"_t, 100.f}}))
285 DEF_GM(return F(C(sweep_varsweep), 0.0f, 0.0f, {}))
286 DEF_GM(return F(C(sweep_varsweep), -0.5f, 0.0f, {}))
287 DEF_GM(return F(C(sweep_varsweep), -0.5f, 20.0f, {}))
288 DEF_GM(return F(C(sweep_varsweep), 0.0f, 20.0f, {}))
289 DEF_GM(return F(C(sweep_varsweep), 0.0f, 0.0f, {{"SWPS"_t, 0.f}}))
290 DEF_GM(return F(C(sweep_varsweep), 0.0f, 0.0f, {{"SWPS"_t, 90.f}}))
291 DEF_GM(return F(C(sweep_varsweep), 0.0f, 0.0f, {{"SWPE"_t, -90.f}}))
292 DEF_GM(return F(C(sweep_varsweep), 0.0f, 0.0f, {{"SWPE"_t, -45.f}}))
293 DEF_GM(return F(C(sweep_varsweep), 0.0f, 0.0f, {{"SWPS"_t, -45.f},{"SWPE"_t, 45.f}}))
294 DEF_GM(return F(C(sweep_varsweep),
295 0.0f,
296 0.0f,
297 {{"SWC1"_t, -0.25f},
298 {"SWC2"_t, 0.083333333f},
299 {"SWC3"_t, 0.083333333f},
300 {"SWC4"_t, +0.25f}}))
301 DEF_GM(return F(C(variable_alpha), 0.0f, 0.0f, {}))
302 DEF_GM(return F(C(variable_alpha), 0.0f, 0.0f, {{"APH1"_t, -0.7f}}))
303 DEF_GM(return F(C(variable_alpha), 0.0f, 0.0f, {{"APH2"_t, -0.7f}, {"APH3"_t, -0.2f}}))
304
305 } // namespace skiagm
306