svg_loader: struct used to pass 1 args instead of 4 of them
[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 /************************************************************************/
60 /* Internal Class Implementation                                        */
61 /************************************************************************/
62
63 struct Box
64 {
65     float x, y, w, h;
66 };
67
68
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);
71
72
73 static inline bool _isGroupType(SvgNodeType type)
74 {
75     if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true;
76     return false;
77 }
78
79
80 static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
81 {
82     gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
83     gradTransf->e12 *= mBBox->e11;
84     gradTransf->e11 *= mBBox->e11;
85
86     gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
87     gradTransf->e22 *= mBBox->e22;
88     gradTransf->e21 *= mBBox->e22;
89 }
90
91
92 static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
93 {
94     Fill::ColorStop* stops;
95     int stopCount = 0;
96     auto fillGrad = LinearGradient::gen();
97
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;
101
102     if (g->userSpace) {
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;
107     } else {
108         Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
109         if (isTransform) _transformMultiply(&m, &finalTransform);
110         else {
111             finalTransform = m;
112             isTransform = true;
113         }
114     }
115
116     if (isTransform) fillGrad->transform(finalTransform);
117
118     fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
119     fillGrad->spread(g->spread);
120
121     //Update the stops
122     stopCount = g->stops.count;
123     if (stopCount > 0) {
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;
139         }
140         fillGrad->colorStops(stops, stopCount);
141         free(stops);
142     }
143     return fillGrad;
144 }
145
146
147 static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
148 {
149     Fill::ColorStop *stops;
150     int stopCount = 0;
151     auto fillGrad = RadialGradient::gen();
152
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;
156
157     if (g->userSpace) {
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;
165     } else {
166         Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
167         if (isTransform) _transformMultiply(&m, &finalTransform);
168         else {
169             finalTransform = m;
170             isTransform = true;
171         }
172     }
173
174     if (isTransform) fillGrad->transform(finalTransform);
175
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);
179     //}
180     fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
181     fillGrad->spread(g->spread);
182
183     //Update the stops
184     stopCount = g->stops.count;
185     if (stopCount > 0) {
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;
201         }
202         fillGrad->colorStops(stops, stopCount);
203         free(stops);
204     }
205     return fillGrad;
206 }
207
208
209 static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
210 {
211     auto valid = false;
212
213     if (_appendShape(node, shape, vBox, svgPath)) valid = true;
214
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;
219         }
220     }
221
222     return valid;
223 }
224
225
226 static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
227 {
228     /* ClipPath */
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?");
233     } else {
234         auto compNode = node->style->clipPath.node;
235         if (compNode && compNode->child.count > 0) {
236             node->style->clipPath.applying = true;
237
238             auto comp = Shape::gen();
239             comp->fill(255, 255, 255, 255);
240             if (node->transform) comp->transform(*node->transform);
241
242             auto child = compNode->child.data;
243             auto valid = false; //Composite only when valid shapes are existed
244
245             for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
246                 if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true;
247             }
248
249             if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);
250
251             node->style->clipPath.applying = false;
252         }
253     }
254
255     /* Mask */
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?");
260     } else  {
261         auto compNode = node->style->mask.node;
262         if (compNode && compNode->child.count > 0) {
263             node->style->mask.applying = true;
264
265             auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true);
266             if (comp) {
267                 if (node->transform) comp->transform(*node->transform);
268                 paint->composite(move(comp), CompositeMethod::AlphaMask);
269             }
270
271             node->style->mask.applying = false;
272         }
273     }
274 }
275
276
277 static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath)
278 {
279     SvgStyleProperty* style = node->style;
280
281     if (node->transform) vg->transform(*node->transform);
282     if (node->type == SvgNodeType::Doc || !node->display) return;
283
284     //If fill property is nullptr then do nothing
285     if (style->fill.paint.none) {
286         //Do nothing
287     } else if (style->fill.paint.gradient) {
288         Box bBox = vBox;
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;
296                 bBox.w -= strokeW;
297                 bBox.h -= strokeW;
298             }
299         }
300
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));
307         }
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);
313     } else {
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);
316     }
317
318     //Apply the fill rule
319     vg->fill((tvg::FillRule)style->fill.fillRule);
320
321     //Apply node opacity
322     if (style->opacity < 255) vg->opacity(style->opacity);
323
324     if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
325
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);
332     }
333
334     //If stroke property is nullptr then do nothing
335     if (style->stroke.paint.none) {
336         //Do nothing
337     } else if (style->stroke.paint.gradient) {
338         Box bBox = vBox;
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;
346                 bBox.w -= strokeW;
347                 bBox.h -= strokeW;
348             }
349         }
350
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));
357         }
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);
363     } else {
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);
366     }
367
368     _applyComposition(vg, node, vBox, svgPath);
369 }
370
371
372 static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
373 {
374     auto shape = Shape::gen();
375     if (_appendShape(node, shape.get(), vBox, svgPath)) return shape;
376     else return nullptr;
377 }
378
379
380 static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
381 {
382     Array<PathCommand> cmds;
383     Array<Point> pts;
384
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);
390                 }
391             }
392             break;
393         }
394         case SvgNodeType::Ellipse: {
395             shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
396             break;
397         }
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]);
403             }
404             shape->close();
405             break;
406         }
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]);
412             }
413             break;
414         }
415         case SvgNodeType::Circle: {
416             shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
417             break;
418         }
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);
421             break;
422         }
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);
426             break;
427         }
428         default: {
429             return false;
430         }
431     }
432
433     _applyProperty(node, shape, vBox, svgPath);
434     return true;
435 }
436
437
438 enum class imageMimeTypeEncoding
439 {
440     base64 = 0x1,
441     utf8 = 0x2
442 };
443 constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
444     return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
445 }
446 constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
447     return (static_cast<int>(a) & static_cast<int>(b));
448 }
449
450
451 static constexpr struct
452 {
453     const char* name;
454     int sz;
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},
460 };
461
462
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;
466
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;
474
475             while (**href && **href != ',') {
476                 while (**href && **href != ';') ++(*href);
477                 if (!**href) return false;
478                 ++(*href);
479
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
485                     }
486                 }
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
492                     }
493                 }
494             }
495             //no encoding defined
496             if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
497                 ++(*href);
498                 *encoding = imageMimeTypeEncoding::utf8;
499                 return true; //allow no encoding defined if utf8 expected
500             }
501             return false;
502         }
503     }
504     return false;
505 }
506
507
508 static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
509 {
510     if (!node->node.image.href) return nullptr;
511     auto picture = Picture::gen();
512
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;
522         } else {
523             string decoded = svgUtilURLDecode(href);
524             if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
525         }
526     } else {
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.");
533             return nullptr;
534         }
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;
539         }
540         if (picture->load(imagePath) != Result::Success) return nullptr;
541     }
542
543     float w, h;
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);
549     }
550
551     _applyComposition(picture.get(), node, vBox, svgPath);
552     return picture;
553 }
554
555
556 static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath)
557 {
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);
561     }
562     if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
563         //TODO: handle width/height properties
564     }
565     return scene;
566 }
567
568
569 static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask)
570 {
571     if (_isGroupType(node->type) || mask) {
572         auto scene = Scene::gen();
573         if (!mask && node->transform) scene->transform(*node->transform);
574
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));
581                     else
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));
589                 }
590             }
591             _applyComposition(scene.get(), node, vBox, svgPath);
592             scene->opacity(node->style->opacity);
593         }
594         return scene;
595     }
596     return nullptr;
597 }
598
599
600 /************************************************************************/
601 /* External Class Implementation                                        */
602 /************************************************************************/
603
604 unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
605 {
606     if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
607
608     Box vBox = {vx, vy, vw, vh};
609     auto docNode = _sceneBuildHelper(node, vBox, svgPath, false);
610
611     if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
612         auto sx = w / vw;
613         auto sy = h / vh;
614
615         if (preserveAspect) {
616             //Scale
617             auto scale = sx < sy ? sx : sy;
618             docNode->scale(scale);
619             //Align
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);
627         } else {
628             //Align
629             auto tvx = vx * sx;
630             auto tvy = vy * sy;
631             auto tvw = vw * sx;
632             auto tvh = vh * sy;
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);
637         }
638     } else if (!mathZero(vx) || !mathZero(vy)) {
639         docNode->translate(-vx, -vy);
640     }
641
642     auto viewBoxClip = Shape::gen();
643     viewBoxClip->appendRect(0, 0, w, h, 0, 0);
644     viewBoxClip->fill(0, 0, 0, 255);
645
646     auto compositeLayer = Scene::gen();
647     compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
648     compositeLayer->push(move(docNode));
649
650     auto root = Scene::gen();
651     root->push(move(compositeLayer));
652
653     return root;
654 }