Flutter Engine
The Flutter Engine
SVGDeviceTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2015 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
9#ifdef SK_XML
10
14#include "include/core/SkData.h"
26#include "src/svg/SkSVGDevice.h"
27#include "src/xml/SkDOM.h"
28#include "src/xml/SkXMLWriter.h"
29#include "tests/Test.h"
30#include "tools/ToolUtils.h"
32
33#include <string>
34
35using namespace skia_private;
36
37#define ABORT_TEST(r, cond, ...) \
38 do { \
39 if (cond) { \
40 REPORT_FAILURE(r, #cond, SkStringPrintf(__VA_ARGS__)); \
41 return; \
42 } \
43 } while (0)
44
45
46static std::unique_ptr<SkCanvas> MakeDOMCanvas(SkDOM* dom, uint32_t flags = 0) {
47 auto svgDevice = SkSVGDevice::Make(SkISize::Make(100, 100),
48 std::make_unique<SkXMLParserWriter>(dom->beginParsing()),
49 flags);
50 return svgDevice ? std::make_unique<SkCanvas>(svgDevice)
51 : nullptr;
52}
53
54namespace {
55
56
57void check_text_node(skiatest::Reporter* reporter,
58 const SkDOM& dom,
59 const SkDOM::Node* root,
60 const SkPoint& offset,
61 unsigned scalarsPerPos,
62 const char* txt,
63 const char* expected) {
64 if (root == nullptr) {
65 ERRORF(reporter, "root element not found.");
66 return;
67 }
68
69 const SkDOM::Node* textElem = dom.getFirstChild(root, "text");
70 if (textElem == nullptr) {
71 ERRORF(reporter, "<text> element not found.");
72 return;
73 }
74 REPORTER_ASSERT(reporter, dom.getType(textElem) == SkDOM::kElement_Type);
75
76 const SkDOM::Node* textNode= dom.getFirstChild(textElem);
77 REPORTER_ASSERT(reporter, textNode != nullptr);
78 if (textNode != nullptr) {
79 REPORTER_ASSERT(reporter, dom.getType(textNode) == SkDOM::kText_Type);
80 REPORTER_ASSERT(reporter, strcmp(expected, dom.getName(textNode)) == 0);
81 }
82
83 int textLen = SkToInt(strlen(expected));
84
85 const char* x = dom.findAttr(textElem, "x");
86 REPORTER_ASSERT(reporter, x != nullptr);
87 if (x != nullptr) {
88 int xposCount = textLen;
90
91 AutoTMalloc<SkScalar> xpos(xposCount);
92 SkParse::FindScalars(x, xpos.get(), xposCount);
93 if (scalarsPerPos < 1) {
94 // For default-positioned text, we cannot make any assumptions regarding
95 // the first glyph position when the string has leading whitespace (to be stripped).
96 if (txt[0] != ' ' && txt[0] != '\t') {
97 REPORTER_ASSERT(reporter, xpos[0] == offset.x());
98 }
99 } else {
100 for (int i = 0; i < xposCount; ++i) {
101 REPORTER_ASSERT(reporter, xpos[i] == SkIntToScalar(expected[i]));
102 }
103 }
104 }
105
106 const char* y = dom.findAttr(textElem, "y");
107 REPORTER_ASSERT(reporter, y != nullptr);
108 if (y != nullptr) {
109 int yposCount = (scalarsPerPos < 2) ? 1 : textLen;
111
112 AutoTMalloc<SkScalar> ypos(yposCount);
113 SkParse::FindScalars(y, ypos.get(), yposCount);
114 if (scalarsPerPos < 2) {
115 REPORTER_ASSERT(reporter, ypos[0] == offset.y());
116 } else {
117 for (int i = 0; i < yposCount; ++i) {
118 REPORTER_ASSERT(reporter, ypos[i] == 150 - SkIntToScalar(expected[i]));
119 }
120 }
121 }
122}
123
124void test_whitespace_pos(skiatest::Reporter* reporter,
125 const char* txt,
126 const char* expected) {
127 size_t len = strlen(txt);
128
129 SkDOM dom;
132 SkPoint offset = SkPoint::Make(10, 20);
133
134 {
135 MakeDOMCanvas(&dom)->drawSimpleText(txt, len, SkTextEncoding::kUTF8,
136 offset.x(), offset.y(), font, paint);
137 }
138 check_text_node(reporter, dom, dom.finishParsing(), offset, 0, txt, expected);
139
140 {
142 for (int i = 0; i < SkToInt(len); ++i) {
143 xpos[i] = SkIntToScalar(txt[i]);
144 }
145
146 auto blob = SkTextBlob::MakeFromPosTextH(txt, len, &xpos[0], offset.y(), font);
147 MakeDOMCanvas(&dom)->drawTextBlob(blob, 0, 0, paint);
148 }
149 check_text_node(reporter, dom, dom.finishParsing(), offset, 1, txt, expected);
150
151 {
153 for (int i = 0; i < SkToInt(len); ++i) {
155 }
156
157 auto blob = SkTextBlob::MakeFromPosText(txt, len, &pos[0], font);
158 MakeDOMCanvas(&dom)->drawTextBlob(blob, 0, 0, paint);
159 }
160 check_text_node(reporter, dom, dom.finishParsing(), offset, 2, txt, expected);
161}
162
163} // namespace
164
165DEF_TEST(SVGDevice_whitespace_pos, reporter) {
166 static const struct {
167 const char* tst_in;
168 const char* tst_out;
169 } tests[] = {
170 { "abcd" , "abcd" },
171 { "ab cd" , "ab cd" },
172 { "ab \t\t cd", "ab cd" },
173 { " abcd" , "abcd" },
174 { " abcd" , "abcd" },
175 { " \t\t abcd", "abcd" },
176 { "abcd " , "abcd " }, // we allow one trailing whitespace char
177 { "abcd " , "abcd " }, // because it makes no difference and
178 { "abcd\t " , "abcd " }, // simplifies the implementation
179 { "\t\t \t ab \t\t \t cd \t\t \t ", "ab cd " },
180 };
181
182 for (unsigned i = 0; i < std::size(tests); ++i) {
183 test_whitespace_pos(reporter, tests[i].tst_in, tests[i].tst_out);
184 }
185}
186
187void SetImageShader(SkPaint* paint, int imageWidth, int imageHeight, SkTileMode xTile,
188 SkTileMode yTile) {
190 paint->setShader(surface->makeImageSnapshot()->makeShader(xTile, yTile, SkSamplingOptions()));
191}
192
193// Attempt to find the three nodes on which we have expectations:
194// the pattern node, the image within that pattern, and the rect which
195// uses the pattern as a fill.
196// returns false if not all nodes are found.
197bool FindImageShaderNodes(skiatest::Reporter* reporter, const SkDOM* dom, const SkDOM::Node* root,
198 const SkDOM::Node** patternOut, const SkDOM::Node** imageOut,
199 const SkDOM::Node** rectOut) {
200 if (root == nullptr || dom == nullptr) {
201 ERRORF(reporter, "root element not found");
202 return false;
203 }
204
205
206 const SkDOM::Node* rect = dom->getFirstChild(root, "rect");
207 if (rect == nullptr) {
208 ERRORF(reporter, "rect not found");
209 return false;
210 }
211 *rectOut = rect;
212
213 const SkDOM::Node* defs = dom->getFirstChild(root, "defs");
214 if (defs == nullptr) {
215 ERRORF(reporter, "defs not found");
216 return false;
217 }
218
219 const SkDOM::Node* pattern = dom->getFirstChild(defs, "pattern");
220 if (pattern == nullptr) {
221 ERRORF(reporter, "pattern not found");
222 return false;
223 }
224 *patternOut = pattern;
225
226 const SkDOM::Node* image = dom->getFirstChild(pattern, "image");
227 if (image == nullptr) {
228 ERRORF(reporter, "image not found");
229 return false;
230 }
231 *imageOut = image;
232
233 return true;
234}
235
236void ImageShaderTestSetup(SkDOM* dom, SkPaint* paint, int imageWidth, int imageHeight,
237 int rectWidth, int rectHeight, SkTileMode xTile, SkTileMode yTile) {
238 SetImageShader(paint, imageWidth, imageHeight, xTile, yTile);
239 auto svgCanvas = MakeDOMCanvas(dom);
240
241 SkRect bounds{0, 0, SkIntToScalar(rectWidth), SkIntToScalar(rectHeight)};
242 svgCanvas->drawRect(bounds, *paint);
243}
244
245
246DEF_TEST(SVGDevice_image_shader_norepeat, reporter) {
247 SkDOM dom;
249 int imageWidth = 3, imageHeight = 3;
250 int rectWidth = 10, rectHeight = 10;
251 ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight,
253
254 const SkDOM::Node* root = dom.finishParsing();
255
256 const SkDOM::Node *patternNode, *imageNode, *rectNode;
257 bool structureAppropriate =
258 FindImageShaderNodes(reporter, &dom, root, &patternNode, &imageNode, &rectNode);
259 REPORTER_ASSERT(reporter, structureAppropriate);
260
261 // the image should always maintain its size.
262 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageWidth);
263 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageHeight);
264
265 // making the pattern as large as the container prevents
266 // it from repeating.
267 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "width"), "100%") == 0);
268 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "height"), "100%") == 0);
269}
270
271DEF_TEST(SVGDevice_image_shader_tilex, reporter) {
272 SkDOM dom;
274 int imageWidth = 3, imageHeight = 3;
275 int rectWidth = 10, rectHeight = 10;
276 ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight,
278
279 const SkDOM::Node* root = dom.finishParsing();
280 const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg");
281 if (innerSvg == nullptr) {
282 ERRORF(reporter, "inner svg element not found");
283 return;
284 }
285
286 const SkDOM::Node *patternNode, *imageNode, *rectNode;
287 bool structureAppropriate =
288 FindImageShaderNodes(reporter, &dom, innerSvg, &patternNode, &imageNode, &rectNode);
289 REPORTER_ASSERT(reporter, structureAppropriate);
290
291 // the imageNode should always maintain its size.
292 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageWidth);
293 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageHeight);
294
295 // if the patternNode width matches the imageNode width,
296 // it will repeat in along the x axis.
297 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "width")) == imageWidth);
298 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "height"), "100%") == 0);
299}
300
301DEF_TEST(SVGDevice_image_shader_tiley, reporter) {
302 SkDOM dom;
304 int imageNodeWidth = 3, imageNodeHeight = 3;
305 int rectNodeWidth = 10, rectNodeHeight = 10;
306 ImageShaderTestSetup(&dom, &paint, imageNodeWidth, imageNodeHeight, rectNodeWidth,
307 rectNodeHeight, SkTileMode::kClamp, SkTileMode::kRepeat);
308
309 const SkDOM::Node* root = dom.finishParsing();
310 const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg");
311 if (innerSvg == nullptr) {
312 ERRORF(reporter, "inner svg element not found");
313 return;
314 }
315
316 const SkDOM::Node *patternNode, *imageNode, *rectNode;
317 bool structureAppropriate =
318 FindImageShaderNodes(reporter, &dom, innerSvg, &patternNode, &imageNode, &rectNode);
319 REPORTER_ASSERT(reporter, structureAppropriate);
320
321 // the imageNode should always maintain its size.
322 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageNodeWidth);
323 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageNodeHeight);
324
325 // making the patternNode as large as the container prevents
326 // it from repeating.
327 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "width"), "100%") == 0);
328 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "height")) == imageNodeHeight);
329}
330
331DEF_TEST(SVGDevice_image_shader_tileboth, reporter) {
332 SkDOM dom;
334 int imageWidth = 3, imageHeight = 3;
335 int rectWidth = 10, rectHeight = 10;
336 ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight,
338
339 const SkDOM::Node* root = dom.finishParsing();
340
341 const SkDOM::Node *patternNode, *imageNode, *rectNode;
342 const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg");
343 if (innerSvg == nullptr) {
344 ERRORF(reporter, "inner svg element not found");
345 return;
346 }
347 bool structureAppropriate =
348 FindImageShaderNodes(reporter, &dom, innerSvg, &patternNode, &imageNode, &rectNode);
349 REPORTER_ASSERT(reporter, structureAppropriate);
350
351 // the imageNode should always maintain its size.
352 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageWidth);
353 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageHeight);
354
355 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "width")) == imageWidth);
356 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "height")) == imageHeight);
357}
358
359DEF_TEST(SVGDevice_ColorFilters, reporter) {
360 SkDOM dom;
363 {
364 auto svgCanvas = MakeDOMCanvas(&dom);
365 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)};
366 svgCanvas->drawRect(bounds, paint);
367 }
368 const SkDOM::Node* rootElement = dom.finishParsing();
369 ABORT_TEST(reporter, !rootElement, "root element not found");
370
371 const SkDOM::Node* filterElement = dom.getFirstChild(rootElement, "filter");
372 ABORT_TEST(reporter, !filterElement, "filter element not found");
373
374 const SkDOM::Node* floodElement = dom.getFirstChild(filterElement, "feFlood");
375 ABORT_TEST(reporter, !floodElement, "feFlood element not found");
376
377 const SkDOM::Node* compositeElement = dom.getFirstChild(filterElement, "feComposite");
378 ABORT_TEST(reporter, !compositeElement, "feComposite element not found");
379
380 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(filterElement, "width"), "100%") == 0);
381 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(filterElement, "height"), "100%") == 0);
382
384 strcmp(dom.findAttr(floodElement, "flood-color"), "red") == 0);
385 REPORTER_ASSERT(reporter, atoi(dom.findAttr(floodElement, "flood-opacity")) == 1);
386
387 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(compositeElement, "in"), "flood") == 0);
388 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(compositeElement, "operator"), "in") == 0);
389}
390
391DEF_TEST(SVGDevice_textpath, reporter) {
392 SkDOM dom;
395
396 auto check_text = [&](uint32_t flags, bool expect_path) {
397 // By default, we emit <text> nodes.
398 {
399 auto svgCanvas = MakeDOMCanvas(&dom, flags);
400 svgCanvas->drawString("foo", 100, 100, font, paint);
401 }
402 const auto* rootElement = dom.finishParsing();
403 REPORTER_ASSERT(reporter, rootElement, "root element not found");
404 const auto* textElement = dom.getFirstChild(rootElement, "text");
405 REPORTER_ASSERT(reporter, !!textElement == !expect_path, "unexpected text element");
406 const auto* pathElement = dom.getFirstChild(rootElement, "path");
407 REPORTER_ASSERT(reporter, !!pathElement == expect_path, "unexpected path element");
408 };
409
410 // By default, we emit <text> nodes.
411 check_text(0, /*expect_path=*/false);
412
413 // With kConvertTextToPaths_Flag, we emit <path> nodes.
414 check_text(SkSVGCanvas::kConvertTextToPaths_Flag, /*expect_path=*/true);
415
416 // We also use paths in the presence of path effects.
417 SkScalar intervals[] = {10, 5};
418 paint.setPathEffect(SkDashPathEffect::Make(intervals, std::size(intervals), 0));
419 check_text(0, /*expect_path=*/true);
420}
421
422DEF_TEST(SVGDevice_fill_stroke, reporter) {
423 struct {
425 SkPaint::Style style;
426 const char* expected_fill;
427 const char* expected_stroke;
428 } gTests[] = {
429 { SK_ColorBLACK, SkPaint::kFill_Style , nullptr, nullptr },
430 { SK_ColorBLACK, SkPaint::kStroke_Style, "none" , "black" },
431 { SK_ColorRED , SkPaint::kFill_Style , "red" , nullptr },
432 { SK_ColorRED , SkPaint::kStroke_Style, "none" , "red" },
433 };
434
435 for (const auto& tst : gTests) {
436 SkPaint p;
437 p.setColor(tst.color);
438 p.setStyle(tst.style);
439
440 SkDOM dom;
441 {
442 MakeDOMCanvas(&dom)->drawRect(SkRect::MakeWH(100, 100), p);
443 }
444
445 const auto* root = dom.finishParsing();
446 REPORTER_ASSERT(reporter, root, "root element not found");
447 const auto* rect = dom.getFirstChild(root, "rect");
448 REPORTER_ASSERT(reporter, rect, "rect element not found");
449 const auto* fill = dom.findAttr(rect, "fill");
450 REPORTER_ASSERT(reporter, !!fill == !!tst.expected_fill);
451 if (fill) {
452 REPORTER_ASSERT(reporter, strcmp(fill, tst.expected_fill) == 0);
453 }
454 const auto* stroke = dom.findAttr(rect, "stroke");
455 REPORTER_ASSERT(reporter, !!stroke == !!tst.expected_stroke);
456 if (stroke) {
457 REPORTER_ASSERT(reporter, strcmp(stroke, tst.expected_stroke) == 0);
458 }
459 }
460}
461
462DEF_TEST(SVGDevice_fill_opacity_black_fill, reporter) {
463 struct {
465 const char* expected_fill_opacity;
466 } gTests[] = {
467 // Semi-transparent black
468 { SkColorSetARGB(0x33, 0x00, 0x00, 0x00), "0.2" },
469 // Opaque black
470 { SkColorSetARGB(0xFF, 0x00, 0x00, 0x00), nullptr },
471 };
472
473 for (const auto& tst : gTests) {
474 SkPaint p;
475 p.setColor(tst.color);
476 p.setStyle(SkPaint::kFill_Style);
477
478 SkDOM dom;
479 {
480 auto svgCanvas = MakeDOMCanvas(&dom);
481 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)};
482 svgCanvas->drawRect(bounds, p);
483 }
484
485 const SkDOM::Node* rootElement = dom.finishParsing();
486 ABORT_TEST(reporter, !rootElement, "root element not found");
487
488 const SkDOM::Node* rectElement = dom.getFirstChild(rootElement, "rect");
489 ABORT_TEST(reporter, !rectElement, "rect element not found");
490 const auto* fill_opacity = dom.findAttr(rectElement, "fill-opacity");
491 REPORTER_ASSERT(reporter, !!fill_opacity == !!tst.expected_fill_opacity);
492 if (fill_opacity) {
493 REPORTER_ASSERT(reporter, strcmp(fill_opacity, tst.expected_fill_opacity) == 0);
494 }
495 }
496}
497
498DEF_TEST(SVGDevice_fill_rect_hex, reporter) {
499 SkDOM dom;
501 paint.setColor(SK_ColorBLUE);
502 {
503 auto svgCanvas = MakeDOMCanvas(&dom);
504 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)};
505 svgCanvas->drawRect(bounds, paint);
506 }
507 const SkDOM::Node* rootElement = dom.finishParsing();
508 ABORT_TEST(reporter, !rootElement, "root element not found");
509
510 const SkDOM::Node* rectElement = dom.getFirstChild(rootElement, "rect");
511 ABORT_TEST(reporter, !rectElement, "rect element not found");
512 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "blue") == 0);
513}
514
515DEF_TEST(SVGDevice_fill_rect_custom_hex, reporter) {
516 SkDOM dom;
517 {
519 paint.setColor(0xFFAABCDE);
520 auto svgCanvas = MakeDOMCanvas(&dom);
521 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)};
522 svgCanvas->drawRect(bounds, paint);
523 paint.setColor(0xFFAABBCC);
524 svgCanvas->drawRect(bounds, paint);
525 paint.setColor(0xFFAA1123);
526 svgCanvas->drawRect(bounds, paint);
527 }
528 const SkDOM::Node* rootElement = dom.finishParsing();
529 ABORT_TEST(reporter, !rootElement, "root element not found");
530
531 // Test 0xAABCDE filled rect.
532 const SkDOM::Node* rectElement = dom.getFirstChild(rootElement, "rect");
533 ABORT_TEST(reporter, !rectElement, "rect element not found");
534 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "#AABCDE") == 0);
535
536 // Test 0xAABBCC filled rect.
537 rectElement = dom.getNextSibling(rectElement, "rect");
538 ABORT_TEST(reporter, !rectElement, "rect element not found");
539 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "#ABC") == 0);
540
541 // Test 0xFFAA1123 filled rect. Make sure it does not turn into #A123.
542 rectElement = dom.getNextSibling(rectElement, "rect");
543 ABORT_TEST(reporter, !rectElement, "rect element not found");
544 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "#AA1123") == 0);
545}
546
547DEF_TEST(SVGDevice_fill_stroke_rect_hex, reporter) {
548 SkDOM dom;
549 {
550 auto svgCanvas = MakeDOMCanvas(&dom);
551 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)};
552
554 paint.setColor(0xFF00BBAC);
555 svgCanvas->drawRect(bounds, paint);
557 paint.setColor(0xFF123456);
558 paint.setStrokeWidth(1);
559 svgCanvas->drawRect(bounds, paint);
560 }
561 const SkDOM::Node* rootElement = dom.finishParsing();
562 ABORT_TEST(reporter, !rootElement, "root element not found");
563
564 const SkDOM::Node* rectNode = dom.getFirstChild(rootElement, "rect");
565 ABORT_TEST(reporter, !rectNode, "rect element not found");
566 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectNode, "fill"), "#00BBAC") == 0);
567
568 rectNode = dom.getNextSibling(rectNode, "rect");
569 ABORT_TEST(reporter, !rectNode, "rect element not found");
570 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectNode, "stroke"), "#123456") == 0);
571 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectNode, "stroke-width"), "1") == 0);
572}
573
574DEF_TEST(SVGDevice_rect_with_path_effect, reporter) {
575 SkDOM dom;
576
577 SkScalar intervals[] = {0, 20};
578 sk_sp<SkPathEffect> pathEffect = SkDashPathEffect::Make(intervals, 2, 0);
579
581 paint.setPathEffect(pathEffect);
582
583 {
584 auto svgCanvas = MakeDOMCanvas(&dom);
585 svgCanvas->drawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
586 }
587
588 const auto* rootElement = dom.finishParsing();
589 REPORTER_ASSERT(reporter, rootElement, "root element not found");
590 const auto* pathElement = dom.getFirstChild(rootElement, "path");
591 REPORTER_ASSERT(reporter, pathElement, "path element not found");
592}
593
594DEF_TEST(SVGDevice_rrect_with_path_effect, reporter) {
595 SkDOM dom;
596
597 SkScalar intervals[] = {0, 20};
598 sk_sp<SkPathEffect> pathEffect = SkDashPathEffect::Make(intervals, 2, 0);
599
601 paint.setPathEffect(pathEffect);
602
603 {
604 auto svgCanvas = MakeDOMCanvas(&dom);
605 svgCanvas->drawRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(0, 0, 100, 100), 10, 10), paint);
606 }
607
608 const auto* rootElement = dom.finishParsing();
609 REPORTER_ASSERT(reporter, rootElement, "root element not found");
610 const auto* pathElement = dom.getFirstChild(rootElement, "path");
611 REPORTER_ASSERT(reporter, pathElement, "path element not found");
612}
613
614DEF_TEST(SVGDevice_oval_with_path_effect, reporter) {
615 SkDOM dom;
616
617 SkScalar intervals[] = {0, 20};
618 sk_sp<SkPathEffect> pathEffect = SkDashPathEffect::Make(intervals, 2, 0);
619
621 paint.setPathEffect(pathEffect);
622
623 {
624 auto svgCanvas = MakeDOMCanvas(&dom);
625 svgCanvas->drawOval(SkRect::MakeXYWH(0, 0, 100, 100), paint);
626 }
627
628 const auto* rootElement = dom.finishParsing();
629 REPORTER_ASSERT(reporter, rootElement, "root element not found");
630 const auto* pathElement = dom.getFirstChild(rootElement, "path");
631 REPORTER_ASSERT(reporter, pathElement, "path element not found");
632}
633
634
635DEF_TEST(SVGDevice_path_effect, reporter) {
636 SkDOM dom;
637
639 paint.setColor(SK_ColorRED);
641 paint.setStrokeWidth(10);
642 paint.setStrokeCap(SkPaint::kRound_Cap);
643
644 // Produces a line of three red dots.
645 SkScalar intervals[] = {0, 20};
646 sk_sp<SkPathEffect> pathEffect = SkDashPathEffect::Make(intervals, 2, 0);
647 paint.setPathEffect(pathEffect);
648 SkPoint points[] = {{50, 15}, {100, 15}, {150, 15} };
649 {
650 auto svgCanvas = MakeDOMCanvas(&dom);
651 svgCanvas->drawPoints(SkCanvas::kLines_PointMode, 3, points, paint);
652 }
653 const auto* rootElement = dom.finishParsing();
654 REPORTER_ASSERT(reporter, rootElement, "root element not found");
655 const auto* pathElement = dom.getFirstChild(rootElement, "path");
656 REPORTER_ASSERT(reporter, pathElement, "path element not found");
657
658 // The SVG path to draw the three dots is a complex list of instructions.
659 // To avoid test brittleness, we don't attempt to match the entire path.
660 // Instead, we simply confirm there are three (M)ove instructions, one per
661 // dot. If path effects were not being honored, we would expect only one
662 // Move instruction, to the starting position, before drawing a continuous
663 // straight line.
664 const auto* d = dom.findAttr(pathElement, "d");
665 int mCount = 0;
666 const char* pos;
667 for (pos = d; *pos != '\0'; pos++) {
668 mCount += (*pos == 'M') ? 1 : 0;
669 }
670 REPORTER_ASSERT(reporter, mCount == 3);
671}
672
673DEF_TEST(SVGDevice_relative_path_encoding, reporter) {
674 SkDOM dom;
675 {
676 auto svgCanvas = MakeDOMCanvas(&dom, SkSVGCanvas::kRelativePathEncoding_Flag);
677 SkPath path;
678 path.moveTo(100, 50);
679 path.lineTo(200, 50);
680 path.lineTo(200, 150);
681 path.close();
682
683 svgCanvas->drawPath(path, SkPaint());
684 }
685
686 const auto* rootElement = dom.finishParsing();
687 REPORTER_ASSERT(reporter, rootElement, "root element not found");
688 const auto* pathElement = dom.getFirstChild(rootElement, "path");
689 REPORTER_ASSERT(reporter, pathElement, "path element not found");
690 const auto* d = dom.findAttr(pathElement, "d");
691 REPORTER_ASSERT(reporter, !strcmp(d, "m100 50l100 0l0 100l-100 -100Z"));
692}
693
694DEF_TEST(SVGDevice_color_shader, reporter) {
695 SkDOM dom;
696 {
697 auto svgCanvas = MakeDOMCanvas(&dom);
698
700 paint.setShader(SkShaders::Color(0xffffff00));
701
702 svgCanvas->drawCircle(100, 100, 100, paint);
703 }
704
705 const auto* rootElement = dom.finishParsing();
706 REPORTER_ASSERT(reporter, rootElement, "root element not found");
707 const auto* ellipseElement = dom.getFirstChild(rootElement, "ellipse");
708 REPORTER_ASSERT(reporter, ellipseElement, "ellipse element not found");
709 const auto* fill = dom.findAttr(ellipseElement, "fill");
710 REPORTER_ASSERT(reporter, fill, "fill attribute not found");
711 REPORTER_ASSERT(reporter, !strcmp(fill, "yellow"));
712}
713
714DEF_TEST(SVGDevice_parse_minmax, reporter) {
715 auto check = [&](int64_t n, bool expected) {
716 const auto str = std::to_string(n);
717
718 int val;
719 REPORTER_ASSERT(reporter, SkToBool(SkParse::FindS32(str.c_str(), &val)) == expected);
720 if (expected) {
721 REPORTER_ASSERT(reporter, val == n);
722 }
723 };
724
727 check(static_cast<int64_t>(std::numeric_limits<int>::max()) + 1, false);
728 check(static_cast<int64_t>(std::numeric_limits<int>::min()) - 1, false);
729}
730
731#endif
static BlurTest tests[]
Definition: BlurTest.cpp:84
static const TestCase gTests[]
reporter
Definition: FontMgrTest.cpp:39
static const int points[]
SkPoint pos
#define check(reporter, ref, unref, make, kill)
Definition: RefCntTest.cpp:85
@ kSrcIn
r = s * da
uint32_t SkColor
Definition: SkColor.h:37
constexpr SkColor SK_ColorBLUE
Definition: SkColor.h:135
constexpr SkColor SK_ColorRED
Definition: SkColor.h:126
static constexpr SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
Definition: SkColor.h:49
constexpr SkColor SK_ColorBLACK
Definition: SkColor.h:103
@ kUTF8
uses bytes to represent UTF-8 or ASCII
#define SkIntToScalar(x)
Definition: SkScalar.h:57
SkTileMode
Definition: SkTileMode.h:13
constexpr int SkToInt(S x)
Definition: SkTo.h:29
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
#define DEF_TEST(name, reporter)
Definition: Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
#define ERRORF(r,...)
Definition: Test.h:293
@ kLines_PointMode
draw each pair of points as a line segment
Definition: SkCanvas.h:1242
static sk_sp< SkColorFilter > Blend(const SkColor4f &c, sk_sp< SkColorSpace >, SkBlendMode mode)
Definition: SkDOM.h:24
@ kText_Type
Definition: SkDOM.h:44
@ kElement_Type
Definition: SkDOM.h:43
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
Definition: SkFont.h:35
@ kRound_Cap
adds circle
Definition: SkPaint.h:335
@ kStroke_Style
set to stroke geometry
Definition: SkPaint.h:194
@ kFill_Style
set to fill geometry
Definition: SkPaint.h:193
static const char * FindS32(const char str[], int32_t *value)
Definition: SkParse.cpp:143
static int Count(const char str[])
Definition: SkParse.cpp:72
static const char * FindScalars(const char str[], SkScalar value[], int count)
Definition: SkParse.cpp:231
Definition: SkPath.h:59
static SkRRect MakeRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition: SkRRect.h:180
@ kRelativePathEncoding_Flag
Definition: SkSVGCanvas.h:25
@ kConvertTextToPaths_Flag
Definition: SkSVGCanvas.h:23
static sk_sp< SkDevice > Make(const SkISize &size, std::unique_ptr< SkXMLWriter >, uint32_t flags)
static sk_sp< SkTextBlob > MakeFromPosTextH(const void *text, size_t byteLength, const SkScalar xpos[], SkScalar constY, const SkFont &font, SkTextEncoding encoding=SkTextEncoding::kUTF8)
Definition: SkTextBlob.cpp:817
static sk_sp< SkTextBlob > MakeFromPosText(const void *text, size_t byteLength, const SkPoint pos[], const SkFont &font, SkTextEncoding encoding=SkTextEncoding::kUTF8)
Definition: SkTextBlob.cpp:803
const Paint & paint
Definition: color_source.cc:38
DlColor color
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
VkSurfaceKHR surface
Definition: main.cc:49
float SkScalar
Definition: extension.cpp:12
FlutterSemanticsFlag flags
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
double y
double x
Optional< SkRect > bounds
Definition: SkRecords.h:189
sk_sp< const SkImage > image
Definition: SkRecords.h:269
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
SK_API sk_sp< SkShader > Color(SkColor)
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
SkFont DefaultPortableFont()
Definition: dom.py:1
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
font
Font Metadata and Metrics.
string root
Definition: scale_cpu.py:20
static const int imageHeight
static const int imageWidth
static SkString to_string(int n)
Definition: nanobench.cpp:119
SeparatedVector2 offset
static constexpr SkISize Make(int32_t w, int32_t h)
Definition: SkSize.h:20
static SkImageInfo MakeN32Premul(int width, int height)
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
static constexpr SkRect MakeWH(float w, float h)
Definition: SkRect.h:609