1/*
2 * Copyright (c) 2020 - 2023 the ThorVG project. 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 "tvgMath.h" /* to include math.h before cstring */
53#include <cstring>
54#include <string>
55#include "tvgSvgLoaderCommon.h"
56#include "tvgSvgSceneBuilder.h"
57#include "tvgSvgPath.h"
58#include "tvgSvgUtil.h"
59
60/************************************************************************/
61/* Internal Class Implementation */
62/************************************************************************/
63
64static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
65static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr);
66
67
68static inline bool _isGroupType(SvgNodeType type)
69{
70 if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol) return true;
71 return false;
72}
73
74
75//According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
76//a stroke width should be ignored for bounding box calculations
77static Box _boundingBox(const Shape* shape)
78{
79 float x, y, w, h;
80 shape->bounds(&x, &y, &w, &h, false);
81
82 if (auto strokeW = shape->strokeWidth()) {
83 x += 0.5f * strokeW;
84 y += 0.5f * strokeW;
85 w -= strokeW;
86 h -= strokeW;
87 }
88
89 return {x, y, w, h};
90}
91
92
93static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
94{
95 gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
96 gradTransf->e12 *= mBBox->e11;
97 gradTransf->e11 *= mBBox->e11;
98
99 gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
100 gradTransf->e22 *= mBBox->e22;
101 gradTransf->e21 *= mBBox->e22;
102}
103
104
105static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
106{
107 Fill::ColorStop* stops;
108 int stopCount = 0;
109 auto fillGrad = LinearGradient::gen();
110
111 bool isTransform = (g->transform ? true : false);
112 Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
113 if (isTransform) finalTransform = *g->transform;
114
115 if (g->userSpace) {
116 g->linear->x1 = g->linear->x1 * vBox.w;
117 g->linear->y1 = g->linear->y1 * vBox.h;
118 g->linear->x2 = g->linear->x2 * vBox.w;
119 g->linear->y2 = g->linear->y2 * vBox.h;
120 } else {
121 Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
122 if (isTransform) _transformMultiply(&m, &finalTransform);
123 else {
124 finalTransform = m;
125 isTransform = true;
126 }
127 }
128
129 if (isTransform) fillGrad->transform(finalTransform);
130
131 fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
132 fillGrad->spread(g->spread);
133
134 //Update the stops
135 stopCount = g->stops.count;
136 if (stopCount > 0) {
137 stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
138 if (!stops) return fillGrad;
139 auto prevOffset = 0.0f;
140 for (uint32_t i = 0; i < g->stops.count; ++i) {
141 auto colorStop = &g->stops.data[i];
142 //Use premultiplied color
143 stops[i].r = colorStop->r;
144 stops[i].g = colorStop->g;
145 stops[i].b = colorStop->b;
146 stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
147 stops[i].offset = colorStop->offset;
148 //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
149 if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
150 else if (colorStop->offset > 1) stops[i].offset = 1;
151 prevOffset = stops[i].offset;
152 }
153 fillGrad->colorStops(stops, stopCount);
154 free(stops);
155 }
156 return fillGrad;
157}
158
159
160static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
161{
162 Fill::ColorStop *stops;
163 int stopCount = 0;
164 auto fillGrad = RadialGradient::gen();
165
166 bool isTransform = (g->transform ? true : false);
167 Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
168 if (isTransform) finalTransform = *g->transform;
169
170 if (g->userSpace) {
171 //The radius scalling is done according to the Units section:
172 //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
173 g->radial->cx = g->radial->cx * vBox.w;
174 g->radial->cy = g->radial->cy * vBox.h;
175 g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
176 g->radial->fx = g->radial->fx * vBox.w;
177 g->radial->fy = g->radial->fy * vBox.h;
178 } else {
179 Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
180 if (isTransform) _transformMultiply(&m, &finalTransform);
181 else {
182 finalTransform = m;
183 isTransform = true;
184 }
185 }
186
187 if (isTransform) fillGrad->transform(finalTransform);
188
189 //TODO: Tvg is not support to focal
190 //if (g->radial->fx != 0 && g->radial->fy != 0) {
191 // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
192 //}
193 fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
194 fillGrad->spread(g->spread);
195
196 //Update the stops
197 stopCount = g->stops.count;
198 if (stopCount > 0) {
199 stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
200 if (!stops) return fillGrad;
201 auto prevOffset = 0.0f;
202 for (uint32_t i = 0; i < g->stops.count; ++i) {
203 auto colorStop = &g->stops.data[i];
204 //Use premultiplied color
205 stops[i].r = colorStop->r;
206 stops[i].g = colorStop->g;
207 stops[i].b = colorStop->b;
208 stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
209 stops[i].offset = colorStop->offset;
210 //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
211 if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
212 else if (colorStop->offset > 1) stops[i].offset = 1;
213 prevOffset = stops[i].offset;
214 }
215 fillGrad->colorStops(stops, stopCount);
216 free(stops);
217 }
218 return fillGrad;
219}
220
221
222static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
223{
224 auto valid = false;
225
226 if (_appendShape(node, shape, vBox, svgPath)) valid = true;
227
228 if (node->child.count > 0) {
229 auto child = node->child.data;
230 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
231 if (_appendChildShape(*child, shape, vBox, svgPath)) valid = true;
232 }
233 }
234
235 return valid;
236}
237
238
239static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
240{
241 /* ClipPath */
242 /* Do not drop in Circular Dependency for ClipPath.
243 Composition can be applied recursively if its children nodes have composition target to this one. */
244 if (node->style->clipPath.applying) {
245 TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
246 } else {
247 auto compNode = node->style->clipPath.node;
248 if (compNode && compNode->child.count > 0) {
249 node->style->clipPath.applying = true;
250
251 auto comp = Shape::gen();
252
253 auto child = compNode->child.data;
254 auto valid = false; //Composite only when valid shapes are existed
255
256 for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
257 if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true;
258 }
259
260 if (node->transform) {
261 auto m = comp->transform();
262 m = mathMultiply(node->transform, &m);
263 comp->transform(m);
264 }
265
266 if (valid) paint->composite(std::move(comp), CompositeMethod::ClipPath);
267
268 node->style->clipPath.applying = false;
269 }
270 }
271
272 /* Mask */
273 /* Do not drop in Circular Dependency for Mask.
274 Composition can be applied recursively if its children nodes have composition target to this one. */
275 if (node->style->mask.applying) {
276 TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
277 } else {
278 auto compNode = node->style->mask.node;
279 if (compNode && compNode->child.count > 0) {
280 node->style->mask.applying = true;
281
282 bool isMaskWhite = true;
283 auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true, 0, &isMaskWhite);
284 if (comp) {
285 if (node->transform) comp->transform(*node->transform);
286
287 if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) {
288 paint->composite(std::move(comp), CompositeMethod::LumaMask);
289 } else {
290 paint->composite(std::move(comp), CompositeMethod::AlphaMask);
291 }
292 }
293
294 node->style->mask.applying = false;
295 }
296 }
297}
298
299
300static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath)
301{
302 SvgStyleProperty* style = node->style;
303
304 if (node->transform) vg->transform(*node->transform);
305 if (node->type == SvgNodeType::Doc || !node->display) return;
306
307 //If fill property is nullptr then do nothing
308 if (style->fill.paint.none) {
309 //Do nothing
310 } else if (style->fill.paint.gradient) {
311 Box bBox = vBox;
312 if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
313
314 if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
315 auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
316 vg->fill(std::move(linear));
317 } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
318 auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
319 vg->fill(std::move(radial));
320 }
321 } else if (style->fill.paint.url) {
322 //TODO: Apply the color pointed by url
323 } else if (style->fill.paint.curColor) {
324 //Apply the current style color
325 vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
326 } else {
327 //Apply the fill color
328 vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
329 }
330
331 //Apply the fill rule
332 vg->fill((tvg::FillRule)style->fill.fillRule);
333 //Rendering order
334 vg->order(!style->paintOrder);
335
336 //Apply node opacity
337 if (style->opacity < 255) vg->opacity(style->opacity);
338
339 if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
340
341 //Apply the stroke style property
342 vg->stroke(style->stroke.width);
343 vg->stroke(style->stroke.cap);
344 vg->stroke(style->stroke.join);
345 vg->strokeMiterlimit(style->stroke.miterlimit);
346 if (style->stroke.dash.array.count > 0) {
347 vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
348 }
349
350 //If stroke property is nullptr then do nothing
351 if (style->stroke.paint.none) {
352 vg->stroke(0.0f);
353 } else if (style->stroke.paint.gradient) {
354 Box bBox = vBox;
355 if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
356
357 if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
358 auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
359 vg->stroke(std::move(linear));
360 } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
361 auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
362 vg->stroke(std::move(radial));
363 }
364 } else if (style->stroke.paint.url) {
365 //TODO: Apply the color pointed by url
366 } else if (style->stroke.paint.curColor) {
367 //Apply the current style color
368 vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
369 } else {
370 //Apply the stroke color
371 vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
372 }
373
374 _applyComposition(vg, node, vBox, svgPath);
375}
376
377
378static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
379{
380 auto shape = Shape::gen();
381 if (_appendShape(node, shape.get(), vBox, svgPath)) return shape;
382 else return nullptr;
383}
384
385
386static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
387{
388 Array<PathCommand> cmds;
389 Array<Point> pts;
390
391 switch (node->type) {
392 case SvgNodeType::Path: {
393 if (node->node.path.path) {
394 if (svgPathToTvgPath(node->node.path.path, cmds, pts)) {
395 shape->appendPath(cmds.data, cmds.count, pts.data, pts.count);
396 }
397 }
398 break;
399 }
400 case SvgNodeType::Ellipse: {
401 shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
402 break;
403 }
404 case SvgNodeType::Polygon: {
405 if (node->node.polygon.pts.count < 2) break;
406 auto pts = node->node.polygon.pts.data;
407 shape->moveTo(pts[0], pts[1]);
408 for (pts += 2; pts < node->node.polygon.pts.end(); pts += 2) {
409 shape->lineTo(pts[0], pts[1]);
410 }
411 shape->close();
412 break;
413 }
414 case SvgNodeType::Polyline: {
415 if (node->node.polyline.pts.count < 2) break;
416 auto pts = node->node.polyline.pts.data;
417 shape->moveTo(pts[0], pts[1]);
418 for (pts += 2; pts < node->node.polyline.pts.end(); pts += 2) {
419 shape->lineTo(pts[0], pts[1]);
420 }
421 break;
422 }
423 case SvgNodeType::Circle: {
424 shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
425 break;
426 }
427 case SvgNodeType::Rect: {
428 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);
429 break;
430 }
431 case SvgNodeType::Line: {
432 shape->moveTo(node->node.line.x1, node->node.line.y1);
433 shape->lineTo(node->node.line.x2, node->node.line.y2);
434 break;
435 }
436 default: {
437 return false;
438 }
439 }
440
441 _applyProperty(node, shape, vBox, svgPath);
442 return true;
443}
444
445
446enum class imageMimeTypeEncoding
447{
448 base64 = 0x1,
449 utf8 = 0x2
450};
451
452constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
453 return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
454}
455
456constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
457 return (static_cast<int>(a) & static_cast<int>(b));
458}
459
460
461static constexpr struct
462{
463 const char* name;
464 int sz;
465 imageMimeTypeEncoding encoding;
466} imageMimeTypes[] = {
467 {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
468 {"png", sizeof("png"), imageMimeTypeEncoding::base64},
469 {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
470};
471
472
473static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
474 if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
475 *href += sizeof("image/") - 1;
476
477 //RFC2397 data:[<mediatype>][;base64],<data>
478 //mediatype := [ type "/" subtype ] *( ";" parameter )
479 //parameter := attribute "=" value
480 for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
481 if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
482 *href += imageMimeTypes[i].sz - 1;
483 *mimetype = imageMimeTypes[i].name;
484
485 while (**href && **href != ',') {
486 while (**href && **href != ';') ++(*href);
487 if (!**href) return false;
488 ++(*href);
489
490 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
491 if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
492 *href += sizeof("base64,") - 1;
493 *encoding = imageMimeTypeEncoding::base64;
494 return true; //valid base64
495 }
496 }
497 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
498 if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
499 *href += sizeof("utf8,") - 1;
500 *encoding = imageMimeTypeEncoding::utf8;
501 return true; //valid utf8
502 }
503 }
504 }
505 //no encoding defined
506 if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
507 ++(*href);
508 *encoding = imageMimeTypeEncoding::utf8;
509 return true; //allow no encoding defined if utf8 expected
510 }
511 return false;
512 }
513 }
514 return false;
515}
516
517
518static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
519{
520 if (!node->node.image.href) return nullptr;
521 auto picture = Picture::gen();
522
523 const char* href = node->node.image.href;
524 if (!strncmp(href, "data:", sizeof("data:") - 1)) {
525 href += sizeof("data:") - 1;
526 const char* mimetype;
527 imageMimeTypeEncoding encoding;
528 if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
529 if (encoding == imageMimeTypeEncoding::base64) {
530 string decoded = svgUtilBase64Decode(href);
531 if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
532 } else {
533 string decoded = svgUtilURLDecode(href);
534 if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
535 }
536 } else {
537 if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
538 //TODO: protect against recursive svg image loading
539 //Temporarily disable embedded svg:
540 const char *dot = strrchr(href, '.');
541 if (dot && !strcmp(dot, ".svg")) {
542 TVGLOG("SVG", "Embedded svg file is disabled.");
543 return nullptr;
544 }
545 string imagePath = href;
546 if (strncmp(href, "/", 1)) {
547 auto last = svgPath.find_last_of("/");
548 imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath;
549 }
550 if (picture->load(imagePath) != Result::Success) return nullptr;
551 }
552
553 float w, h;
554 Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
555 if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
556 auto sx = node->node.image.w / w;
557 auto sy = node->node.image.h / h;
558 m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
559 }
560 if (node->transform) m = mathMultiply(node->transform, &m);
561 picture->transform(m);
562
563 _applyComposition(picture.get(), node, vBox, svgPath);
564 return picture;
565}
566
567
568static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, float width, float height, const Box& box)
569{
570 auto sx = width / box.w;
571 auto sy = height / box.h;
572 auto tvx = box.x * sx;
573 auto tvy = box.y * sy;
574
575 if (align == AspectRatioAlign::None)
576 return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
577
578 //Scale
579 if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
580 if (sx < sy) sy = sx;
581 else sx = sy;
582 } else {
583 if (sx < sy) sx = sy;
584 else sy = sx;
585 }
586
587 //Align
588 tvx = box.x * sx;
589 tvy = box.y * sy;
590 auto tvw = box.w * sx;
591 auto tvh = box.h * sy;
592
593 switch (align) {
594 case AspectRatioAlign::XMinYMin: {
595 break;
596 }
597 case AspectRatioAlign::XMidYMin: {
598 tvx -= (width - tvw) * 0.5f;
599 break;
600 }
601 case AspectRatioAlign::XMaxYMin: {
602 tvx -= width - tvw;
603 break;
604 }
605 case AspectRatioAlign::XMinYMid: {
606 tvy -= (height - tvh) * 0.5f;
607 break;
608 }
609 case AspectRatioAlign::XMidYMid: {
610 tvx -= (width - tvw) * 0.5f;
611 tvy -= (height - tvh) * 0.5f;
612 break;
613 }
614 case AspectRatioAlign::XMaxYMid: {
615 tvx -= width - tvw;
616 tvy -= (height - tvh) * 0.5f;
617 break;
618 }
619 case AspectRatioAlign::XMinYMax: {
620 tvy -= height - tvh;
621 break;
622 }
623 case AspectRatioAlign::XMidYMax: {
624 tvx -= (width - tvw) * 0.5f;
625 tvy -= height - tvh;
626 break;
627 }
628 case AspectRatioAlign::XMaxYMax: {
629 tvx -= width - tvw;
630 tvy -= height - tvh;
631 break;
632 }
633 default: {
634 break;
635 }
636 }
637
638 return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
639}
640
641
642static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
643{
644 unique_ptr<Scene> finalScene;
645 auto scene = _sceneBuildHelper(node, vBox, svgPath, false, depth + 1, isMaskWhite);
646
647 // mUseTransform = mUseTransform * mTranslate
648 Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
649 if (node->transform) mUseTransform = *node->transform;
650 if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
651 Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
652 mUseTransform = mathMultiply(&mUseTransform, &mTranslate);
653 }
654
655 if (node->node.use.symbol) {
656 auto symbol = node->node.use.symbol->node.symbol;
657
658 auto width = (symbol.hasWidth ? symbol.w : vBox.w);
659 if (node->node.use.isWidthSet) width = node->node.use.w;
660 auto height = (symbol.hasHeight ? symbol.h : vBox.h);;
661 if (node->node.use.isHeightSet) height = node->node.use.h;
662 auto vw = (symbol.hasViewBox ? symbol.vw : width);
663 auto vh = (symbol.hasViewBox ? symbol.vh : height);
664
665 Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
666 if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
667 Box box = {symbol.vx, symbol.vy, vw, vh};
668 mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
669 } else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
670 mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
671 }
672
673 // mSceneTransform = mUseTransform * mSymbolTransform * mViewBox
674 Matrix mSceneTransform = mViewBox;
675 if (node->node.use.symbol->transform) {
676 mSceneTransform = mathMultiply(node->node.use.symbol->transform, &mViewBox);
677 }
678 mSceneTransform = mathMultiply(&mUseTransform, &mSceneTransform);
679 scene->transform(mSceneTransform);
680
681 if (node->node.use.symbol->node.symbol.overflowVisible) {
682 finalScene = std::move(scene);
683 } else {
684 auto viewBoxClip = Shape::gen();
685 viewBoxClip->appendRect(0, 0, width, height, 0, 0);
686
687 // mClipTransform = mUseTransform * mSymbolTransform
688 Matrix mClipTransform = mUseTransform;
689 if (node->node.use.symbol->transform) {
690 mClipTransform = mathMultiply(&mUseTransform, node->node.use.symbol->transform);
691 }
692 viewBoxClip->transform(mClipTransform);
693
694 auto compositeLayer = Scene::gen();
695 compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
696 compositeLayer->push(std::move(scene));
697
698 auto root = Scene::gen();
699 root->push(std::move(compositeLayer));
700
701 finalScene = std::move(root);
702 }
703 } else {
704 if (!mathIdentity((const Matrix*)(&mUseTransform))) scene->transform(mUseTransform);
705 finalScene = std::move(scene);
706 }
707
708 return finalScene;
709}
710
711
712static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
713{
714 /* Exception handling: Prevent invalid SVG data input.
715 The size is the arbitrary value, we need an experimental size. */
716 if (depth > 2192) {
717 TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth);
718 return nullptr;
719 }
720
721 if (_isGroupType(node->type) || mask) {
722 auto scene = Scene::gen();
723 // For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
724 if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform);
725
726 if (node->display && node->style->opacity != 0) {
727 auto child = node->child.data;
728 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
729 if (_isGroupType((*child)->type)) {
730 if ((*child)->type == SvgNodeType::Use)
731 scene->push(_useBuildHelper(*child, vBox, svgPath, depth + 1, isMaskWhite));
732 else
733 scene->push(_sceneBuildHelper(*child, vBox, svgPath, false, depth + 1, isMaskWhite));
734 } else if ((*child)->type == SvgNodeType::Image) {
735 auto image = _imageBuildHelper(*child, vBox, svgPath);
736 if (image) {
737 scene->push(std::move(image));
738 if (isMaskWhite) *isMaskWhite = false;
739 }
740 } else if ((*child)->type != SvgNodeType::Mask) {
741 auto shape = _shapeBuildHelper(*child, vBox, svgPath);
742 if (shape) {
743 if (isMaskWhite) {
744 uint8_t r, g, b;
745 shape->fillColor(&r, &g, &b);
746 if (shape->fill() || r < 255 || g < 255 || b < 255 || shape->strokeFill() ||
747 (shape->strokeColor(&r, &g, &b) == Result::Success && (r < 255 || g < 255 || b < 255))) {
748 *isMaskWhite = false;
749 }
750 }
751 scene->push(std::move(shape));
752 }
753 }
754 }
755 _applyComposition(scene.get(), node, vBox, svgPath);
756 scene->opacity(node->style->opacity);
757 }
758 return scene;
759 }
760 return nullptr;
761}
762
763
764static void _updateInvalidViewSize(const Scene* scene, Box& vBox, float& w, float& h, SvgViewFlag viewFlag)
765{
766 bool validWidth = (viewFlag & SvgViewFlag::Width);
767 bool validHeight = (viewFlag & SvgViewFlag::Height);
768
769 float x, y;
770 scene->bounds(&x, &y, &vBox.w, &vBox.h, false);
771 if (!validWidth && !validHeight) {
772 vBox.x = x;
773 vBox.y = y;
774 } else {
775 if (validWidth) vBox.w = w;
776 if (validHeight) vBox.h = h;
777 }
778
779 //the size would have 1x1 or percentage values.
780 if (!validWidth) w *= vBox.w;
781 if (!validHeight) h *= vBox.h;
782}
783
784/************************************************************************/
785/* External Class Implementation */
786/************************************************************************/
787
788unique_ptr<Scene> svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag)
789{
790 //TODO: aspect ratio is valid only if viewBox was set
791
792 if (!loaderData.doc || (loaderData.doc->type != SvgNodeType::Doc)) return nullptr;
793
794 auto docNode = _sceneBuildHelper(loaderData.doc, vBox, svgPath, false, 0);
795
796 if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode.get(), vBox, w, h, viewFlag);
797
798 if (!mathEqual(w, vBox.w) || !mathEqual(h, vBox.h)) {
799 Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
800 docNode->transform(m);
801 } else if (!mathZero(vBox.x) || !mathZero(vBox.y)) {
802 docNode->translate(-vBox.x, -vBox.y);
803 }
804
805 auto viewBoxClip = Shape::gen();
806 viewBoxClip->appendRect(0, 0, w, h, 0, 0);
807 viewBoxClip->fill(0, 0, 0);
808
809 auto compositeLayer = Scene::gen();
810 compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
811 compositeLayer->push(std::move(docNode));
812
813 auto root = Scene::gen();
814 root->push(std::move(compositeLayer));
815
816 loaderData.doc->node.doc.vx = vBox.x;
817 loaderData.doc->node.doc.vy = vBox.y;
818 loaderData.doc->node.doc.vw = vBox.w;
819 loaderData.doc->node.doc.vh = vBox.h;
820 loaderData.doc->node.doc.w = w;
821 loaderData.doc->node.doc.h = h;
822
823 return root;
824}
825