mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00

Supports case of using style attribute for defined <clipPath>. In SVG, <clipPath> can be used as a "clipPath" attribute or a style "clip-path". This patch only supports "clip-path" of style is declared. The remaining features will be added later. [Example SVG case] <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"> <defs> <clipPath id="clipPath"> <rect x="15" y="15" width="40" height="40" fill="#F00" /> <circle cx="20" cy="20" r="10" fill="#F00" /> </clipPath> </defs> <circle cx="25" cy="25" r="20" style="fill: #0000ff; clip-path: url(#clipPath); " /> </svg>
404 lines
15 KiB
C++
404 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2020 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.
|
|
*/
|
|
#include <math.h>
|
|
#include "tvgSvgSceneBuilder.h"
|
|
#include "tvgSvgPath.h"
|
|
|
|
void _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh);
|
|
|
|
bool _isGroupType(SvgNodeType type)
|
|
{
|
|
if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::ClipPath) return true;
|
|
return false;
|
|
}
|
|
|
|
unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, Shape* vg, float rx, float ry, float rw, float rh)
|
|
{
|
|
Fill::ColorStop* stops;
|
|
int stopCount = 0;
|
|
float fillOpacity = 255.0f;
|
|
float gx, gy, gw, gh;
|
|
|
|
auto fillGrad = LinearGradient::gen();
|
|
|
|
if (g->usePercentage) {
|
|
g->linear->x1 = g->linear->x1 * rw + rx;
|
|
g->linear->y1 = g->linear->y1 * rh + ry;
|
|
g->linear->x2 = g->linear->x2 * rw + rx;
|
|
g->linear->y2 = g->linear->y2 * rh + ry;
|
|
}
|
|
|
|
//In case of objectBoundingBox it need proper scaling
|
|
if (!g->userSpace) {
|
|
float scaleX = 1.0, scaleReversedX = 1.0;
|
|
float scaleY = 1.0, scaleReversedY = 1.0;
|
|
|
|
//Check the smallest size, find the scale value
|
|
if (rh > rw) {
|
|
scaleY = ((float)rw) / rh;
|
|
scaleReversedY = ((float)rh) / rw;
|
|
} else {
|
|
scaleX = ((float)rh) / rw;
|
|
scaleReversedX = ((float)rw) / rh;
|
|
}
|
|
|
|
vg->bounds(&gx, &gy, &gw, &gh);
|
|
|
|
float cy = ((float)gh) * 0.5 + gy;
|
|
float cy_scaled = (((float)gh) * 0.5) * scaleReversedY;
|
|
float cx = ((float)gw) * 0.5 + gx;
|
|
float cx_scaled = (((float)gw) * 0.5) * scaleReversedX;
|
|
|
|
//= T(gx, gy) x S(scaleX, scaleY) x T(cx_scaled - cx, cy_scaled - cy) x (radial->x, radial->y)
|
|
g->linear->x1 = g->linear->x1 * scaleX + scaleX * (cx_scaled - cx) + gx;
|
|
g->linear->y1 = g->linear->y1 * scaleY + scaleY * (cy_scaled - cy) + gy;
|
|
g->linear->x2 = g->linear->x2 * scaleX + scaleX * (cx_scaled - cx) + gx;
|
|
g->linear->y2 = g->linear->y2 * scaleY + scaleY * (cy_scaled - cy) + gy;
|
|
}
|
|
|
|
if (g->transform) {
|
|
float cy = ((float) rh) * 0.5 + ry;
|
|
float cx = ((float) rw) * 0.5 + rx;
|
|
|
|
//= T(x - cx, y - cy) x g->transform x T(cx, cy)
|
|
//Calc start point
|
|
g->linear->x1 = (g->transform->e11 * cx) + (g->transform->e12 * cy) + g->linear->x1 + g->transform->e13 - cx;
|
|
g->linear->y1 = (g->transform->e21 * cx) + (g->transform->e22 * cy) + g->linear->y1 + g->transform->e23 - cy;
|
|
|
|
//Calc end point
|
|
g->linear->x2 = (g->transform->e11 * cx) + (g->transform->e12 * cy) + g->linear->x2 + g->transform->e13 - cx;
|
|
g->linear->y2 = (g->transform->e21 * cx) + (g->transform->e22 * cy) + g->linear->y2 + g->transform->e23 - cy;
|
|
}
|
|
|
|
fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
|
|
fillGrad->spread(g->spread);
|
|
|
|
//Update the stops
|
|
stopCount = g->stops.cnt;
|
|
if (stopCount > 0) {
|
|
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
|
|
for (uint32_t i = 0; i < g->stops.cnt; ++i) {
|
|
auto colorStop = g->stops.list[i];
|
|
//Use premultiplied color
|
|
stops[i].r = colorStop->r;
|
|
stops[i].g = colorStop->g;
|
|
stops[i].b = colorStop->b;
|
|
stops[i].a = (colorStop->a * fillOpacity) / 255.0f;
|
|
stops[i].offset = colorStop->offset;
|
|
}
|
|
fillGrad->colorStops(stops, stopCount);
|
|
free(stops);
|
|
}
|
|
return fillGrad;
|
|
}
|
|
|
|
|
|
unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, Shape* vg, float rx, float ry, float rw, float rh)
|
|
{
|
|
Fill::ColorStop *stops;
|
|
int stopCount = 0;
|
|
float gx, gy, gw, gh;
|
|
int radius;
|
|
float fillOpacity = 255.0f;
|
|
|
|
auto fillGrad = RadialGradient::gen();
|
|
|
|
radius = sqrt(pow(rw, 2) + pow(rh, 2)) / sqrt(2.0);
|
|
if (!g->userSpace) {
|
|
//That is according to Units in here
|
|
//https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
|
|
int min = (rh > rw) ? rw : rh;
|
|
radius = sqrt(pow(min, 2) + pow(min, 2)) / sqrt(2.0);
|
|
}
|
|
|
|
if (g->usePercentage) {
|
|
g->radial->cx = g->radial->cx * rw + rx;
|
|
g->radial->cy = g->radial->cy * rh + ry;
|
|
g->radial->r = g->radial->r * radius;
|
|
g->radial->fx = g->radial->fx * rw + rx;
|
|
g->radial->fy = g->radial->fy * rh + ry;
|
|
}
|
|
|
|
//In case of objectBoundingBox it need proper scaling
|
|
if (!g->userSpace) {
|
|
float scaleX = 1.0, scaleReversedX = 1.0;
|
|
float scaleY = 1.0, scaleReversedY = 1.0;
|
|
|
|
//Check the smallest size, find the scale value
|
|
if (rh > rw) {
|
|
scaleY = ((float)rw) / rh;
|
|
scaleReversedY = ((float)rh) / rw;
|
|
} else {
|
|
scaleX = ((float)rh) / rw;
|
|
scaleReversedX = ((float)rw) / rh;
|
|
}
|
|
|
|
vg->bounds(&gx, &gy, &gw, &gh);
|
|
|
|
float cy = ((float)gh) * 0.5 + gy;
|
|
float cy_scaled = (((float)gh) * 0.5) * scaleReversedY;
|
|
float cx = ((float)gw) * 0.5 + gx;
|
|
float cx_scaled = (((float)gw) * 0.5) * scaleReversedX;
|
|
|
|
//= T(gx, gy) x S(scaleX, scaleY) x T(cx_scaled - cx, cy_scaled - cy) x (radial->x, radial->y)
|
|
g->radial->cx = g->radial->cx * scaleX + scaleX * (cx_scaled - cx) + gx;
|
|
g->radial->cy = g->radial->cy * scaleY + scaleY * (cy_scaled - cy) + gy;
|
|
}
|
|
|
|
//TODO: Radial gradient transformation is not yet supported.
|
|
//if (g->transform) {}
|
|
|
|
//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.cnt;
|
|
if (stopCount > 0) {
|
|
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
|
|
for (uint32_t i = 0; i < g->stops.cnt; ++i) {
|
|
auto colorStop = g->stops.list[i];
|
|
//Use premultiplied color
|
|
stops[i].r = colorStop->r;
|
|
stops[i].g = colorStop->g;
|
|
stops[i].b = colorStop->b;
|
|
stops[i].a = (colorStop->a * fillOpacity) / 255.0f;
|
|
stops[i].offset = colorStop->offset;
|
|
}
|
|
fillGrad->colorStops(stops, stopCount);
|
|
free(stops);
|
|
}
|
|
return fillGrad;
|
|
}
|
|
|
|
void _appendChildShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh)
|
|
{
|
|
_appendShape(node, shape, vx, vy, vw, vh);
|
|
if (node->child.cnt > 0) {
|
|
auto child = node->child.list;
|
|
for (uint32_t i = 0; i < node->child.cnt; ++i, ++child) _appendChildShape(*child, shape, vx, vy, vw, vh);
|
|
}
|
|
}
|
|
|
|
void _applyProperty(SvgNode* node, Shape* vg, float vx, float vy, float vw, float vh)
|
|
{
|
|
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) {
|
|
if (!style->fill.paint.gradient->userSpace) vg->bounds(&vx, &vy, &vw, &vh);
|
|
|
|
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
|
|
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh);
|
|
vg->fill(move(linear));
|
|
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
|
|
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh);
|
|
vg->fill(move(radial));
|
|
}
|
|
} else if (style->fill.paint.curColor) {
|
|
//Apply the current style color
|
|
vg->fill(style->r, style->g, style->b, style->fill.opacity);
|
|
} else {
|
|
//Apply the fill color
|
|
vg->fill(style->fill.paint.r, style->fill.paint.g, style->fill.paint.b, style->fill.opacity);
|
|
}
|
|
|
|
//Apply node opacity
|
|
if (style->opacity < 255) {
|
|
uint8_t r, g, b, a;
|
|
vg->fill(&r, &g, &b, &a);
|
|
vg->fill(r, g, b, (a * style->opacity) / 255.0f);
|
|
}
|
|
|
|
if (node->type == SvgNodeType::G) 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.cnt > 0)
|
|
vg->stroke(style->stroke.dash.array.list, style->stroke.dash.array.cnt);
|
|
|
|
//If stroke property is nullptr then do nothing
|
|
if (style->stroke.paint.none) {
|
|
//Do nothing
|
|
} else if (style->stroke.paint.gradient) {
|
|
//TODO: Support gradient style
|
|
} 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->r, style->g, style->b, style->stroke.opacity);
|
|
|
|
} else {
|
|
//Apply the stroke color
|
|
vg->stroke(style->stroke.paint.r, style->stroke.paint.g, style->stroke.paint.b, style->stroke.opacity);
|
|
}
|
|
|
|
//Apply node opacity to stroke color
|
|
if (style->opacity < 255) {
|
|
uint8_t r, g, b, a;
|
|
vg->strokeColor(&r, &g, &b, &a);
|
|
vg->stroke(r, g, b, (a * style->opacity) / 255.0f);
|
|
}
|
|
|
|
//Apply composite node
|
|
if (style->comp.node) {
|
|
//Composite ClipPath
|
|
if (((int)style->comp.flags & (int)SvgCompositeFlags::ClipPath)) {
|
|
auto compNode = style->comp.node;
|
|
if (compNode->child.cnt > 0) {
|
|
auto comp = Shape::gen();
|
|
auto child = compNode->child.list;
|
|
for (uint32_t i = 0; i < compNode->child.cnt; ++i, ++child) _appendChildShape(*child, comp.get(), vx, vy, vw, vh);
|
|
vg->composite(move(comp), CompositeMethod::ClipPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh)
|
|
{
|
|
auto shape = Shape::gen();
|
|
_appendShape(node, shape.get(), vx, vy, vw, vh);
|
|
return shape;
|
|
}
|
|
|
|
void _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh)
|
|
{
|
|
switch (node->type) {
|
|
case SvgNodeType::Path: {
|
|
if (node->node.path.path) {
|
|
auto pathResult = svgPathToTvgPath(node->node.path.path->c_str());
|
|
shape->appendPath(get<0>(pathResult).data(), get<0>(pathResult).size(), get<1>(pathResult).data(), get<1>(pathResult).size());
|
|
}
|
|
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; 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; 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: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
_applyProperty(node, shape, vx, vy, vw, vh);
|
|
}
|
|
|
|
unique_ptr<Scene> _sceneBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, int parentOpacity)
|
|
{
|
|
if (_isGroupType(node->type)) {
|
|
auto scene = Scene::gen();
|
|
if (node->transform) scene->transform(*node->transform);
|
|
node->style->opacity = (node->style->opacity * parentOpacity) / 255.0f;
|
|
|
|
if (node->display) {
|
|
auto child = node->child.list;
|
|
for (uint32_t i = 0; i < node->child.cnt; ++i, ++child) {
|
|
if (_isGroupType((*child)->type)) {
|
|
scene->push(_sceneBuildHelper(*child, vx, vy, vw, vh, node->style->opacity));
|
|
} else {
|
|
(*child)->style->opacity = ((*child)->style->opacity * node->style->opacity) / 255.0f;
|
|
scene->push(_shapeBuildHelper(*child, vx, vy, vw, vh));
|
|
}
|
|
|
|
//Apply composite node
|
|
if (node->style->comp.node) {
|
|
//Composite ClipPath
|
|
if (((int)node->style->comp.flags & (int)SvgCompositeFlags::ClipPath)) {
|
|
auto compNode = node->style->comp.node;
|
|
if (compNode->child.cnt > 0) {
|
|
auto comp = Shape::gen();
|
|
auto child = compNode->child.list;
|
|
for (uint32_t i = 0; i < compNode->child.cnt; ++i, ++child) _appendChildShape(*child, comp.get(), vx, vy, vw, vh);
|
|
scene->composite(move(comp), CompositeMethod::ClipPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return scene;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
SvgSceneBuilder::SvgSceneBuilder()
|
|
{
|
|
}
|
|
|
|
|
|
SvgSceneBuilder::~SvgSceneBuilder()
|
|
{
|
|
}
|
|
|
|
|
|
unique_ptr<Scene> SvgSceneBuilder::build(SvgNode* node)
|
|
{
|
|
if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
|
|
|
|
viewBox.x = node->node.doc.vx;
|
|
viewBox.y = node->node.doc.vy;
|
|
viewBox.w = node->node.doc.vw;
|
|
viewBox.h = node->node.doc.vh;
|
|
preserveAspect = node->node.doc.preserveAspect;
|
|
return _sceneBuildHelper(node, viewBox.x, viewBox.y, viewBox.w, viewBox.h, 255);
|
|
}
|