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 8#define ABORT_TEST(r, cond, ...) \ 9 do { \ 10 if (cond) { \ 11 REPORT_FAILURE(r, #cond, SkStringPrintf(__VA_ARGS__)); \ 12 return; \ 13 } \ 14 } while (0) 15 16#include "include/core/SkBitmap.h" 17#include "include/core/SkCanvas.h" 18#include "include/core/SkColorFilter.h" 19#include "include/core/SkData.h" 20#include "include/core/SkImage.h" 21#include "include/core/SkShader.h" 22#include "include/core/SkStream.h" 23#include "include/core/SkTextBlob.h" 24#include "include/effects/SkDashPathEffect.h" 25#include "include/private/SkTo.h" 26#include "include/svg/SkSVGCanvas.h" 27#include "include/utils/SkParse.h" 28#include "src/shaders/SkImageShader.h" 29#include "tests/Test.h" 30#include "tools/ToolUtils.h" 31 32#include <string.h> 33 34#ifdef SK_XML 35 36#include "src/svg/SkSVGDevice.h" 37#include "src/xml/SkDOM.h" 38#include "src/xml/SkXMLWriter.h" 39 40static std::unique_ptr<SkCanvas> MakeDOMCanvas(SkDOM* dom, uint32_t flags = 0) { 41 auto svgDevice = SkSVGDevice::Make(SkISize::Make(100, 100), 42 std::make_unique<SkXMLParserWriter>(dom->beginParsing()), 43 flags); 44 return svgDevice ? std::make_unique<SkCanvas>(svgDevice) 45 : nullptr; 46} 47 48namespace { 49 50 51void check_text_node(skiatest::Reporter* reporter, 52 const SkDOM& dom, 53 const SkDOM::Node* root, 54 const SkPoint& offset, 55 unsigned scalarsPerPos, 56 const char* txt, 57 const char* expected) { 58 if (root == nullptr) { 59 ERRORF(reporter, "root element not found."); 60 return; 61 } 62 63 const SkDOM::Node* textElem = dom.getFirstChild(root, "text"); 64 if (textElem == nullptr) { 65 ERRORF(reporter, "<text> element not found."); 66 return; 67 } 68 REPORTER_ASSERT(reporter, dom.getType(textElem) == SkDOM::kElement_Type); 69 70 const SkDOM::Node* textNode= dom.getFirstChild(textElem); 71 REPORTER_ASSERT(reporter, textNode != nullptr); 72 if (textNode != nullptr) { 73 REPORTER_ASSERT(reporter, dom.getType(textNode) == SkDOM::kText_Type); 74 REPORTER_ASSERT(reporter, strcmp(expected, dom.getName(textNode)) == 0); 75 } 76 77 int textLen = SkToInt(strlen(expected)); 78 79 const char* x = dom.findAttr(textElem, "x"); 80 REPORTER_ASSERT(reporter, x != nullptr); 81 if (x != nullptr) { 82 int xposCount = textLen; 83 REPORTER_ASSERT(reporter, SkParse::Count(x) == xposCount); 84 85 SkAutoTMalloc<SkScalar> xpos(xposCount); 86 SkParse::FindScalars(x, xpos.get(), xposCount); 87 if (scalarsPerPos < 1) { 88 // For default-positioned text, we cannot make any assumptions regarding 89 // the first glyph position when the string has leading whitespace (to be stripped). 90 if (txt[0] != ' ' && txt[0] != '\t') { 91 REPORTER_ASSERT(reporter, xpos[0] == offset.x()); 92 } 93 } else { 94 for (int i = 0; i < xposCount; ++i) { 95 REPORTER_ASSERT(reporter, xpos[i] == SkIntToScalar(expected[i])); 96 } 97 } 98 } 99 100 const char* y = dom.findAttr(textElem, "y"); 101 REPORTER_ASSERT(reporter, y != nullptr); 102 if (y != nullptr) { 103 int yposCount = (scalarsPerPos < 2) ? 1 : textLen; 104 REPORTER_ASSERT(reporter, SkParse::Count(y) == yposCount); 105 106 SkAutoTMalloc<SkScalar> ypos(yposCount); 107 SkParse::FindScalars(y, ypos.get(), yposCount); 108 if (scalarsPerPos < 2) { 109 REPORTER_ASSERT(reporter, ypos[0] == offset.y()); 110 } else { 111 for (int i = 0; i < yposCount; ++i) { 112 REPORTER_ASSERT(reporter, ypos[i] == 150 - SkIntToScalar(expected[i])); 113 } 114 } 115 } 116} 117 118void test_whitespace_pos(skiatest::Reporter* reporter, 119 const char* txt, 120 const char* expected) { 121 size_t len = strlen(txt); 122 123 SkDOM dom; 124 SkPaint paint; 125 SkFont font(ToolUtils::create_portable_typeface()); 126 SkPoint offset = SkPoint::Make(10, 20); 127 128 { 129 MakeDOMCanvas(&dom)->drawSimpleText(txt, len, SkTextEncoding::kUTF8, 130 offset.x(), offset.y(), font, paint); 131 } 132 check_text_node(reporter, dom, dom.finishParsing(), offset, 0, txt, expected); 133 134 { 135 SkAutoTMalloc<SkScalar> xpos(len); 136 for (int i = 0; i < SkToInt(len); ++i) { 137 xpos[i] = SkIntToScalar(txt[i]); 138 } 139 140 auto blob = SkTextBlob::MakeFromPosTextH(txt, len, &xpos[0], offset.y(), font); 141 MakeDOMCanvas(&dom)->drawTextBlob(blob, 0, 0, paint); 142 } 143 check_text_node(reporter, dom, dom.finishParsing(), offset, 1, txt, expected); 144 145 { 146 SkAutoTMalloc<SkPoint> pos(len); 147 for (int i = 0; i < SkToInt(len); ++i) { 148 pos[i] = SkPoint::Make(SkIntToScalar(txt[i]), 150 - SkIntToScalar(txt[i])); 149 } 150 151 auto blob = SkTextBlob::MakeFromPosText(txt, len, &pos[0], font); 152 MakeDOMCanvas(&dom)->drawTextBlob(blob, 0, 0, paint); 153 } 154 check_text_node(reporter, dom, dom.finishParsing(), offset, 2, txt, expected); 155} 156 157} // namespace 158 159DEF_TEST(SVGDevice_whitespace_pos, reporter) { 160 static const struct { 161 const char* tst_in; 162 const char* tst_out; 163 } tests[] = { 164 { "abcd" , "abcd" }, 165 { "ab cd" , "ab cd" }, 166 { "ab \t\t cd", "ab cd" }, 167 { " abcd" , "abcd" }, 168 { " abcd" , "abcd" }, 169 { " \t\t abcd", "abcd" }, 170 { "abcd " , "abcd " }, // we allow one trailing whitespace char 171 { "abcd " , "abcd " }, // because it makes no difference and 172 { "abcd\t " , "abcd " }, // simplifies the implementation 173 { "\t\t \t ab \t\t \t cd \t\t \t ", "ab cd " }, 174 }; 175 176 for (unsigned i = 0; i < SK_ARRAY_COUNT(tests); ++i) { 177 test_whitespace_pos(reporter, tests[i].tst_in, tests[i].tst_out); 178 } 179} 180 181void SetImageShader(SkPaint* paint, int imageWidth, int imageHeight, SkTileMode xTile, 182 SkTileMode yTile) { 183 auto surface = SkSurface::MakeRasterN32Premul(imageWidth, imageHeight); 184 paint->setShader(surface->makeImageSnapshot()->makeShader(xTile, yTile, SkSamplingOptions())); 185} 186 187// Attempt to find the three nodes on which we have expectations: 188// the pattern node, the image within that pattern, and the rect which 189// uses the pattern as a fill. 190// returns false if not all nodes are found. 191bool FindImageShaderNodes(skiatest::Reporter* reporter, const SkDOM* dom, const SkDOM::Node* root, 192 const SkDOM::Node** patternOut, const SkDOM::Node** imageOut, 193 const SkDOM::Node** rectOut) { 194 if (root == nullptr || dom == nullptr) { 195 ERRORF(reporter, "root element not found"); 196 return false; 197 } 198 199 200 const SkDOM::Node* rect = dom->getFirstChild(root, "rect"); 201 if (rect == nullptr) { 202 ERRORF(reporter, "rect not found"); 203 return false; 204 } 205 *rectOut = rect; 206 207 const SkDOM::Node* defs = dom->getFirstChild(root, "defs"); 208 if (defs == nullptr) { 209 ERRORF(reporter, "defs not found"); 210 return false; 211 } 212 213 const SkDOM::Node* pattern = dom->getFirstChild(defs, "pattern"); 214 if (pattern == nullptr) { 215 ERRORF(reporter, "pattern not found"); 216 return false; 217 } 218 *patternOut = pattern; 219 220 const SkDOM::Node* image = dom->getFirstChild(pattern, "image"); 221 if (image == nullptr) { 222 ERRORF(reporter, "image not found"); 223 return false; 224 } 225 *imageOut = image; 226 227 return true; 228} 229 230void ImageShaderTestSetup(SkDOM* dom, SkPaint* paint, int imageWidth, int imageHeight, 231 int rectWidth, int rectHeight, SkTileMode xTile, SkTileMode yTile) { 232 SetImageShader(paint, imageWidth, imageHeight, xTile, yTile); 233 auto svgCanvas = MakeDOMCanvas(dom); 234 235 SkRect bounds{0, 0, SkIntToScalar(rectWidth), SkIntToScalar(rectHeight)}; 236 svgCanvas->drawRect(bounds, *paint); 237} 238 239 240DEF_TEST(SVGDevice_image_shader_norepeat, reporter) { 241 SkDOM dom; 242 SkPaint paint; 243 int imageWidth = 3, imageHeight = 3; 244 int rectWidth = 10, rectHeight = 10; 245 ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight, 246 SkTileMode::kClamp, SkTileMode::kClamp); 247 248 const SkDOM::Node* root = dom.finishParsing(); 249 250 const SkDOM::Node *patternNode, *imageNode, *rectNode; 251 bool structureAppropriate = 252 FindImageShaderNodes(reporter, &dom, root, &patternNode, &imageNode, &rectNode); 253 REPORTER_ASSERT(reporter, structureAppropriate); 254 255 // the image should always maintain its size. 256 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageWidth); 257 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageHeight); 258 259 // making the pattern as large as the container prevents 260 // it from repeating. 261 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "width"), "100%") == 0); 262 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "height"), "100%") == 0); 263} 264 265DEF_TEST(SVGDevice_image_shader_tilex, reporter) { 266 SkDOM dom; 267 SkPaint paint; 268 int imageWidth = 3, imageHeight = 3; 269 int rectWidth = 10, rectHeight = 10; 270 ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight, 271 SkTileMode::kRepeat, SkTileMode::kClamp); 272 273 const SkDOM::Node* root = dom.finishParsing(); 274 const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg"); 275 if (innerSvg == nullptr) { 276 ERRORF(reporter, "inner svg element not found"); 277 return; 278 } 279 280 const SkDOM::Node *patternNode, *imageNode, *rectNode; 281 bool structureAppropriate = 282 FindImageShaderNodes(reporter, &dom, innerSvg, &patternNode, &imageNode, &rectNode); 283 REPORTER_ASSERT(reporter, structureAppropriate); 284 285 // the imageNode should always maintain its size. 286 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageWidth); 287 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageHeight); 288 289 // if the patternNode width matches the imageNode width, 290 // it will repeat in along the x axis. 291 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "width")) == imageWidth); 292 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "height"), "100%") == 0); 293} 294 295DEF_TEST(SVGDevice_image_shader_tiley, reporter) { 296 SkDOM dom; 297 SkPaint paint; 298 int imageNodeWidth = 3, imageNodeHeight = 3; 299 int rectNodeWidth = 10, rectNodeHeight = 10; 300 ImageShaderTestSetup(&dom, &paint, imageNodeWidth, imageNodeHeight, rectNodeWidth, 301 rectNodeHeight, SkTileMode::kClamp, SkTileMode::kRepeat); 302 303 const SkDOM::Node* root = dom.finishParsing(); 304 const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg"); 305 if (innerSvg == nullptr) { 306 ERRORF(reporter, "inner svg element not found"); 307 return; 308 } 309 310 const SkDOM::Node *patternNode, *imageNode, *rectNode; 311 bool structureAppropriate = 312 FindImageShaderNodes(reporter, &dom, innerSvg, &patternNode, &imageNode, &rectNode); 313 REPORTER_ASSERT(reporter, structureAppropriate); 314 315 // the imageNode should always maintain its size. 316 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageNodeWidth); 317 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageNodeHeight); 318 319 // making the patternNode as large as the container prevents 320 // it from repeating. 321 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(patternNode, "width"), "100%") == 0); 322 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "height")) == imageNodeHeight); 323} 324 325DEF_TEST(SVGDevice_image_shader_tileboth, reporter) { 326 SkDOM dom; 327 SkPaint paint; 328 int imageWidth = 3, imageHeight = 3; 329 int rectWidth = 10, rectHeight = 10; 330 ImageShaderTestSetup(&dom, &paint, imageWidth, imageHeight, rectWidth, rectHeight, 331 SkTileMode::kRepeat, SkTileMode::kRepeat); 332 333 const SkDOM::Node* root = dom.finishParsing(); 334 335 const SkDOM::Node *patternNode, *imageNode, *rectNode; 336 const SkDOM::Node* innerSvg = dom.getFirstChild(root, "svg"); 337 if (innerSvg == nullptr) { 338 ERRORF(reporter, "inner svg element not found"); 339 return; 340 } 341 bool structureAppropriate = 342 FindImageShaderNodes(reporter, &dom, innerSvg, &patternNode, &imageNode, &rectNode); 343 REPORTER_ASSERT(reporter, structureAppropriate); 344 345 // the imageNode should always maintain its size. 346 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "width")) == imageWidth); 347 REPORTER_ASSERT(reporter, atoi(dom.findAttr(imageNode, "height")) == imageHeight); 348 349 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "width")) == imageWidth); 350 REPORTER_ASSERT(reporter, atoi(dom.findAttr(patternNode, "height")) == imageHeight); 351} 352 353DEF_TEST(SVGDevice_ColorFilters, reporter) { 354 SkDOM dom; 355 SkPaint paint; 356 paint.setColorFilter(SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcIn)); 357 { 358 auto svgCanvas = MakeDOMCanvas(&dom); 359 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)}; 360 svgCanvas->drawRect(bounds, paint); 361 } 362 const SkDOM::Node* rootElement = dom.finishParsing(); 363 ABORT_TEST(reporter, !rootElement, "root element not found"); 364 365 const SkDOM::Node* filterElement = dom.getFirstChild(rootElement, "filter"); 366 ABORT_TEST(reporter, !filterElement, "filter element not found"); 367 368 const SkDOM::Node* floodElement = dom.getFirstChild(filterElement, "feFlood"); 369 ABORT_TEST(reporter, !floodElement, "feFlood element not found"); 370 371 const SkDOM::Node* compositeElement = dom.getFirstChild(filterElement, "feComposite"); 372 ABORT_TEST(reporter, !compositeElement, "feComposite element not found"); 373 374 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(filterElement, "width"), "100%") == 0); 375 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(filterElement, "height"), "100%") == 0); 376 377 REPORTER_ASSERT(reporter, 378 strcmp(dom.findAttr(floodElement, "flood-color"), "red") == 0); 379 REPORTER_ASSERT(reporter, atoi(dom.findAttr(floodElement, "flood-opacity")) == 1); 380 381 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(compositeElement, "in"), "flood") == 0); 382 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(compositeElement, "operator"), "in") == 0); 383} 384 385DEF_TEST(SVGDevice_textpath, reporter) { 386 SkDOM dom; 387 SkFont font(ToolUtils::create_portable_typeface()); 388 SkPaint paint; 389 390 auto check_text = [&](uint32_t flags, bool expect_path) { 391 // By default, we emit <text> nodes. 392 { 393 auto svgCanvas = MakeDOMCanvas(&dom, flags); 394 svgCanvas->drawString("foo", 100, 100, font, paint); 395 } 396 const auto* rootElement = dom.finishParsing(); 397 REPORTER_ASSERT(reporter, rootElement, "root element not found"); 398 const auto* textElement = dom.getFirstChild(rootElement, "text"); 399 REPORTER_ASSERT(reporter, !!textElement == !expect_path, "unexpected text element"); 400 const auto* pathElement = dom.getFirstChild(rootElement, "path"); 401 REPORTER_ASSERT(reporter, !!pathElement == expect_path, "unexpected path element"); 402 }; 403 404 // By default, we emit <text> nodes. 405 check_text(0, /*expect_path=*/false); 406 407 // With kConvertTextToPaths_Flag, we emit <path> nodes. 408 check_text(SkSVGCanvas::kConvertTextToPaths_Flag, /*expect_path=*/true); 409 410 // We also use paths in the presence of path effects. 411 SkScalar intervals[] = {10, 5}; 412 paint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0)); 413 check_text(0, /*expect_path=*/true); 414} 415 416DEF_TEST(SVGDevice_fill_stroke, reporter) { 417 struct { 418 SkColor color; 419 SkPaint::Style style; 420 const char* expected_fill; 421 const char* expected_stroke; 422 } gTests[] = { 423 { SK_ColorBLACK, SkPaint::kFill_Style , nullptr, nullptr }, 424 { SK_ColorBLACK, SkPaint::kStroke_Style, "none" , "black" }, 425 { SK_ColorRED , SkPaint::kFill_Style , "red" , nullptr }, 426 { SK_ColorRED , SkPaint::kStroke_Style, "none" , "red" }, 427 }; 428 429 for (const auto& tst : gTests) { 430 SkPaint p; 431 p.setColor(tst.color); 432 p.setStyle(tst.style); 433 434 SkDOM dom; 435 { 436 MakeDOMCanvas(&dom)->drawRect(SkRect::MakeWH(100, 100), p); 437 } 438 439 const auto* root = dom.finishParsing(); 440 REPORTER_ASSERT(reporter, root, "root element not found"); 441 const auto* rect = dom.getFirstChild(root, "rect"); 442 REPORTER_ASSERT(reporter, rect, "rect element not found"); 443 const auto* fill = dom.findAttr(rect, "fill"); 444 REPORTER_ASSERT(reporter, !!fill == !!tst.expected_fill); 445 if (fill) { 446 REPORTER_ASSERT(reporter, strcmp(fill, tst.expected_fill) == 0); 447 } 448 const auto* stroke = dom.findAttr(rect, "stroke"); 449 REPORTER_ASSERT(reporter, !!stroke == !!tst.expected_stroke); 450 if (stroke) { 451 REPORTER_ASSERT(reporter, strcmp(stroke, tst.expected_stroke) == 0); 452 } 453 } 454} 455 456DEF_TEST(SVGDevice_fill_rect_hex, reporter) { 457 SkDOM dom; 458 SkPaint paint; 459 paint.setColor(SK_ColorBLUE); 460 { 461 auto svgCanvas = MakeDOMCanvas(&dom); 462 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)}; 463 svgCanvas->drawRect(bounds, paint); 464 } 465 const SkDOM::Node* rootElement = dom.finishParsing(); 466 ABORT_TEST(reporter, !rootElement, "root element not found"); 467 468 const SkDOM::Node* rectElement = dom.getFirstChild(rootElement, "rect"); 469 ABORT_TEST(reporter, !rectElement, "rect element not found"); 470 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "blue") == 0); 471} 472 473DEF_TEST(SVGDevice_fill_rect_custom_hex, reporter) { 474 SkDOM dom; 475 { 476 SkPaint paint; 477 paint.setColor(0xFFAABCDE); 478 auto svgCanvas = MakeDOMCanvas(&dom); 479 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)}; 480 svgCanvas->drawRect(bounds, paint); 481 paint.setColor(0xFFAABBCC); 482 svgCanvas->drawRect(bounds, paint); 483 paint.setColor(0xFFAA1123); 484 svgCanvas->drawRect(bounds, paint); 485 } 486 const SkDOM::Node* rootElement = dom.finishParsing(); 487 ABORT_TEST(reporter, !rootElement, "root element not found"); 488 489 // Test 0xAABCDE filled rect. 490 const SkDOM::Node* rectElement = dom.getFirstChild(rootElement, "rect"); 491 ABORT_TEST(reporter, !rectElement, "rect element not found"); 492 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "#AABCDE") == 0); 493 494 // Test 0xAABBCC filled rect. 495 rectElement = dom.getNextSibling(rectElement, "rect"); 496 ABORT_TEST(reporter, !rectElement, "rect element not found"); 497 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "#ABC") == 0); 498 499 // Test 0xFFAA1123 filled rect. Make sure it does not turn into #A123. 500 rectElement = dom.getNextSibling(rectElement, "rect"); 501 ABORT_TEST(reporter, !rectElement, "rect element not found"); 502 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectElement, "fill"), "#AA1123") == 0); 503} 504 505DEF_TEST(SVGDevice_fill_stroke_rect_hex, reporter) { 506 SkDOM dom; 507 { 508 auto svgCanvas = MakeDOMCanvas(&dom); 509 SkRect bounds{0, 0, SkIntToScalar(100), SkIntToScalar(100)}; 510 511 SkPaint paint; 512 paint.setColor(0xFF00BBAC); 513 svgCanvas->drawRect(bounds, paint); 514 paint.setStyle(SkPaint::kStroke_Style); 515 paint.setColor(0xFF123456); 516 paint.setStrokeWidth(1); 517 svgCanvas->drawRect(bounds, paint); 518 } 519 const SkDOM::Node* rootElement = dom.finishParsing(); 520 ABORT_TEST(reporter, !rootElement, "root element not found"); 521 522 const SkDOM::Node* rectNode = dom.getFirstChild(rootElement, "rect"); 523 ABORT_TEST(reporter, !rectNode, "rect element not found"); 524 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectNode, "fill"), "#00BBAC") == 0); 525 526 rectNode = dom.getNextSibling(rectNode, "rect"); 527 ABORT_TEST(reporter, !rectNode, "rect element not found"); 528 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectNode, "stroke"), "#123456") == 0); 529 REPORTER_ASSERT(reporter, strcmp(dom.findAttr(rectNode, "stroke-width"), "1") == 0); 530} 531 532DEF_TEST(SVGDevice_path_effect, reporter) { 533 SkDOM dom; 534 535 SkPaint paint; 536 paint.setColor(SK_ColorRED); 537 paint.setStyle(SkPaint::kStroke_Style); 538 paint.setStrokeWidth(10); 539 paint.setStrokeCap(SkPaint::kRound_Cap); 540 541 // Produces a line of three red dots. 542 SkScalar intervals[] = {0, 20}; 543 sk_sp<SkPathEffect> pathEffect = SkDashPathEffect::Make(intervals, 2, 0); 544 paint.setPathEffect(pathEffect); 545 SkPoint points[] = {{50, 15}, {100, 15}, {150, 15} }; 546 { 547 auto svgCanvas = MakeDOMCanvas(&dom); 548 svgCanvas->drawPoints(SkCanvas::kLines_PointMode, 3, points, paint); 549 } 550 const auto* rootElement = dom.finishParsing(); 551 REPORTER_ASSERT(reporter, rootElement, "root element not found"); 552 const auto* pathElement = dom.getFirstChild(rootElement, "path"); 553 REPORTER_ASSERT(reporter, pathElement, "path element not found"); 554 555 // The SVG path to draw the three dots is a complex list of instructions. 556 // To avoid test brittleness, we don't attempt to match the entire path. 557 // Instead, we simply confirm there are three (M)ove instructions, one per 558 // dot. If path effects were not being honored, we would expect only one 559 // Move instruction, to the starting position, before drawing a continuous 560 // straight line. 561 const auto* d = dom.findAttr(pathElement, "d"); 562 int mCount = 0; 563 const char* pos; 564 for (pos = d; *pos != '\0'; pos++) { 565 mCount += (*pos == 'M') ? 1 : 0; 566 } 567 REPORTER_ASSERT(reporter, mCount == 3); 568} 569 570DEF_TEST(SVGDevice_relative_path_encoding, reporter) { 571 SkDOM dom; 572 { 573 auto svgCanvas = MakeDOMCanvas(&dom, SkSVGCanvas::kRelativePathEncoding_Flag); 574 SkPath path; 575 path.moveTo(100, 50); 576 path.lineTo(200, 50); 577 path.lineTo(200, 150); 578 path.close(); 579 580 svgCanvas->drawPath(path, SkPaint()); 581 } 582 583 const auto* rootElement = dom.finishParsing(); 584 REPORTER_ASSERT(reporter, rootElement, "root element not found"); 585 const auto* pathElement = dom.getFirstChild(rootElement, "path"); 586 REPORTER_ASSERT(reporter, pathElement, "path element not found"); 587 const auto* d = dom.findAttr(pathElement, "d"); 588 REPORTER_ASSERT(reporter, !strcmp(d, "m100 50l100 0l0 100l-100 -100Z")); 589} 590 591DEF_TEST(SVGDevice_color_shader, reporter) { 592 SkDOM dom; 593 { 594 auto svgCanvas = MakeDOMCanvas(&dom); 595 596 SkPaint paint; 597 paint.setShader(SkShaders::Color(0xffffff00)); 598 599 svgCanvas->drawCircle(100, 100, 100, paint); 600 } 601 602 const auto* rootElement = dom.finishParsing(); 603 REPORTER_ASSERT(reporter, rootElement, "root element not found"); 604 const auto* ellipseElement = dom.getFirstChild(rootElement, "ellipse"); 605 REPORTER_ASSERT(reporter, ellipseElement, "ellipse element not found"); 606 const auto* fill = dom.findAttr(ellipseElement, "fill"); 607 REPORTER_ASSERT(reporter, fill, "fill attribute not found"); 608 REPORTER_ASSERT(reporter, !strcmp(fill, "yellow")); 609} 610 611#endif 612