diff options
author | K. S. Ernest (iFire) Lee <ernest.lee@chibifire.com> | 2022-01-13 13:54:19 +0100 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2022-01-14 15:49:39 +0100 |
commit | 8d02759c720c3a91663e56979273feabad1dc051 (patch) | |
tree | b939f28feb3224d4c4d2e39d12ef191157e2664b /thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp | |
parent | 9b3535a33a1dda541a3a39e7786b8428fadbff6c (diff) |
Use ThorVG instead of NanoSVG for importing SVGs
ThorVG is a platform-independent portable library for drawing vector-based
scene and animation.
Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
Diffstat (limited to 'thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp')
-rw-r--r-- | thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp new file mode 100644 index 0000000000..8701fe32b1 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Copyright notice for the EFL: + + * Copyright (C) EFL developers (see AUTHORS) + + * All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include <string> +#include "tvgMath.h" +#include "tvgSvgLoaderCommon.h" +#include "tvgSvgSceneBuilder.h" +#include "tvgSvgPath.h" +#include "tvgSvgUtil.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Box +{ + float x, y, w, h; +}; + + +static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath); +static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask); + + +static inline bool _isGroupType(SvgNodeType type) +{ + if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true; + return false; +} + + +//According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph) +//a stroke width should be ignored for bounding box calculations +static Box _boundingBox(const Shape* shape) +{ + float x, y, w, h; + shape->bounds(&x, &y, &w, &h, false); + + if (auto strokeW = shape->strokeWidth()) { + x += 0.5f * strokeW; + y += 0.5f * strokeW; + w -= strokeW; + h -= strokeW; + } + + return {x, y, w, h}; +} + + +static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf) +{ + gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13; + gradTransf->e12 *= mBBox->e11; + gradTransf->e11 *= mBBox->e11; + + gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23; + gradTransf->e22 *= mBBox->e22; + gradTransf->e21 *= mBBox->e22; +} + + +static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity) +{ + Fill::ColorStop* stops; + int stopCount = 0; + auto fillGrad = LinearGradient::gen(); + + bool isTransform = (g->transform ? true : false); + Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if (isTransform) finalTransform = *g->transform; + + if (g->userSpace) { + g->linear->x1 = g->linear->x1 * vBox.w; + g->linear->y1 = g->linear->y1 * vBox.h; + g->linear->x2 = g->linear->x2 * vBox.w; + g->linear->y2 = g->linear->y2 * vBox.h; + } else { + Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1}; + if (isTransform) _transformMultiply(&m, &finalTransform); + else { + finalTransform = m; + isTransform = true; + } + } + + if (isTransform) fillGrad->transform(finalTransform); + + fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2); + fillGrad->spread(g->spread); + + //Update the stops + stopCount = g->stops.count; + if (stopCount > 0) { + stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); + if (!stops) return fillGrad; + auto prevOffset = 0.0f; + for (uint32_t i = 0; i < g->stops.count; ++i) { + auto colorStop = &g->stops.data[i]; + //Use premultiplied color + stops[i].r = colorStop->r; + stops[i].g = colorStop->g; + stops[i].b = colorStop->b; + stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255); + stops[i].offset = colorStop->offset; + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; + else if (colorStop->offset > 1) stops[i].offset = 1; + prevOffset = stops[i].offset; + } + fillGrad->colorStops(stops, stopCount); + free(stops); + } + return fillGrad; +} + + +static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity) +{ + Fill::ColorStop *stops; + int stopCount = 0; + auto fillGrad = RadialGradient::gen(); + + bool isTransform = (g->transform ? true : false); + Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if (isTransform) finalTransform = *g->transform; + + if (g->userSpace) { + //The radius scalling is done according to the Units section: + //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + g->radial->cx = g->radial->cx * vBox.w; + g->radial->cy = g->radial->cy * vBox.h; + g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f); + g->radial->fx = g->radial->fx * vBox.w; + g->radial->fy = g->radial->fy * vBox.h; + } else { + Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1}; + if (isTransform) _transformMultiply(&m, &finalTransform); + else { + finalTransform = m; + isTransform = true; + } + } + + if (isTransform) fillGrad->transform(finalTransform); + + //TODO: Tvg is not support to focal + //if (g->radial->fx != 0 && g->radial->fy != 0) { + // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r); + //} + fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r); + fillGrad->spread(g->spread); + + //Update the stops + stopCount = g->stops.count; + if (stopCount > 0) { + stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); + if (!stops) return fillGrad; + auto prevOffset = 0.0f; + for (uint32_t i = 0; i < g->stops.count; ++i) { + auto colorStop = &g->stops.data[i]; + //Use premultiplied color + stops[i].r = colorStop->r; + stops[i].g = colorStop->g; + stops[i].b = colorStop->b; + stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255); + stops[i].offset = colorStop->offset; + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; + else if (colorStop->offset > 1) stops[i].offset = 1; + prevOffset = stops[i].offset; + } + fillGrad->colorStops(stops, stopCount); + free(stops); + } + return fillGrad; +} + + +static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) +{ + auto valid = false; + + if (_appendShape(node, shape, vBox, svgPath)) valid = true; + + if (node->child.count > 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + if (_appendChildShape(*child, shape, vBox, svgPath)) valid = true; + } + } + + return valid; +} + + +static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath) +{ + /* ClipPath */ + /* Do not drop in Circular Dependency for ClipPath. + Composition can be applied recursively if its children nodes have composition target to this one. */ + if (node->style->clipPath.applying) { + TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?"); + } else { + auto compNode = node->style->clipPath.node; + if (compNode && compNode->child.count > 0) { + node->style->clipPath.applying = true; + + auto comp = Shape::gen(); + comp->fill(255, 255, 255, 255); + if (node->transform) comp->transform(*node->transform); + + auto child = compNode->child.data; + auto valid = false; //Composite only when valid shapes are existed + + for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) { + if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true; + } + + if (valid) paint->composite(move(comp), CompositeMethod::ClipPath); + + node->style->clipPath.applying = false; + } + } + + /* Mask */ + /* Do not drop in Circular Dependency for Mask. + Composition can be applied recursively if its children nodes have composition target to this one. */ + if (node->style->mask.applying) { + TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?"); + } else { + auto compNode = node->style->mask.node; + if (compNode && compNode->child.count > 0) { + node->style->mask.applying = true; + + auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true); + if (comp) { + if (node->transform) comp->transform(*node->transform); + paint->composite(move(comp), CompositeMethod::AlphaMask); + } + + node->style->mask.applying = false; + } + } +} + + +static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath) +{ + SvgStyleProperty* style = node->style; + + if (node->transform) vg->transform(*node->transform); + if (node->type == SvgNodeType::Doc || !node->display) return; + + //If fill property is nullptr then do nothing + if (style->fill.paint.none) { + //Do nothing + } else if (style->fill.paint.gradient) { + Box bBox = vBox; + if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg); + + if (style->fill.paint.gradient->type == SvgGradientType::Linear) { + auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity); + vg->fill(move(linear)); + } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) { + auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity); + vg->fill(move(radial)); + } + } else if (style->fill.paint.url) { + //TODO: Apply the color pointed by url + } else if (style->fill.paint.curColor) { + //Apply the current style color + vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity); + } else { + //Apply the fill color + vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity); + } + + //Apply the fill rule + vg->fill((tvg::FillRule)style->fill.fillRule); + + //Apply node opacity + if (style->opacity < 255) vg->opacity(style->opacity); + + if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return; + + //Apply the stroke style property + vg->stroke(style->stroke.width); + vg->stroke(style->stroke.cap); + vg->stroke(style->stroke.join); + if (style->stroke.dash.array.count > 0) { + vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count); + } + + //If stroke property is nullptr then do nothing + if (style->stroke.paint.none) { + //Do nothing + } else if (style->stroke.paint.gradient) { + Box bBox = vBox; + if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg); + + if (style->stroke.paint.gradient->type == SvgGradientType::Linear) { + auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity); + vg->stroke(move(linear)); + } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) { + auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity); + vg->stroke(move(radial)); + } + } else if (style->stroke.paint.url) { + //TODO: Apply the color pointed by url + } else if (style->stroke.paint.curColor) { + //Apply the current style color + vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity); + } else { + //Apply the stroke color + vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity); + } + + _applyComposition(vg, node, vBox, svgPath); +} + + +static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath) +{ + auto shape = Shape::gen(); + if (_appendShape(node, shape.get(), vBox, svgPath)) return shape; + else return nullptr; +} + + +static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) +{ + Array<PathCommand> cmds; + Array<Point> pts; + + switch (node->type) { + case SvgNodeType::Path: { + if (node->node.path.path) { + if (svgPathToTvgPath(node->node.path.path, cmds, pts)) { + shape->appendPath(cmds.data, cmds.count, pts.data, pts.count); + } + } + break; + } + case SvgNodeType::Ellipse: { + shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry); + break; + } + case SvgNodeType::Polygon: { + if (node->node.polygon.pointsCount < 2) break; + shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); + for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) { + shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); + } + shape->close(); + break; + } + case SvgNodeType::Polyline: { + if (node->node.polygon.pointsCount < 2) break; + shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); + for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) { + shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); + } + break; + } + case SvgNodeType::Circle: { + shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r); + break; + } + case SvgNodeType::Rect: { + 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); + break; + } + case SvgNodeType::Line: { + shape->moveTo(node->node.line.x1, node->node.line.y1); + shape->lineTo(node->node.line.x2, node->node.line.y2); + break; + } + default: { + return false; + } + } + + _applyProperty(node, shape, vBox, svgPath); + return true; +} + + +enum class imageMimeTypeEncoding +{ + base64 = 0x1, + utf8 = 0x2 +}; +constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { + return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b)); +} +constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { + return (static_cast<int>(a) & static_cast<int>(b)); +} + + +static constexpr struct +{ + const char* name; + int sz; + imageMimeTypeEncoding encoding; +} imageMimeTypes[] = { + {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64}, + {"png", sizeof("png"), imageMimeTypeEncoding::base64}, + {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8}, +}; + + +static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) { + if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type + *href += sizeof("image/") - 1; + + //RFC2397 data:[<mediatype>][;base64],<data> + //mediatype := [ type "/" subtype ] *( ";" parameter ) + //parameter := attribute "=" value + for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) { + if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) { + *href += imageMimeTypes[i].sz - 1; + *mimetype = imageMimeTypes[i].name; + + while (**href && **href != ',') { + while (**href && **href != ';') ++(*href); + if (!**href) return false; + ++(*href); + + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) { + if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) { + *href += sizeof("base64,") - 1; + *encoding = imageMimeTypeEncoding::base64; + return true; //valid base64 + } + } + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) { + if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) { + *href += sizeof("utf8,") - 1; + *encoding = imageMimeTypeEncoding::utf8; + return true; //valid utf8 + } + } + } + //no encoding defined + if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) { + ++(*href); + *encoding = imageMimeTypeEncoding::utf8; + return true; //allow no encoding defined if utf8 expected + } + return false; + } + } + return false; +} + + +static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath) +{ + if (!node->node.image.href) return nullptr; + auto picture = Picture::gen(); + + const char* href = node->node.image.href; + if (!strncmp(href, "data:", sizeof("data:") - 1)) { + href += sizeof("data:") - 1; + const char* mimetype; + imageMimeTypeEncoding encoding; + if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding + if (encoding == imageMimeTypeEncoding::base64) { + string decoded = svgUtilBase64Decode(href); + if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr; + } else { + string decoded = svgUtilURLDecode(href); + if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr; + } + } else { + if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1; + //TODO: protect against recursive svg image loading + //Temporarily disable embedded svg: + const char *dot = strrchr(href, '.'); + if (dot && !strcmp(dot, ".svg")) { + TVGLOG("SVG", "Embedded svg file is disabled."); + return nullptr; + } + string imagePath = href; + if (strncmp(href, "/", 1)) { + auto last = svgPath.find_last_of("/"); + imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath; + } + if (picture->load(imagePath) != Result::Success) return nullptr; + } + + float w, h; + if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { + auto sx = node->node.image.w / w; + auto sy = node->node.image.h / h; + Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; + picture->transform(m); + } + + _applyComposition(picture.get(), node, vBox, svgPath); + return picture; +} + + +static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath) +{ + auto scene = _sceneBuildHelper(node, vBox, svgPath, false); + if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { + scene->translate(node->node.use.x, node->node.use.y); + } + if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) { + //TODO: handle width/height properties + } + return scene; +} + + +static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask) +{ + if (_isGroupType(node->type) || mask) { + auto scene = Scene::gen(); + if (!mask && node->transform) scene->transform(*node->transform); + + if (node->display && node->style->opacity != 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + if (_isGroupType((*child)->type)) { + if ((*child)->type == SvgNodeType::Use) + scene->push(_useBuildHelper(*child, vBox, svgPath)); + else + scene->push(_sceneBuildHelper(*child, vBox, svgPath, false)); + } else if ((*child)->type == SvgNodeType::Image) { + auto image = _imageBuildHelper(*child, vBox, svgPath); + if (image) scene->push(move(image)); + } else if ((*child)->type != SvgNodeType::Mask) { + auto shape = _shapeBuildHelper(*child, vBox, svgPath); + if (shape) scene->push(move(shape)); + } + } + _applyComposition(scene.get(), node, vBox, svgPath); + scene->opacity(node->style->opacity); + } + return scene; + } + return nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath) +{ + if (!node || (node->type != SvgNodeType::Doc)) return nullptr; + + Box vBox = {vx, vy, vw, vh}; + auto docNode = _sceneBuildHelper(node, vBox, svgPath, false); + + if (!mathEqual(w, vw) || !mathEqual(h, vh)) { + auto sx = w / vw; + auto sy = h / vh; + + if (preserveAspect) { + //Scale + auto scale = sx < sy ? sx : sy; + docNode->scale(scale); + //Align + auto tvx = vx * scale; + auto tvy = vy * scale; + auto tvw = vw * scale; + auto tvh = vh * scale; + if (vw > vh) tvy -= (h - tvh) * 0.5f; + else tvx -= (w - tvw) * 0.5f; + docNode->translate(-tvx, -tvy); + } else { + //Align + auto tvx = vx * sx; + auto tvy = vy * sy; + auto tvw = vw * sx; + auto tvh = vh * sy; + if (tvw > tvh) tvy -= (h - tvh) * 0.5f; + else tvx -= (w - tvw) * 0.5f; + Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1}; + docNode->transform(m); + } + } else if (!mathZero(vx) || !mathZero(vy)) { + docNode->translate(-vx, -vy); + } + + auto viewBoxClip = Shape::gen(); + viewBoxClip->appendRect(0, 0, w, h, 0, 0); + viewBoxClip->fill(0, 0, 0, 255); + + auto compositeLayer = Scene::gen(); + compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath); + compositeLayer->push(move(docNode)); + + auto root = Scene::gen(); + root->push(move(compositeLayer)); + + return root; +} |