sw_engine raster: fix the texmap regression bug.
[platform/core/graphics/tizenvg.git] / src / loaders / svg / tvgSvgSceneBuilder.cpp
1 /*
2  * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
3
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:
10
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13
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
20  * SOFTWARE.
21  */
22
23 /*
24  * Copyright notice for the EFL:
25
26  * Copyright (C) EFL developers (see AUTHORS)
27
28  * All rights reserved.
29
30  * Redistribution and use in source and binary forms, with or without
31  * modification, are permitted provided that the following conditions are met:
32
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.
38
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.
49 */
50
51
52 #include <string>
53 #include "tvgMath.h"
54 #include "tvgSvgLoaderCommon.h"
55 #include "tvgSvgSceneBuilder.h"
56 #include "tvgSvgPath.h"
57 #include "tvgSvgUtil.h"
58
59 static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh, const string& svgPath);
60 static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath, bool mask);
61
62 /************************************************************************/
63 /* Internal Class Implementation                                        */
64 /************************************************************************/
65
66 static inline bool _isGroupType(SvgNodeType type)
67 {
68     if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true;
69     return false;
70 }
71
72
73 static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
74 {
75     gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
76     gradTransf->e12 *= mBBox->e11;
77     gradTransf->e11 *= mBBox->e11;
78
79     gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
80     gradTransf->e22 *= mBBox->e22;
81     gradTransf->e21 *= mBBox->e22;
82 }
83
84
85 static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, float rx, float ry, float rw, float rh, int opacity)
86 {
87     Fill::ColorStop* stops;
88     int stopCount = 0;
89     auto fillGrad = LinearGradient::gen();
90
91     bool isTransform = (g->transform ? true : false);
92     Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
93     if (isTransform) finalTransform = *g->transform;
94
95     if (g->userSpace) {
96         g->linear->x1 = g->linear->x1 * rw;
97         g->linear->y1 = g->linear->y1 * rh;
98         g->linear->x2 = g->linear->x2 * rw;
99         g->linear->y2 = g->linear->y2 * rh;
100     } else {
101         Matrix m = {rw, 0, rx, 0, rh, ry, 0, 0, 1};
102         if (isTransform) _transformMultiply(&m, &finalTransform);
103         else {
104             finalTransform = m;
105             isTransform = true;
106         }
107     }
108
109     if (isTransform) fillGrad->transform(finalTransform);
110
111     fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
112     fillGrad->spread(g->spread);
113
114     //Update the stops
115     stopCount = g->stops.count;
116     if (stopCount > 0) {
117         stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
118         if (!stops) return fillGrad;
119         auto prevOffset = 0.0f;
120         for (uint32_t i = 0; i < g->stops.count; ++i) {
121             auto colorStop = &g->stops.data[i];
122             //Use premultiplied color
123             stops[i].r = colorStop->r;
124             stops[i].g = colorStop->g;
125             stops[i].b = colorStop->b;
126             stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
127             stops[i].offset = colorStop->offset;
128             //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
129             if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
130             else if (colorStop->offset > 1) stops[i].offset = 1;
131             prevOffset = stops[i].offset;
132         }
133         fillGrad->colorStops(stops, stopCount);
134         free(stops);
135     }
136     return fillGrad;
137 }
138
139
140 static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, float rx, float ry, float rw, float rh, int opacity)
141 {
142     Fill::ColorStop *stops;
143     int stopCount = 0;
144     auto fillGrad = RadialGradient::gen();
145
146     bool isTransform = (g->transform ? true : false);
147     Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
148     if (isTransform) finalTransform = *g->transform;
149
150     if (g->userSpace) {
151         //The radius scalling is done according to the Units section:
152         //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
153         g->radial->cx = g->radial->cx * rw;
154         g->radial->cy = g->radial->cy * rh;
155         g->radial->r = g->radial->r * sqrtf(powf(rw, 2.0f) + powf(rh, 2.0f)) / sqrtf(2.0f);
156         g->radial->fx = g->radial->fx * rw;
157         g->radial->fy = g->radial->fy * rh;
158     } else {
159         Matrix m = {rw, 0, rx, 0, rh, ry, 0, 0, 1};
160         if (isTransform) _transformMultiply(&m, &finalTransform);
161         else {
162             finalTransform = m;
163             isTransform = true;
164         }
165     }
166
167     if (isTransform) fillGrad->transform(finalTransform);
168
169     //TODO: Tvg is not support to focal
170     //if (g->radial->fx != 0 && g->radial->fy != 0) {
171     //    fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
172     //}
173     fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
174     fillGrad->spread(g->spread);
175
176     //Update the stops
177     stopCount = g->stops.count;
178     if (stopCount > 0) {
179         stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
180         if (!stops) return fillGrad;
181         auto prevOffset = 0.0f;
182         for (uint32_t i = 0; i < g->stops.count; ++i) {
183             auto colorStop = &g->stops.data[i];
184             //Use premultiplied color
185             stops[i].r = colorStop->r;
186             stops[i].g = colorStop->g;
187             stops[i].b = colorStop->b;
188             stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
189             stops[i].offset = colorStop->offset;
190             //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
191             if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
192             else if (colorStop->offset > 1) stops[i].offset = 1;
193             prevOffset = stops[i].offset;
194         }
195         fillGrad->colorStops(stops, stopCount);
196         free(stops);
197     }
198     return fillGrad;
199 }
200
201
202 static bool _appendChildShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh, const string& svgPath)
203 {
204     auto valid = false;
205
206     if (_appendShape(node, shape, vx, vy, vw, vh, svgPath)) valid = true;
207
208     if (node->child.count > 0) {
209         auto child = node->child.data;
210         for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
211             if (_appendChildShape(*child, shape, vx, vy, vw, vh, svgPath)) valid = true;
212         }
213     }
214
215     return valid;
216 }
217
218
219 static void _applyComposition(Paint* paint, const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
220 {
221     /* ClipPath */
222     /* Do not drop in Circular Dependency for ClipPath.
223        Composition can be applied recursively if its children nodes have composition target to this one. */
224     if (node->style->clipPath.applying) {
225         TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
226     } else {
227         auto compNode = node->style->clipPath.node;
228         if (compNode && compNode->child.count > 0) {
229             node->style->clipPath.applying = true;
230
231             auto comp = Shape::gen();
232             comp->fill(255, 255, 255, 255);
233             if (node->transform) comp->transform(*node->transform);
234
235             auto child = compNode->child.data;
236             auto valid = false; //Composite only when valid shapes are existed
237
238             for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
239                 if (_appendChildShape(*child, comp.get(), vx, vy, vw, vh, svgPath)) valid = true;
240             }
241
242             if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);
243
244             node->style->clipPath.applying = false;
245         }
246     }
247
248     /* Mask */
249     /* Do not drop in Circular Dependency for Mask.
250        Composition can be applied recursively if its children nodes have composition target to this one. */
251     if (node->style->mask.applying) {
252         TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
253     } else  {
254         auto compNode = node->style->mask.node;
255         if (compNode && compNode->child.count > 0) {
256             node->style->mask.applying = true;
257
258             auto comp = _sceneBuildHelper(compNode, vx, vy, vw, vh, svgPath, true);
259             if (comp) {
260                 if (node->transform) comp->transform(*node->transform);
261                 paint->composite(move(comp), CompositeMethod::AlphaMask);
262             }
263
264             node->style->mask.applying = false;
265         }
266     }
267 }
268
269
270 static void _applyProperty(SvgNode* node, Shape* vg, float vx, float vy, float vw, float vh, const string& svgPath)
271 {
272     SvgStyleProperty* style = node->style;
273
274     if (node->transform) vg->transform(*node->transform);
275     if (node->type == SvgNodeType::Doc || !node->display) return;
276
277     //If fill property is nullptr then do nothing
278     if (style->fill.paint.none) {
279         //Do nothing
280     } else if (style->fill.paint.gradient) {
281         if (!style->fill.paint.gradient->userSpace) {
282             vg->bounds(&vx, &vy, &vw, &vh, false);
283             //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
284             //a stroke width should be ignored for bounding box calculations
285             if (auto strokeW = vg->strokeWidth()) {
286                 vx += 0.5f * strokeW;
287                 vy += 0.5f * strokeW;
288                 vw -= strokeW;
289                 vh -= strokeW;
290             }
291         }
292
293         if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
294              auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh, style->fill.opacity);
295              vg->fill(move(linear));
296         } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
297              auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh, style->fill.opacity);
298              vg->fill(move(radial));
299         }
300     } else if (style->fill.paint.url) {
301         //TODO: Apply the color pointed by url
302     } else if (style->fill.paint.curColor) {
303         //Apply the current style color
304         vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
305     } else {
306         //Apply the fill color
307         vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
308     }
309
310     //Apply the fill rule
311     vg->fill((tvg::FillRule)style->fill.fillRule);
312
313     //Apply node opacity
314     if (style->opacity < 255) vg->opacity(style->opacity);
315
316     if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
317
318     //Apply the stroke style property
319     vg->stroke(style->stroke.width);
320     vg->stroke(style->stroke.cap);
321     vg->stroke(style->stroke.join);
322     if (style->stroke.dash.array.count > 0) {
323         vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
324     }
325
326     //If stroke property is nullptr then do nothing
327     if (style->stroke.paint.none) {
328         //Do nothing
329     } else if (style->stroke.paint.gradient) {
330         if (!style->stroke.paint.gradient->userSpace) {
331             //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
332             //a stroke width should be ignored for bounding box calculations
333             vg->bounds(&vx, &vy, &vw, &vh, false);
334             if (auto strokeW = vg->strokeWidth()) {
335                 vx += 0.5f * strokeW;
336                 vy += 0.5f * strokeW;
337                 vw -= strokeW;
338                 vh -= strokeW;
339             }
340         }
341
342         if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
343              auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, vx, vy, vw, vh, style->stroke.opacity);
344              vg->stroke(move(linear));
345         } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
346              auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, vx, vy, vw, vh, style->stroke.opacity);
347              vg->stroke(move(radial));
348         }
349     } else if (style->stroke.paint.url) {
350         //TODO: Apply the color pointed by url
351     } else if (style->stroke.paint.curColor) {
352         //Apply the current style color
353         vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
354     } else {
355         //Apply the stroke color
356         vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
357     }
358
359     _applyComposition(vg, node, vx, vy, vw, vh, svgPath);
360 }
361
362
363 static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
364 {
365     auto shape = Shape::gen();
366     if (_appendShape(node, shape.get(), vx, vy, vw, vh, svgPath)) return shape;
367     else return nullptr;
368 }
369
370
371 static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh, const string& svgPath)
372 {
373     Array<PathCommand> cmds;
374     Array<Point> pts;
375
376     switch (node->type) {
377         case SvgNodeType::Path: {
378             if (node->node.path.path) {
379                 if (svgPathToTvgPath(node->node.path.path, cmds, pts)) {
380                     shape->appendPath(cmds.data, cmds.count, pts.data, pts.count);
381                 }
382             }
383             break;
384         }
385         case SvgNodeType::Ellipse: {
386             shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
387             break;
388         }
389         case SvgNodeType::Polygon: {
390             if (node->node.polygon.pointsCount < 2) break;
391             shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
392             for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
393                 shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
394             }
395             shape->close();
396             break;
397         }
398         case SvgNodeType::Polyline: {
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]);
403             }
404             break;
405         }
406         case SvgNodeType::Circle: {
407             shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
408             break;
409         }
410         case SvgNodeType::Rect: {
411             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);
412             break;
413         }
414         case SvgNodeType::Line: {
415             shape->moveTo(node->node.line.x1, node->node.line.y1);
416             shape->lineTo(node->node.line.x2, node->node.line.y2);
417             break;
418         }
419         default: {
420             return false;
421         }
422     }
423
424     _applyProperty(node, shape, vx, vy, vw, vh, svgPath);
425     return true;
426 }
427
428
429 enum class imageMimeTypeEncoding
430 {
431     base64 = 0x1,
432     utf8 = 0x2
433 };
434 constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
435     return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
436 }
437 constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
438     return (static_cast<int>(a) & static_cast<int>(b));
439 }
440
441
442 static constexpr struct
443 {
444     const char* name;
445     int sz;
446     imageMimeTypeEncoding encoding;
447 } imageMimeTypes[] = {
448     {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
449     {"png", sizeof("png"), imageMimeTypeEncoding::base64},
450     {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
451 };
452
453
454 static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
455     if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
456     *href += sizeof("image/") - 1;
457
458     //RFC2397 data:[<mediatype>][;base64],<data>
459     //mediatype  := [ type "/" subtype ] *( ";" parameter )
460     //parameter  := attribute "=" value
461     for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
462         if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
463             *href += imageMimeTypes[i].sz  - 1;
464             *mimetype = imageMimeTypes[i].name;
465
466             while (**href && **href != ',') {
467                 while (**href && **href != ';') ++(*href);
468                 if (!**href) return false;
469                 ++(*href);
470
471                 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
472                     if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
473                         *href += sizeof("base64,") - 1;
474                         *encoding = imageMimeTypeEncoding::base64;
475                         return true; //valid base64
476                     }
477                 }
478                 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
479                     if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
480                         *href += sizeof("utf8,") - 1;
481                         *encoding = imageMimeTypeEncoding::utf8;
482                         return true; //valid utf8
483                     }
484                 }
485             }
486             //no encoding defined
487             if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
488                 ++(*href);
489                 *encoding = imageMimeTypeEncoding::utf8;
490                 return true; //allow no encoding defined if utf8 expected
491             }
492             return false;
493         }
494     }
495     return false;
496 }
497
498
499 static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
500 {
501     if (!node->node.image.href) return nullptr;
502     auto picture = Picture::gen();
503
504     const char* href = node->node.image.href;
505     if (!strncmp(href, "data:", sizeof("data:") - 1)) {
506         href += sizeof("data:") - 1;
507         const char* mimetype;
508         imageMimeTypeEncoding encoding;
509         if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
510         if (encoding == imageMimeTypeEncoding::base64) {
511             string decoded = svgUtilBase64Decode(href);
512             if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
513         } else {
514             string decoded = svgUtilURLDecode(href);
515             if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
516         }
517     } else {
518         if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
519         //TODO: protect against recursive svg image loading
520         //Temporarily disable embedded svg:
521         const char *dot = strrchr(href, '.');
522         if (dot && !strcmp(dot, ".svg")) {
523             TVGLOG("SVG", "Embedded svg file is disabled.");
524             return nullptr;
525         }
526         string imagePath = href;
527         if (strncmp(href, "/", 1)) {
528             auto last = svgPath.find_last_of("/");
529             imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath;
530         }
531         if (picture->load(imagePath) != Result::Success) return nullptr;
532     }
533
534     float w, h;
535     if (picture->size(&w, &h) == Result::Success && w  > 0 && h > 0) {
536         auto sx = node->node.image.w / w;
537         auto sy = node->node.image.h / h;
538         Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
539         picture->transform(m);
540     }
541
542     _applyComposition(picture.get(), node, vx, vy, vw, vh, svgPath);
543     return picture;
544 }
545
546
547 static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
548 {
549     auto scene = _sceneBuildHelper(node, vx, vy, vw, vh, svgPath, false);
550     if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
551         scene->translate(node->node.use.x, node->node.use.y);
552     }
553     if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
554         //TODO: handle width/height properties
555     }
556     return scene;
557 }
558
559
560 static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath, bool mask)
561 {
562     if (_isGroupType(node->type) || mask) {
563         auto scene = Scene::gen();
564         if (!mask && node->transform) scene->transform(*node->transform);
565
566         if (node->display && node->style->opacity != 0) {
567             auto child = node->child.data;
568             for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
569                 if (_isGroupType((*child)->type)) {
570                     if ((*child)->type == SvgNodeType::Use)
571                         scene->push(_useBuildHelper(*child, vx, vy, vw, vh, svgPath));
572                     else
573                         scene->push(_sceneBuildHelper(*child, vx, vy, vw, vh, svgPath, false));
574                 } else if ((*child)->type == SvgNodeType::Image) {
575                     auto image = _imageBuildHelper(*child, vx, vy, vw, vh, svgPath);
576                     if (image) scene->push(move(image));
577                 } else if ((*child)->type != SvgNodeType::Mask) {
578                     auto shape = _shapeBuildHelper(*child, vx, vy, vw, vh, svgPath);
579                     if (shape) scene->push(move(shape));
580                 }
581             }
582             _applyComposition(scene.get(), node, vx, vy, vw, vh, svgPath);
583             scene->opacity(node->style->opacity);
584         }
585         return scene;
586     }
587     return nullptr;
588 }
589
590
591 /************************************************************************/
592 /* External Class Implementation                                        */
593 /************************************************************************/
594
595 unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
596 {
597     if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
598
599     auto docNode = _sceneBuildHelper(node, vx, vy, vw, vh, svgPath, false);
600
601     if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
602         auto sx = w / vw;
603         auto sy = h / vh;
604
605         if (preserveAspect) {
606             //Scale
607             auto scale = sx < sy ? sx : sy;
608             docNode->scale(scale);
609             //Align
610             auto tvx = vx * scale;
611             auto tvy = vy * scale;
612             auto tvw = vw * scale;
613             auto tvh = vh * scale;
614             if (vw > vh) tvy -= (h - tvh) * 0.5f;
615             else  tvx -= (w - tvw) * 0.5f;
616             docNode->translate(-tvx, -tvy);
617         } else {
618             //Align
619             auto tvx = vx * sx;
620             auto tvy = vy * sy;
621             auto tvw = vw * sx;
622             auto tvh = vh * sy;
623             if (tvw > tvh) tvy -= (h - tvh) * 0.5f;
624             else tvx -= (w - tvw) * 0.5f;
625             Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
626             docNode->transform(m);
627         }
628     } else if (!mathZero(vx) || !mathZero(vy)) {
629         docNode->translate(-vx, -vy);
630     }
631
632     auto viewBoxClip = Shape::gen();
633     viewBoxClip->appendRect(0, 0, w, h, 0, 0);
634     viewBoxClip->fill(0, 0, 0, 255);
635
636     auto compositeLayer = Scene::gen();
637     compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
638     compositeLayer->push(move(docNode));
639
640     auto root = Scene::gen();
641     root->push(move(compositeLayer));
642
643     return root;
644 }