2 * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * Copyright notice for the EFL:
26 * Copyright (C) EFL developers (see AUTHORS)
28 * All rights reserved.
30 * Redistribution and use in source and binary forms, with or without
31 * modification, are permitted provided that the following conditions are met:
33 * 1. Redistributions of source code must retain the above copyright
34 * notice, this list of conditions and the following disclaimer.
35 * 2. Redistributions in binary form must reproduce the above copyright
36 * notice, this list of conditions and the following disclaimer in the
37 * documentation and/or other materials provided with the distribution.
39 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
40 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
41 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
42 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
44 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
45 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
46 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
47 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
48 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54 #include "tvgSvgLoaderCommon.h"
55 #include "tvgSvgSceneBuilder.h"
56 #include "tvgSvgPath.h"
57 #include "tvgSvgUtil.h"
59 /************************************************************************/
60 /* Internal Class Implementation */
61 /************************************************************************/
69 static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
70 static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask);
73 static inline bool _isGroupType(SvgNodeType type)
75 if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true;
80 static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
82 gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
83 gradTransf->e12 *= mBBox->e11;
84 gradTransf->e11 *= mBBox->e11;
86 gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
87 gradTransf->e22 *= mBBox->e22;
88 gradTransf->e21 *= mBBox->e22;
92 static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
94 Fill::ColorStop* stops;
96 auto fillGrad = LinearGradient::gen();
98 bool isTransform = (g->transform ? true : false);
99 Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
100 if (isTransform) finalTransform = *g->transform;
103 g->linear->x1 = g->linear->x1 * vBox.w;
104 g->linear->y1 = g->linear->y1 * vBox.h;
105 g->linear->x2 = g->linear->x2 * vBox.w;
106 g->linear->y2 = g->linear->y2 * vBox.h;
108 Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
109 if (isTransform) _transformMultiply(&m, &finalTransform);
116 if (isTransform) fillGrad->transform(finalTransform);
118 fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
119 fillGrad->spread(g->spread);
122 stopCount = g->stops.count;
124 stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
125 if (!stops) return fillGrad;
126 auto prevOffset = 0.0f;
127 for (uint32_t i = 0; i < g->stops.count; ++i) {
128 auto colorStop = &g->stops.data[i];
129 //Use premultiplied color
130 stops[i].r = colorStop->r;
131 stops[i].g = colorStop->g;
132 stops[i].b = colorStop->b;
133 stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
134 stops[i].offset = colorStop->offset;
135 //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
136 if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
137 else if (colorStop->offset > 1) stops[i].offset = 1;
138 prevOffset = stops[i].offset;
140 fillGrad->colorStops(stops, stopCount);
147 static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
149 Fill::ColorStop *stops;
151 auto fillGrad = RadialGradient::gen();
153 bool isTransform = (g->transform ? true : false);
154 Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
155 if (isTransform) finalTransform = *g->transform;
158 //The radius scalling is done according to the Units section:
159 //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
160 g->radial->cx = g->radial->cx * vBox.w;
161 g->radial->cy = g->radial->cy * vBox.h;
162 g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
163 g->radial->fx = g->radial->fx * vBox.w;
164 g->radial->fy = g->radial->fy * vBox.h;
166 Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
167 if (isTransform) _transformMultiply(&m, &finalTransform);
174 if (isTransform) fillGrad->transform(finalTransform);
176 //TODO: Tvg is not support to focal
177 //if (g->radial->fx != 0 && g->radial->fy != 0) {
178 // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
180 fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
181 fillGrad->spread(g->spread);
184 stopCount = g->stops.count;
186 stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
187 if (!stops) return fillGrad;
188 auto prevOffset = 0.0f;
189 for (uint32_t i = 0; i < g->stops.count; ++i) {
190 auto colorStop = &g->stops.data[i];
191 //Use premultiplied color
192 stops[i].r = colorStop->r;
193 stops[i].g = colorStop->g;
194 stops[i].b = colorStop->b;
195 stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
196 stops[i].offset = colorStop->offset;
197 //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
198 if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
199 else if (colorStop->offset > 1) stops[i].offset = 1;
200 prevOffset = stops[i].offset;
202 fillGrad->colorStops(stops, stopCount);
209 static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
213 if (_appendShape(node, shape, vBox, svgPath)) valid = true;
215 if (node->child.count > 0) {
216 auto child = node->child.data;
217 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
218 if (_appendChildShape(*child, shape, vBox, svgPath)) valid = true;
226 static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
229 /* Do not drop in Circular Dependency for ClipPath.
230 Composition can be applied recursively if its children nodes have composition target to this one. */
231 if (node->style->clipPath.applying) {
232 TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
234 auto compNode = node->style->clipPath.node;
235 if (compNode && compNode->child.count > 0) {
236 node->style->clipPath.applying = true;
238 auto comp = Shape::gen();
239 comp->fill(255, 255, 255, 255);
240 if (node->transform) comp->transform(*node->transform);
242 auto child = compNode->child.data;
243 auto valid = false; //Composite only when valid shapes are existed
245 for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
246 if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true;
249 if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);
251 node->style->clipPath.applying = false;
256 /* Do not drop in Circular Dependency for Mask.
257 Composition can be applied recursively if its children nodes have composition target to this one. */
258 if (node->style->mask.applying) {
259 TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
261 auto compNode = node->style->mask.node;
262 if (compNode && compNode->child.count > 0) {
263 node->style->mask.applying = true;
265 auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true);
267 if (node->transform) comp->transform(*node->transform);
268 paint->composite(move(comp), CompositeMethod::AlphaMask);
271 node->style->mask.applying = false;
277 static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath)
279 SvgStyleProperty* style = node->style;
281 if (node->transform) vg->transform(*node->transform);
282 if (node->type == SvgNodeType::Doc || !node->display) return;
284 //If fill property is nullptr then do nothing
285 if (style->fill.paint.none) {
287 } else if (style->fill.paint.gradient) {
289 if (!style->fill.paint.gradient->userSpace) {
290 vg->bounds(&bBox.x, &bBox.y, &bBox.w, &bBox.h, false);
291 //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
292 //a stroke width should be ignored for bounding box calculations
293 if (auto strokeW = vg->strokeWidth()) {
294 bBox.x += 0.5f * strokeW;
295 bBox.y += 0.5f * strokeW;
301 if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
302 auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
303 vg->fill(move(linear));
304 } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
305 auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
306 vg->fill(move(radial));
308 } else if (style->fill.paint.url) {
309 //TODO: Apply the color pointed by url
310 } else if (style->fill.paint.curColor) {
311 //Apply the current style color
312 vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
314 //Apply the fill color
315 vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
318 //Apply the fill rule
319 vg->fill((tvg::FillRule)style->fill.fillRule);
322 if (style->opacity < 255) vg->opacity(style->opacity);
324 if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
326 //Apply the stroke style property
327 vg->stroke(style->stroke.width);
328 vg->stroke(style->stroke.cap);
329 vg->stroke(style->stroke.join);
330 if (style->stroke.dash.array.count > 0) {
331 vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
334 //If stroke property is nullptr then do nothing
335 if (style->stroke.paint.none) {
337 } else if (style->stroke.paint.gradient) {
339 if (!style->stroke.paint.gradient->userSpace) {
340 //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
341 //a stroke width should be ignored for bounding box calculations
342 vg->bounds(&bBox.x, &bBox.y, &bBox.w, &bBox.h, false);
343 if (auto strokeW = vg->strokeWidth()) {
344 bBox.x += 0.5f * strokeW;
345 bBox.y += 0.5f * strokeW;
351 if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
352 auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
353 vg->stroke(move(linear));
354 } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
355 auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
356 vg->stroke(move(radial));
358 } else if (style->stroke.paint.url) {
359 //TODO: Apply the color pointed by url
360 } else if (style->stroke.paint.curColor) {
361 //Apply the current style color
362 vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
364 //Apply the stroke color
365 vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
368 _applyComposition(vg, node, vBox, svgPath);
372 static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
374 auto shape = Shape::gen();
375 if (_appendShape(node, shape.get(), vBox, svgPath)) return shape;
380 static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
382 Array<PathCommand> cmds;
385 switch (node->type) {
386 case SvgNodeType::Path: {
387 if (node->node.path.path) {
388 if (svgPathToTvgPath(node->node.path.path, cmds, pts)) {
389 shape->appendPath(cmds.data, cmds.count, pts.data, pts.count);
394 case SvgNodeType::Ellipse: {
395 shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
398 case SvgNodeType::Polygon: {
399 if (node->node.polygon.pointsCount < 2) break;
400 shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
401 for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
402 shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
407 case SvgNodeType::Polyline: {
408 if (node->node.polygon.pointsCount < 2) break;
409 shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
410 for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
411 shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
415 case SvgNodeType::Circle: {
416 shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
419 case SvgNodeType::Rect: {
420 shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
423 case SvgNodeType::Line: {
424 shape->moveTo(node->node.line.x1, node->node.line.y1);
425 shape->lineTo(node->node.line.x2, node->node.line.y2);
433 _applyProperty(node, shape, vBox, svgPath);
438 enum class imageMimeTypeEncoding
443 constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
444 return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
446 constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
447 return (static_cast<int>(a) & static_cast<int>(b));
451 static constexpr struct
455 imageMimeTypeEncoding encoding;
456 } imageMimeTypes[] = {
457 {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
458 {"png", sizeof("png"), imageMimeTypeEncoding::base64},
459 {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
463 static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
464 if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
465 *href += sizeof("image/") - 1;
467 //RFC2397 data:[<mediatype>][;base64],<data>
468 //mediatype := [ type "/" subtype ] *( ";" parameter )
469 //parameter := attribute "=" value
470 for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
471 if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
472 *href += imageMimeTypes[i].sz - 1;
473 *mimetype = imageMimeTypes[i].name;
475 while (**href && **href != ',') {
476 while (**href && **href != ';') ++(*href);
477 if (!**href) return false;
480 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
481 if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
482 *href += sizeof("base64,") - 1;
483 *encoding = imageMimeTypeEncoding::base64;
484 return true; //valid base64
487 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
488 if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
489 *href += sizeof("utf8,") - 1;
490 *encoding = imageMimeTypeEncoding::utf8;
491 return true; //valid utf8
495 //no encoding defined
496 if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
498 *encoding = imageMimeTypeEncoding::utf8;
499 return true; //allow no encoding defined if utf8 expected
508 static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
510 if (!node->node.image.href) return nullptr;
511 auto picture = Picture::gen();
513 const char* href = node->node.image.href;
514 if (!strncmp(href, "data:", sizeof("data:") - 1)) {
515 href += sizeof("data:") - 1;
516 const char* mimetype;
517 imageMimeTypeEncoding encoding;
518 if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
519 if (encoding == imageMimeTypeEncoding::base64) {
520 string decoded = svgUtilBase64Decode(href);
521 if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
523 string decoded = svgUtilURLDecode(href);
524 if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
527 if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
528 //TODO: protect against recursive svg image loading
529 //Temporarily disable embedded svg:
530 const char *dot = strrchr(href, '.');
531 if (dot && !strcmp(dot, ".svg")) {
532 TVGLOG("SVG", "Embedded svg file is disabled.");
535 string imagePath = href;
536 if (strncmp(href, "/", 1)) {
537 auto last = svgPath.find_last_of("/");
538 imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath;
540 if (picture->load(imagePath) != Result::Success) return nullptr;
544 if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
545 auto sx = node->node.image.w / w;
546 auto sy = node->node.image.h / h;
547 Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
548 picture->transform(m);
551 _applyComposition(picture.get(), node, vBox, svgPath);
556 static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath)
558 auto scene = _sceneBuildHelper(node, vBox, svgPath, false);
559 if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
560 scene->translate(node->node.use.x, node->node.use.y);
562 if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
563 //TODO: handle width/height properties
569 static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask)
571 if (_isGroupType(node->type) || mask) {
572 auto scene = Scene::gen();
573 if (!mask && node->transform) scene->transform(*node->transform);
575 if (node->display && node->style->opacity != 0) {
576 auto child = node->child.data;
577 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
578 if (_isGroupType((*child)->type)) {
579 if ((*child)->type == SvgNodeType::Use)
580 scene->push(_useBuildHelper(*child, vBox, svgPath));
582 scene->push(_sceneBuildHelper(*child, vBox, svgPath, false));
583 } else if ((*child)->type == SvgNodeType::Image) {
584 auto image = _imageBuildHelper(*child, vBox, svgPath);
585 if (image) scene->push(move(image));
586 } else if ((*child)->type != SvgNodeType::Mask) {
587 auto shape = _shapeBuildHelper(*child, vBox, svgPath);
588 if (shape) scene->push(move(shape));
591 _applyComposition(scene.get(), node, vBox, svgPath);
592 scene->opacity(node->style->opacity);
600 /************************************************************************/
601 /* External Class Implementation */
602 /************************************************************************/
604 unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
606 if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
608 Box vBox = {vx, vy, vw, vh};
609 auto docNode = _sceneBuildHelper(node, vBox, svgPath, false);
611 if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
615 if (preserveAspect) {
617 auto scale = sx < sy ? sx : sy;
618 docNode->scale(scale);
620 auto tvx = vx * scale;
621 auto tvy = vy * scale;
622 auto tvw = vw * scale;
623 auto tvh = vh * scale;
624 if (vw > vh) tvy -= (h - tvh) * 0.5f;
625 else tvx -= (w - tvw) * 0.5f;
626 docNode->translate(-tvx, -tvy);
633 if (tvw > tvh) tvy -= (h - tvh) * 0.5f;
634 else tvx -= (w - tvw) * 0.5f;
635 Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
636 docNode->transform(m);
638 } else if (!mathZero(vx) || !mathZero(vy)) {
639 docNode->translate(-vx, -vy);
642 auto viewBoxClip = Shape::gen();
643 viewBoxClip->appendRect(0, 0, w, h, 0, 0);
644 viewBoxClip->fill(0, 0, 0, 255);
646 auto compositeLayer = Scene::gen();
647 compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
648 compositeLayer->push(move(docNode));
650 auto root = Scene::gen();
651 root->push(move(compositeLayer));