• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/private/SkTo.h"
25 #include "include/svg/SkSVGCanvas.h"
26 #include "include/utils/SkParse.h"
27 #include "src/core/SkMakeUnique.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 
MakeDOMCanvas(SkDOM * dom,uint32_t flags=0)40 static std::unique_ptr<SkCanvas> MakeDOMCanvas(SkDOM* dom, uint32_t flags = 0) {
41     auto svgDevice = SkSVGDevice::Make(SkISize::Make(100, 100),
42                                        skstd::make_unique<SkXMLParserWriter>(dom->beginParsing()),
43                                        flags);
44     return svgDevice ? skstd::make_unique<SkCanvas>(svgDevice)
45                      : nullptr;
46 }
47 
48 namespace {
49 
50 
check_text_node(skiatest::Reporter * reporter,const SkDOM & dom,const SkDOM::Node * root,const SkPoint & offset,unsigned scalarsPerPos,const char * txt,const char * expected)51 void 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 
test_whitespace_pos(skiatest::Reporter * reporter,const char * txt,const char * expected)118 void 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 
DEF_TEST(SVGDevice_whitespace_pos,reporter)159 DEF_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 
SetImageShader(SkPaint * paint,int imageWidth,int imageHeight,SkTileMode xTile,SkTileMode yTile)181 void 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, nullptr));
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.
FindImageShaderNodes(skiatest::Reporter * reporter,const SkDOM * dom,const SkDOM::Node * root,const SkDOM::Node ** patternOut,const SkDOM::Node ** imageOut,const SkDOM::Node ** rectOut)191 bool 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 
ImageShaderTestSetup(SkDOM * dom,SkPaint * paint,int imageWidth,int imageHeight,int rectWidth,int rectHeight,SkTileMode xTile,SkTileMode yTile)230 void 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 
DEF_TEST(SVGDevice_image_shader_norepeat,reporter)240 DEF_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 
DEF_TEST(SVGDevice_image_shader_tilex,reporter)265 DEF_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 
DEF_TEST(SVGDevice_image_shader_tiley,reporter)295 DEF_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 
DEF_TEST(SVGDevice_image_shader_tileboth,reporter)325 DEF_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 
DEF_TEST(SVGDevice_ColorFilters,reporter)353 DEF_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"), "rgb(255,0,0)") == 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 
DEF_TEST(SVGDevice_textpath,reporter)385 DEF_TEST(SVGDevice_textpath, reporter) {
386     SkDOM dom;
387     SkFont font(ToolUtils::create_portable_typeface());
388     SkPaint paint;
389 
390     // By default, we emit <text> nodes.
391     {
392         auto svgCanvas = MakeDOMCanvas(&dom);
393         svgCanvas->drawString("foo", 100, 100, font, paint);
394     }
395     const auto* rootElement = dom.finishParsing();
396     REPORTER_ASSERT(reporter, rootElement, "root element not found");
397     const auto* textElement = dom.getFirstChild(rootElement, "text");
398     REPORTER_ASSERT(reporter, textElement, "text element not found");
399     const auto* pathElement = dom.getFirstChild(rootElement, "path");
400     REPORTER_ASSERT(reporter, !pathElement, "path element found");
401 
402     // With kConvertTextToPaths_Flag, we emit <path> nodes.
403     {
404         auto svgCanvas = MakeDOMCanvas(&dom, SkSVGCanvas::kConvertTextToPaths_Flag);
405         svgCanvas->drawString("foo", 100, 100, font, paint);
406     }
407     rootElement = dom.finishParsing();
408     REPORTER_ASSERT(reporter, rootElement, "root element not found");
409     textElement = dom.getFirstChild(rootElement, "text");
410     REPORTER_ASSERT(reporter, !textElement, "text element found");
411     pathElement = dom.getFirstChild(rootElement, "path");
412     REPORTER_ASSERT(reporter, pathElement, "path element not found");
413 }
414 
415 #endif
416