Icon-O-Matic: replace SVG parser with nanosvg
The parser was based on an old example from agg. NanoSVG is originally
based on the same code, but has lots of bugfixes. So it makes sense
to use it.
Nanosvg revision 25241c5a8f8451d41ab1b02ab2d865b01600d949
Fixes #5955, #8586, #13021.
Change-Id: I38ff9aa4e1d403c41979ebe42f7b45d4500a870c
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2661
Reviewed-by: waddlesplash <waddlesplash@gmail.com>
(cherry picked from commit 2a3b385efba22655c4d6ff61ed59203283392ad7)
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2774
Diff
src/apps/icon-o-matic/Jamfile | 3 ---
src/libs/icon/style/GradientTransformable.cpp | 2 ++
src/libs/icon/transformable/Transformable.cpp | 9 +++++++++
src/libs/icon/transformable/Transformable.h | 2 ++
src/apps/icon-o-matic/import_export/svg/DocumentBuilder.cpp | 836 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
src/apps/icon-o-matic/import_export/svg/DocumentBuilder.h | 208 +++++---------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/PathTokenizer.cpp | 157 --------------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/PathTokenizer.h | 106 --------------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/SVGGradients.cpp | 199 --------------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/SVGGradients.h | 81 --------------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/SVGImporter.cpp | 23 +++++++++++------------
src/apps/icon-o-matic/import_export/svg/SVGParser.cpp | 1152 --------------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/SVGParser.h | 105 --------------------------------------------------------------------------------
src/apps/icon-o-matic/import_export/svg/nanosvg.h | 2975 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 3167 insertions(+), 2691 deletions(-)
@@ -233,11 +233,8 @@
# import_export/svg
DocumentBuilder.cpp
PathTokenizer.cpp
SVGExporter.cpp
SVGGradients.cpp
SVGImporter.cpp
SVGParser.cpp
# shape
PathManipulator.cpp
@@ -601,6 +601,8 @@
step->color.blue,
step->color.alpha);
}
Transformable::PrintToStream();
}
@@ -319,3 +319,12 @@
}
void
Transformable::PrintToStream() const
{
double matrix[6];
store_to(matrix);
printf("Transform:\n%f\t%f\t%f\n%f\t%f\t%f\n",
matrix[0], matrix[2], matrix[4], matrix[1], matrix[3], matrix[5]);
}
@@ -70,6 +70,8 @@
virtual void TransformationChanged();
virtual void PrintToStream() const;
};
@@ -6,21 +6,8 @@
* Stephan Aßmus <superstippi@gmx.de>
*/
#define NANOSVG_IMPLEMENTATION
#include "DocumentBuilder.h"
#include <new>
@@ -28,8 +15,6 @@
#include <Bitmap.h>
#include <agg_bounding_rect.h>
#include "AutoDeleter.h"
#include "GradientTransformable.h"
#include "Icon.h"
@@ -39,166 +24,24 @@
#include "StrokeTransformer.h"
#include "Style.h"
#include "StyleContainer.h"
#include "SVGGradients.h"
#include "SVGImporter.h"
#include "VectorPath.h"
using std::nothrow;
#include <agg_math_stroke.h>
#include <agg_trans_affine.h>
namespace agg {
namespace svg {
using std::nothrow;
DocumentBuilder::DocumentBuilder()
: fGradients(20),
fCurrentGradient(NULL),
fWidth(0),
DocumentBuilder::DocumentBuilder(NSVGimage* source)
: fWidth(0),
fHeight(0),
fViewBox(0.0, 0.0, -1.0, -1.0),
fTitle("")
{
}
void
DocumentBuilder::remove_all()
{
fPathStorage.remove_all();
fAttributesStorage.remove_all();
fAttributesStack.remove_all();
fTransform.reset();
}
void
DocumentBuilder::begin_path()
{
push_attr();
unsigned idx = fPathStorage.start_new_path();
fAttributesStorage.add(path_attributes(cur_attr(), idx));
}
void
DocumentBuilder::end_path()
{
if (fAttributesStorage.size() == 0) {
throw exception("end_path: The path was not begun");
}
path_attributes attr = cur_attr();
unsigned idx = fAttributesStorage[fAttributesStorage.size() - 1].index;
attr.index = idx;
fAttributesStorage[fAttributesStorage.size() - 1] = attr;
pop_attr();
}
void
DocumentBuilder::move_to(double x, double y, bool rel)
{
if (rel)
fPathStorage.move_rel(x, y);
else
fPathStorage.move_to(x, y);
}
void
DocumentBuilder::line_to(double x, double y, bool rel)
{
if (rel)
fPathStorage.line_rel(x, y);
else
fPathStorage.line_to(x, y);
}
void
DocumentBuilder::hline_to(double x, bool rel)
{
if (rel)
fPathStorage.hline_rel(x);
else
fPathStorage.hline_to(x);
}
void
DocumentBuilder::vline_to(double y, bool rel)
{
if (rel)
fPathStorage.vline_rel(y);
else
fPathStorage.vline_to(y);
}
void
DocumentBuilder::curve3(double x1, double y1,
double x, double y, bool rel)
{
if (rel)
fPathStorage.curve3_rel(x1, y1, x, y);
else
fPathStorage.curve3(x1, y1, x, y);
}
void
DocumentBuilder::curve3(double x, double y, bool rel)
{
if (rel)
fPathStorage.curve3_rel(x, y);
else
fPathStorage.curve3(x, y);
}
void
DocumentBuilder::curve4(double x1, double y1,
double x2, double y2,
double x, double y, bool rel)
{
if (rel) {
fPathStorage.curve4_rel(x1, y1, x2, y2, x, y);
} else {
fPathStorage.curve4(x1, y1, x2, y2, x, y);
}
}
void
DocumentBuilder::curve4(double x2, double y2,
double x, double y, bool rel)
{
if (rel) {
fPathStorage.curve4_rel(x2, y2, x, y);
} else {
fPathStorage.curve4(x2, y2, x, y);
}
}
void
DocumentBuilder::elliptical_arc(double rx, double ry, double angle,
bool large_arc_flag, bool sweep_flag,
double x, double y, bool rel)
fTitle(""),
fSource(source)
{
angle = angle / 180.0 * pi;
if (rel) {
fPathStorage.arc_rel(rx, ry, angle, large_arc_flag, sweep_flag, x, y);
} else {
fPathStorage.arc_to(rx, ry, angle, large_arc_flag, sweep_flag, x, y);
}
}
void
DocumentBuilder::close_subpath()
{
fPathStorage.end_poly(path_flags_close);
}
void
@@ -214,244 +57,6 @@
fWidth = width;
fHeight = height;
fViewBox = viewBox;
}
path_attributes&
DocumentBuilder::cur_attr()
{
if (fAttributesStack.size() == 0) {
throw exception("cur_attr: Attribute stack is empty");
}
return fAttributesStack[fAttributesStack.size() - 1];
}
void
DocumentBuilder::push_attr()
{
fAttributesStack.add(fAttributesStack.size() ? fAttributesStack[fAttributesStack.size() - 1]
: path_attributes());
}
void
DocumentBuilder::pop_attr()
{
if (fAttributesStack.size() == 0) {
throw exception("pop_attr: Attribute stack is empty");
}
fAttributesStack.remove_last();
}
void
DocumentBuilder::fill(const rgba8& f)
{
path_attributes& attr = cur_attr();
attr.fill_color = f;
attr.fill_flag = true;
}
void
DocumentBuilder::stroke(const rgba8& s)
{
path_attributes& attr = cur_attr();
attr.stroke_color = s;
attr.stroke_flag = true;
}
void
DocumentBuilder::even_odd(bool flag)
{
cur_attr().even_odd_flag = flag;
}
void
DocumentBuilder::stroke_width(double w)
{
path_attributes& attr = cur_attr();
attr.stroke_width = w;
attr.stroke_flag = true;
}
void
DocumentBuilder::fill_none()
{
cur_attr().fill_flag = false;
}
void
DocumentBuilder::fill_url(const char* url)
{
sprintf(cur_attr().fill_url, "%s", url);
}
void
DocumentBuilder::stroke_none()
{
cur_attr().stroke_flag = false;
}
void
DocumentBuilder::stroke_url(const char* url)
{
sprintf(cur_attr().stroke_url, "%s", url);
}
void
DocumentBuilder::opacity(double op)
{
cur_attr().opacity *= op;
}
void
DocumentBuilder::fill_opacity(double op)
{
cur_attr().fill_color.opacity(op);
}
void
DocumentBuilder::stroke_opacity(double op)
{
cur_attr().stroke_color.opacity(op);
}
void
DocumentBuilder::line_join(line_join_e join)
{
cur_attr().line_join = join;
}
void
DocumentBuilder::line_cap(line_cap_e cap)
{
cur_attr().line_cap = cap;
}
void
DocumentBuilder::miter_limit(double ml)
{
cur_attr().miter_limit = ml;
}
trans_affine&
DocumentBuilder::transform()
{
return cur_attr().transform;
}
void
DocumentBuilder::parse_path(PathTokenizer& tok)
{
char lastCmd = 0;
while(tok.next()) {
double arg[10];
char cmd = tok.last_command();
unsigned i;
switch(cmd) {
case 'M': case 'm':
arg[0] = tok.last_number();
arg[1] = tok.next(cmd);
if (lastCmd != cmd)
move_to(arg[0], arg[1], cmd == 'm');
else
line_to(arg[0], arg[1], lastCmd == 'm');
break;
case 'L': case 'l':
arg[0] = tok.last_number();
arg[1] = tok.next(cmd);
line_to(arg[0], arg[1], cmd == 'l');
break;
case 'V': case 'v':
vline_to(tok.last_number(), cmd == 'v');
break;
case 'H': case 'h':
hline_to(tok.last_number(), cmd == 'h');
break;
case 'Q': case 'q':
arg[0] = tok.last_number();
for(i = 1; i < 4; i++) {
arg[i] = tok.next(cmd);
}
curve3(arg[0], arg[1], arg[2], arg[3], cmd == 'q');
break;
case 'T': case 't':
arg[0] = tok.last_number();
arg[1] = tok.next(cmd);
curve3(arg[0], arg[1], cmd == 't');
break;
case 'C': case 'c':
arg[0] = tok.last_number();
for(i = 1; i < 6; i++) {
arg[i] = tok.next(cmd);
}
curve4(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], cmd == 'c');
break;
case 'S': case 's':
arg[0] = tok.last_number();
for(i = 1; i < 4; i++) {
arg[i] = tok.next(cmd);
}
curve4(arg[0], arg[1], arg[2], arg[3], cmd == 's');
break;
case 'A': case 'a': {
arg[0] = tok.last_number();
for(i = 1; i < 3; i++) {
arg[i] = tok.next(cmd);
}
bool large_arc_flag = (bool)tok.next(cmd);
bool sweep_flag = (bool)tok.next(cmd);
for(i = 3; i < 5; i++) {
arg[i] = tok.next(cmd);
}
elliptical_arc(arg[0], arg[1], arg[2],
large_arc_flag, sweep_flag,
arg[3], arg[4], cmd == 'a');
break;
}
case 'Z': case 'z':
close_subpath();
break;
default:
{
char buf[100];
sprintf(buf, "parse_path: Invalid path command '%c'", cmd);
throw exception(buf);
}
}
lastCmd = cmd;
}
}
@@ -461,22 +66,10 @@
DocumentBuilder::GetIcon(Icon* icon, SVGImporter* importer,
const char* fallbackName)
{
double xMin;
double yMin;
double xMax;
double yMax;
int32 pathCount = fAttributesStorage.size();
agg::conv_transform<agg::path_storage> transformedPaths(
fPathStorage, fTransform);
agg::bounding_rect(transformedPaths, *this, 0, pathCount,
&xMin, &yMin, &xMax, &yMax);
xMin = floor(xMin);
yMin = floor(yMin);
xMax = ceil(xMax);
yMax = ceil(yMax);
double xMin = 0;
double yMin = 0;
double xMax = ceil(fSource->width);
double yMax = ceil(fSource->height);
BRect bounds;
if (fViewBox.IsValid()) {
@@ -497,7 +90,7 @@
bounds.PrintToStream();
}
float size = min_c(bounds.Width() + 1.0, bounds.Height() + 1.0);
float size = max_c(bounds.Width(), bounds.Height());
double scale = 64.0 / size;
printf("scale: %f\n", scale);
@@ -509,16 +102,12 @@
for (int32 i = 0; i < pathCount; i++) {
path_attributes& attributes = fAttributesStorage[i];
if (attributes.fill_flag)
_AddShape(attributes, false, transform, icon);
if (attributes.stroke_flag)
_AddShape(attributes, true, transform, icon);
for (NSVGshape* shape = fSource->shapes; shape != NULL; shape = shape->next) {
if (shape->fill.type != NSVG_PAINT_NONE)
_AddShape(shape, false, transform, icon);
if (shape->stroke.type != NSVG_PAINT_NONE)
_AddShape(shape, true, transform, icon);
}
@@ -541,212 +130,61 @@
}
return B_OK;
}
void
DocumentBuilder::StartGradient(bool radial)
{
if (fCurrentGradient) {
fprintf(stderr, "DocumentBuilder::StartGradient() - ERROR: "
"previous gradient (%s) not finished!\n",
fCurrentGradient->ID());
}
if (radial)
fCurrentGradient = new SVGRadialGradient();
else
fCurrentGradient = new SVGLinearGradient();
_AddGradient(fCurrentGradient);
}
void
DocumentBuilder::EndGradient()
{
if (fCurrentGradient) {
} else {
fprintf(stderr, "DocumentBuilder::EndGradient() - "
"ERROR: no gradient started!\n");
}
fCurrentGradient = NULL;
}
void
DocumentBuilder::_AddGradient(SVGGradient* gradient)
{
if (gradient) {
fGradients.AddItem((void*)gradient);
}
}
SVGGradient*
DocumentBuilder::_GradientAt(int32 index) const
{
return (SVGGradient*)fGradients.ItemAt(index);
}
SVGGradient*
DocumentBuilder::_FindGradient(const char* name) const
{
for (int32 i = 0; SVGGradient* g = _GradientAt(i); i++) {
if (strcmp(g->ID(), name) == 0)
return g;
}
return NULL;
}
template<class VertexSource>
status_t
AddPathsFromVertexSource(Icon* icon, Shape* shape,
VertexSource& source, int32 index)
AddPathsFromVertexSource(Icon* icon, Shape* shape, NSVGshape* svgShape)
{
VectorPath* path = new (nothrow) VectorPath();
if (!path || !icon->Paths()->AddPath(path)) {
delete path;
return B_NO_MEMORY;
}
for (NSVGpath* svgPath = svgShape->paths; svgPath != NULL;
svgPath = svgPath->next) {
VectorPath* path = new (nothrow) VectorPath();
if (!path || !icon->Paths()->AddPath(path)) {
delete path;
return B_NO_MEMORY;
}
if (!shape->Paths()->AddPath(path))
return B_NO_MEMORY;
if (!shape->Paths()->AddPath(path))
return B_NO_MEMORY;
source.rewind(index);
double x1 = 0, y1 = 0;
unsigned cmd = source.vertex(&x1, &y1);
bool keepGoing = true;
int32 subPath = 0;
while (keepGoing) {
if (agg::is_next_poly(cmd)) {
if (agg::is_end_poly(cmd)) {
path->SetClosed(true);
subPath++;
} else {
}
path->SetClosed(svgPath->closed);
if (agg::is_stop(cmd)) {
keepGoing = false;
} else {
if (subPath > 0) {
path->CleanUp();
if (path->CountPoints() == 0) {
icon->Paths()->RemovePath(path);
shape->Paths()->RemovePath(path);
path->ReleaseReference();
}
path = new (nothrow) VectorPath();
if (!path || !icon->Paths()->AddPath(path)) {
delete path;
return B_NO_MEMORY;
}
if (!shape->Paths()->AddPath(path))
return B_NO_MEMORY;
}
}
}
switch (cmd) {
case agg::path_cmd_move_to:
if (path->CountPoints() > 0) {
path->CleanUp();
path = new (nothrow) VectorPath();
if (!path || !icon->Paths()->AddPath(path)) {
delete path;
return B_NO_MEMORY;
}
if (!shape->Paths()->AddPath(path))
return B_NO_MEMORY;
}
if (!path->AddPoint(BPoint(x1, y1)))
return B_NO_MEMORY;
path->SetInOutConnected(path->CountPoints() - 1, false);
break;
int pointCount = svgPath->npts;
float* points = svgPath->pts;
case agg::path_cmd_line_to:
if (!path->AddPoint(BPoint(x1, y1)))
return B_NO_MEMORY;
path->SetInOutConnected(path->CountPoints() - 1, false);
break;
if (!path->AddPoint(BPoint(points[0], points[1])))
return B_NO_MEMORY;
path->SetInOutConnected(path->CountPoints() - 1, false);
case agg::path_cmd_curve3: {
double x2 = 0, y2 = 0;
cmd = source.vertex(&x2, &y2);
int32 start = path->CountPoints() - 1;
BPoint from;
path->GetPointAt(start, from);
double cx2 = (1.0/3.0) * from.x + (2.0/3.0) * x1;
double cy2 = (1.0/3.0) * from.y + (2.0/3.0) * y1;
double cx3 = (2.0/3.0) * x1 + (1.0/3.0) * x2;
double cy3 = (2.0/3.0) * y1 + (1.0/3.0) * y2;
path->SetPointOut(start, BPoint(cx2, cy2));
if (!path->AddPoint(BPoint(x2, y2)))
return B_NO_MEMORY;
int32 end = path->CountPoints() - 1;
path->SetInOutConnected(end, false);
path->SetPointIn(end, BPoint(cx3, cy3));
break;
}
pointCount--;
points += 2;
case agg::path_cmd_curve4: {
double x2 = 0, y2 = 0;
double x3 = 0, y3 = 0;
cmd = source.vertex(&x2, &y2);
cmd = source.vertex(&x3, &y3);
while (pointCount > 0) {
BPoint vector1(points[0], points[1]);
BPoint vector2(points[2], points[3]);
BPoint endPoint(points[4], points[5]);
if (!path->AddPoint(BPoint(x3, y3)))
return B_NO_MEMORY;
if (!path->AddPoint(endPoint))
return B_NO_MEMORY;
int32 start = path->CountPoints() - 2;
int32 end = path->CountPoints() - 1;
int32 start = path->CountPoints() - 2;
int32 end = path->CountPoints() - 1;
path->SetInOutConnected(end, false);
path->SetPointOut(start, vector1);
path->SetPointIn(end, vector2);
path->SetInOutConnected(end, false);
path->SetPointOut(start, BPoint(x1, y1));
path->SetPointIn(end, BPoint(x2, y2));
break;
}
default:
break;
pointCount -= 3;
points += 6;
}
cmd = source.vertex(&x1, &y1);
}
path->CleanUp();
if (path->CountPoints() == 0) {
icon->Paths()->RemovePath(path);
shape->Paths()->RemovePath(path);
path->ReleaseReference();
}
return B_OK;
}
@@ -754,7 +192,7 @@
status_t
DocumentBuilder::_AddShape(path_attributes& attributes, bool outline,
DocumentBuilder::_AddShape(NSVGshape* svgShape, bool outline,
const Transformable& transform, Icon* icon)
{
Shape* shape = new (nothrow) Shape(NULL);
@@ -763,20 +201,43 @@
return B_NO_MEMORY;
}
if (AddPathsFromVertexSource(icon, shape, fPathStorage, attributes.index) < B_OK)
if (AddPathsFromVertexSource(icon, shape, svgShape) < B_OK)
printf("failed to convert from vertex source\n");
shape->multiply(attributes.transform);
shape->SetName(svgShape->id);
shape->Multiply(transform);
StrokeTransformer* stroke = NULL;
NSVGpaint* paint = NULL;
if (outline) {
stroke = new (nothrow) StrokeTransformer(shape->VertexSource());
paint = &svgShape->stroke;
if (stroke) {
stroke->width(attributes.stroke_width);
stroke->line_cap(attributes.line_cap);
stroke->line_join(attributes.line_join);
stroke->width(svgShape->strokeWidth);
switch(svgShape->strokeLineCap) {
case NSVG_CAP_BUTT:
stroke->line_cap(agg::butt_cap);
break;
case NSVG_CAP_ROUND:
stroke->line_cap(agg::round_cap);
break;
case NSVG_CAP_SQUARE:
stroke->line_cap(agg::square_cap);
break;
}
switch(svgShape->strokeLineJoin) {
case NSVG_JOIN_MITER:
stroke->line_join(agg::miter_join);
break;
case NSVG_JOIN_ROUND:
stroke->line_join(agg::round_join);
break;
case NSVG_JOIN_BEVEL:
stroke->line_join(agg::bevel_join);
break;
}
}
if (!shape->AddTransformer(stroke)) {
@@ -784,45 +245,83 @@
stroke = NULL;
}
} else {
}
Gradient* gradient = NULL;
SVGGradient* g = NULL;
const char* url = outline ? attributes.stroke_url : attributes.fill_url;
if (url[0] != 0) {
g = _FindGradient(url);
if (g != NULL)
gradient = g->GetGradient(shape->Bounds());
paint = &svgShape->fill;
#if 0
if (svgShape->fillRule == NSVG_FILLRULE_EVENODD)
shape->SetFillingRule(FILL_MODE_EVEN_ODD);
else
shape->SetFillingRule(FILL_MODE_NON_ZERO);
#endif
}
Gradient gradient(true);
rgb_color color;
switch(paint->type) {
case NSVG_PAINT_COLOR:
color.red = paint->color & 0xFF;
color.green = (paint->color >> 8) & 0xFF;
color.blue = (paint->color >> 16) & 0xFF;
color.alpha = (paint->color >> 24) & 0xFF;
break;
case NSVG_PAINT_LINEAR_GRADIENT:
{
gradient.SetType(GRADIENT_LINEAR);
agg::trans_affine baseTransform(0, 1.0/128.0, -1.0/128.0, 0,
-0.5, 0.5);
gradient.multiply(baseTransform);
break;
}
case NSVG_PAINT_RADIAL_GRADIENT:
{
gradient.SetType(GRADIENT_CIRCULAR);
agg::trans_affine baseTransform(0, 1.0/64.0, -1.0/64.0, 0,
0, 0);
gradient.multiply(baseTransform);
break;
}
}
ObjectDeleter<Gradient> gradientDeleter(gradient);
rgb_color color;
if (paint->type != NSVG_PAINT_COLOR) {
gradient.SetInterpolation(INTERPOLATION_LINEAR);
agg::trans_affine gradientTransform(
paint->gradient->xform[0], paint->gradient->xform[1],
paint->gradient->xform[2], paint->gradient->xform[3],
paint->gradient->xform[4], paint->gradient->xform[5]
);
gradient.multiply_inv(gradientTransform);
gradient.Multiply(*shape);
for (int i = 0; i < paint->gradient->nstops; i++) {
rgb_color stopColor;
stopColor.red = paint->gradient->stops[i].color & 0xFF;
stopColor.green = (paint->gradient->stops[i].color >> 8) & 0xFF;
stopColor.blue = (paint->gradient->stops[i].color >> 16) & 0xFF;
stopColor.alpha = (paint->gradient->stops[i].color >> 24) & 0xFF;
gradient.AddColor(stopColor, paint->gradient->stops[i].offset);
}
BGradient::ColorStop* step;
if (gradient && (step = gradient->ColorAt(0))) {
color.red = step->color.red;
color.green = step->color.green;
color.blue = step->color.blue;
} else {
if (outline) {
color.red = attributes.stroke_color.r;
color.green = attributes.stroke_color.g;
color.blue = attributes.stroke_color.b;
color.alpha = (uint8)(attributes.stroke_color.a * attributes.opacity);
} else {
color.red = attributes.fill_color.r;
color.green = attributes.fill_color.g;
color.blue = attributes.fill_color.b;
color.alpha = (uint8)(attributes.fill_color.a * attributes.opacity);
BGradient::ColorStop* step = gradient.ColorAt(0);
if (step) {
color.red = step->color.red;
color.green = step->color.green;
color.blue = step->color.blue;
color.alpha = step->color.alpha;
}
}
color.alpha = (uint8)(color.alpha * svgShape->opacity);
Style* style = new (nothrow) Style(color);
if (!style || !icon->Styles()->AddStyle(style)) {
delete style;
@@ -831,31 +330,22 @@
int32 pathCount = shape->Paths()->CountPaths();
for (int32 i = 0; i < pathCount; i++) {
VectorPath* path = shape->Paths()->PathAtFast(i);
path->ApplyTransform(*shape);
}
if (gradient)
gradient->Multiply(*shape);
int32 pathCount = shape->Paths()->CountPaths();
for (int32 i = 0; i < pathCount; i++) {
VectorPath* path = shape->Paths()->PathAtFast(i);
path->ApplyTransform(*shape);
}
if (stroke)
stroke->width(stroke->width() * shape->scale());
if (stroke)
stroke->width(stroke->width() * shape->scale());
shape->Reset();
if (paint->type != NSVG_PAINT_COLOR)
style->SetGradient(&gradient);
if (gradient) {
style->SetGradient(gradient);
style->SetName(g->ID());
}
shape->Reset();
shape->SetStyle(style);
return B_OK;
}
}
}
@@ -6,41 +6,17 @@
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef DOCUMENT_BUILD_H
#define DOCUMENT_BUILD_H
#include <stdio.h>
#include <List.h>
#include <Rect.h>
#include <String.h>
#include <agg_array.h>
#include <agg_color_rgba.h>
#include <agg_conv_transform.h>
#include <agg_conv_stroke.h>
#include <agg_conv_contour.h>
#include <agg_conv_curve.h>
#include <agg_path_storage.h>
#include <agg_rasterizer_scanline_aa.h>
#include "IconBuild.h"
#include "PathTokenizer.h"
#include "nanosvg.h"
class SVGImporter;
@@ -52,207 +28,33 @@
_USING_ICON_NAMESPACE
namespace agg {
namespace svg {
class SVGGradient;
struct path_attributes {
unsigned index;
rgba8 fill_color;
rgba8 stroke_color;
double opacity;
bool fill_flag;
bool stroke_flag;
bool even_odd_flag;
line_join_e line_join;
line_cap_e line_cap;
double miter_limit;
double stroke_width;
trans_affine transform;
char stroke_url[64];
char fill_url[64];
path_attributes() :
index (0),
fill_color (rgba(0,0,0)),
stroke_color (rgba(0,0,0)),
opacity (1.0),
fill_flag (true),
stroke_flag (false),
even_odd_flag (false),
line_join (miter_join),
line_cap (butt_cap),
miter_limit (4.0),
stroke_width (1.0),
transform ()
{
stroke_url[0] = 0;
fill_url[0] = 0;
}
path_attributes(const path_attributes& attr) :
index (attr.index),
fill_color (attr.fill_color),
stroke_color (attr.stroke_color),
opacity (attr.opacity),
fill_flag (attr.fill_flag),
stroke_flag (attr.stroke_flag),
even_odd_flag (attr.even_odd_flag),
line_join (attr.line_join),
line_cap (attr.line_cap),
miter_limit (attr.miter_limit),
stroke_width (attr.stroke_width),
transform (attr.transform)
{
sprintf(stroke_url, "%s", attr.stroke_url);
sprintf(fill_url, "%s", attr.fill_url);
}
path_attributes(const path_attributes& attr, unsigned idx) :
index (idx),
fill_color (attr.fill_color),
stroke_color (attr.stroke_color),
fill_flag (attr.fill_flag),
stroke_flag (attr.stroke_flag),
even_odd_flag (attr.even_odd_flag),
line_join (attr.line_join),
line_cap (attr.line_cap),
miter_limit (attr.miter_limit),
stroke_width (attr.stroke_width),
transform (attr.transform)
{
sprintf(stroke_url, "%s", attr.stroke_url);
sprintf(fill_url, "%s", attr.fill_url);
}
};
class DocumentBuilder {
public:
typedef pod_bvector<path_attributes> attr_storage;
DocumentBuilder();
void remove_all();
void begin_path();
void parse_path(PathTokenizer& tok);
void end_path();
void move_to(double x, double y, bool rel = false);
void line_to(double x, double y, bool rel = false);
void hline_to(double x, bool rel = false);
void vline_to(double y, bool rel = false);
void curve3(double x1, double y1,
double x, double y, bool rel = false);
void curve3(double x, double y, bool rel = false);
void curve4(double x1, double y1,
double x2, double y2,
double x, double y, bool rel = false);
void curve4(double x2, double y2,
double x, double y, bool rel = false);
void elliptical_arc(double rx, double ry,
double angle,
bool large_arc_flag,
bool sweep_flag,
double x, double y,
bool rel = false);
void close_subpath();
/* template<class VertexSource>
void add_path(VertexSource& vs,
unsigned path_id = 0,
bool solid_path = true)
{
fPathStorage.add_path(vs, path_id, solid_path);
}*/
DocumentBuilder(NSVGimage* image);
void SetTitle(const char* title);
void SetDimensions(uint32 width, uint32 height, BRect viewBox);
void push_attr();
void pop_attr();
void fill(const rgba8& f);
void stroke(const rgba8& s);
void even_odd(bool flag);
void stroke_width(double w);
void fill_none();
void fill_url(const char* url);
void stroke_none();
void stroke_url(const char* url);
void opacity(double op);
void fill_opacity(double op);
void stroke_opacity(double op);
void line_join(line_join_e join);
void line_cap(line_cap_e cap);
void miter_limit(double ml);
trans_affine& transform();
/*
void arrange_orientations()
{
fPathStorage.arrange_orientations_all_paths(path_flags_ccw);
}*/
unsigned operator [](unsigned idx)
{
fTransform = fAttributesStorage[idx].transform;
return fAttributesStorage[idx].index;
}
status_t GetIcon(Icon* icon,
SVGImporter* importer,
const char* fallbackName);
void StartGradient(bool radial = false);
void EndGradient();
SVGGradient* CurrentGradient() const
{ return fCurrentGradient; }
private:
void _AddGradient(SVGGradient* gradient);
SVGGradient* _GradientAt(int32 index) const;
SVGGradient* _FindGradient(const char* name) const;
status_t _AddShape(path_attributes& attributes,
status_t _AddShape(NSVGshape* svgShape,
bool outline,
const Transformable& transform,
Icon* icon);
path_attributes& cur_attr();
path_storage fPathStorage;
attr_storage fAttributesStorage;
attr_storage fAttributesStack;
trans_affine fTransform;
BList fGradients;
SVGGradient* fCurrentGradient;
uint32 fWidth;
uint32 fHeight;
BRect fViewBox;
BString fTitle;
};
}
}
NSVGimage* fSource;
};
#endif
@@ -1,157 +1,0 @@
/*
* Copyright 2006, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "PathTokenizer.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
namespace agg {
namespace svg {
const char PathTokenizer::sCommands[] = "+-MmZzLlHhVvCcSsQqTtAaFfPp";
const char PathTokenizer::sNumeric[] = ".Ee0123456789";
const char PathTokenizer::sSeparators[] = " ,\t\n\r";
PathTokenizer::PathTokenizer()
: fPath(0),
fLastNumber(0.0),
fLastCommand(0)
{
init_char_mask(fCommandsMask, sCommands);
init_char_mask(fNumericMask, sNumeric);
init_char_mask(fSeparatorsMask, sSeparators);
}
void
PathTokenizer::set_path_str(const char* str)
{
fPath = str;
fLastCommand = 0;
fLastNumber = 0.0;
}
bool
PathTokenizer::next()
{
if(fPath == 0) return false;
while (*fPath && !is_command(*fPath) && !isNumeric(*fPath)) {
if (!is_separator(*fPath)) {
char buf[100];
sprintf(buf, "PathTokenizer::next : Invalid Character %c", *fPath);
throw exception(buf);
}
fPath++;
}
if (*fPath == 0) return false;
if (is_command(*fPath)) {
if(*fPath == '-' || *fPath == '+')
{
return parse_number();
}
fLastCommand = *fPath++;
while(*fPath && is_separator(*fPath)) fPath++;
if(*fPath == 0) return true;
}
return parse_number();
}
double
PathTokenizer::next(char cmd)
{
if (!next()) throw exception("parse_path: Unexpected end of path");
if (last_command() != cmd) {
char buf[100];
sprintf(buf, "parse_path: Command %c: bad or missing parameters", cmd);
throw exception(buf);
}
return last_number();
}
void
PathTokenizer::init_char_mask(char* mask, const char* char_set)
{
memset(mask, 0, 256/8);
while (*char_set) {
unsigned c = unsigned(*char_set++) & 0xFF;
mask[c >> 3] |= 1 << (c & 7);
}
}
inline bool
PathTokenizer::contains(const char* mask, unsigned c) const
{
return (mask[(c >> 3) & (256 / 8 - 1)] & (1 << (c & 7))) != 0;
}
inline bool
PathTokenizer::is_command(unsigned c) const
{
return contains(fCommandsMask, c);
}
inline bool
PathTokenizer::isNumeric(unsigned c) const
{
return contains(fNumericMask, c);
}
inline bool
PathTokenizer::is_separator(unsigned c) const
{
return contains(fSeparatorsMask, c);
}
bool
PathTokenizer::parse_number()
{
char* end;
fLastNumber = strtod(fPath, &end);
fPath = end;
return true;
}
}
}
@@ -1,106 +1,0 @@
/*
* Copyright 2006, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef PATH_TOKENIZER_H
#define PATH_TOKENIZER_H
#include "SVGException.h"
namespace agg {
namespace svg {
class PathTokenizer {
public:
PathTokenizer();
void set_path_str(const char* str);
bool next();
double next(char cmd);
char last_command() const
{ return fLastCommand; }
double last_number() const
{ return fLastNumber; }
private:
static void init_char_mask(char* mask,
const char* char_set);
inline bool contains(const char* mask, unsigned c) const;
inline bool is_command(unsigned c) const;
inline bool isNumeric(unsigned c) const;
inline bool is_separator(unsigned c) const;
bool parse_number();
char fSeparatorsMask[256 / 8];
char fCommandsMask[256 / 8];
char fNumericMask[256 / 8];
const char* fPath;
double fLastNumber;
char fLastCommand;
static const char sCommands[];
static const char sNumeric[];
static const char sSeparators[];
};
}
}
#endif
@@ -1,199 +1,0 @@
/*
* Copyright 2006, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include "GradientTransformable.h"
#include "SVGGradients.h"
namespace agg {
namespace svg {
SVGGradient::SVGGradient()
: BMessage(),
fGradient(NULL),
fID(""),
fGradientUnits(UNSPECIFIED)
{
}
SVGGradient::~SVGGradient()
{
}
void
SVGGradient::SetID(const char* id)
{
fID = id;
}
const char*
SVGGradient::ID() const
{
return fID.String();
}
void
SVGGradient::AddStop(float offset, rgba8 color)
{
if (!fGradient)
fGradient = MakeGradient();
rgb_color c = { color.r, color.g, color.b, color.a };
fGradient->AddColor(c, offset);
}
void
SVGGradient::SetTransformation(const trans_affine& transform)
{
if (!fGradient)
fGradient = MakeGradient();
fGradient->multiply(transform);
}
Gradient*
SVGGradient::GetGradient(BRect objectBounds)
{
if (fGradient) {
Gradient* gradient = new Gradient(*fGradient);
IdentifyGradientUnits();
if (fGradientUnits == OBJECT_BOUNDING_BOX) {
gradient->FitToBounds(objectBounds);
}
return gradient;
}
return NULL;
}
void
SVGGradient::IdentifyGradientUnits()
{
if (fGradientUnits == UNSPECIFIED) {
const char* units;
if (FindString("gradientUnits", &units) >= B_OK) {
if (strcmp(units, "objectBoundingBox") == 0)
fGradientUnits = OBJECT_BOUNDING_BOX;
}
}
}
SVGLinearGradient::SVGLinearGradient()
: SVGGradient()
{
}
SVGLinearGradient::~SVGLinearGradient()
{
}
Gradient*
SVGLinearGradient::MakeGradient() const
{
Gradient* gradient = new Gradient(true);
gradient->SetType(GRADIENT_LINEAR);
gradient->SetInterpolation(INTERPOLATION_LINEAR);
BPoint start(-64.0, -64.0);
BPoint end(64.0, -64.0);
BString coordinate;
if (FindString("x1", &coordinate) >= B_OK)
start.x = atof(coordinate.String());
if (FindString("y1", &coordinate) >= B_OK)
start.y = atof(coordinate.String());
if (FindString("x2", &coordinate) >= B_OK)
end.x = atof(coordinate.String());
if (FindString("y2", &coordinate) >= B_OK)
end.y = atof(coordinate.String());
double parl[6];
parl[0] = start.x;
parl[1] = start.y;
parl[2] = end.x;
parl[3] = end.y;
parl[4] = end.x - (end.y - start.y);
parl[5] = end.y + (end.x - start.x);
trans_affine transform(-64.0, -64.0, 64.0, 64.0, parl);
gradient->multiply(transform);
return gradient;
}
SVGRadialGradient::SVGRadialGradient()
: SVGGradient()
{
}
SVGRadialGradient::~SVGRadialGradient()
{
}
Gradient*
SVGRadialGradient::MakeGradient() const
{
Gradient* gradient = new Gradient(true);
gradient->SetType(GRADIENT_CIRCULAR);
gradient->SetInterpolation(INTERPOLATION_LINEAR);
double cx = 0.0;
double cy = 0.0;
double r = 100.0;
BString value;
if (FindString("cx", &value) >= B_OK)
cx = atof(value.String());
if (FindString("cy", &value) >= B_OK)
cy = atof(value.String());
if (FindString("r", &value) >= B_OK)
r = atof(value.String());
double parl[6];
parl[0] = cx - r;
parl[1] = cy - r;
parl[2] = cx + r;
parl[3] = cy - r;
parl[4] = cx + r;
parl[5] = cy + r;
trans_affine transform(-64.0, -64.0, 64.0, 64.0, parl);
gradient->multiply(transform);
return gradient;
}
}
}
@@ -1,81 +1,0 @@
/*
* Copyright 2006-2007, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef SVG_GRADIENTS_H
#define SVG_GRADIENTS_H
#include <agg_color_rgba.h>
#include <agg_trans_affine.h>
#include <Message.h>
#include <String.h>
#include "IconBuild.h"
_BEGIN_ICON_NAMESPACE
class Gradient;
_END_ICON_NAMESPACE
namespace agg {
namespace svg {
class SVGGradient : public BMessage {
public:
SVGGradient();
virtual ~SVGGradient();
void SetID(const char* id);
const char* ID() const;
virtual void AddStop(float offset, rgba8 color);
void SetTransformation(const trans_affine& transform);
Gradient* GetGradient(BRect objectBounds);
protected:
virtual Gradient* MakeGradient() const = 0;
void IdentifyGradientUnits();
private:
enum {
UNSPECIFIED = 0,
USER_SPACE_ON_USE,
OBJECT_BOUNDING_BOX,
};
Gradient* fGradient;
BString fID;
uint32 fGradientUnits;
};
class SVGLinearGradient : public SVGGradient {
public:
SVGLinearGradient();
virtual ~SVGLinearGradient();
protected:
virtual Gradient* MakeGradient() const;
};
class SVGRadialGradient : public SVGGradient {
public:
SVGRadialGradient();
virtual ~SVGRadialGradient();
protected:
virtual Gradient* MakeGradient() const;
};
}
}
#endif
@@ -18,7 +18,7 @@
#include <Path.h>
#include "DocumentBuilder.h"
#include "SVGParser.h"
#include "nanosvg.h"
#undef B_TRANSLATION_CONTEXT
@@ -69,23 +69,22 @@
return B_ERROR;
}
try {
agg::svg::DocumentBuilder builder;
agg::svg::Parser parser(builder);
parser.parse(path.Path());
ret = builder.GetIcon(icon, this, ref->name);
} catch(agg::svg::exception& e) {
NSVGimage* svg = nsvgParseFromFile(path.Path(), "px", 96);
if (svg == NULL) {
char error[1024];
sprintf(error, B_TRANSLATE("Failed to open the file '%s' as "
"an SVG document.\n\n"
"Error: %s"), ref->name, e.msg());
"an SVG document.\n\n"), ref->name);
BAlert* alert = new BAlert(B_TRANSLATE("load error"),
error, B_TRANSLATE("OK"), NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
error, B_TRANSLATE("OK"), NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go(NULL);
ret = B_ERROR;
return B_ERROR;
}
DocumentBuilder builder(svg);
ret = builder.GetIcon(icon, this, ref->name);
nsvgDelete(svg);
return ret;
}
@@ -1,1152 +1,0 @@
/*
* Copyright 2006, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "SVGParser.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <expat.h>
#include "SVGGradients.h"
namespace agg {
namespace svg {
struct named_color
{
char name[22];
int8u r, g, b, a;
};
named_color colors[] =
{
{ "aliceblue",240,248,255, 255 },
{ "antiquewhite",250,235,215, 255 },
{ "aqua",0,255,255, 255 },
{ "aquamarine",127,255,212, 255 },
{ "azure",240,255,255, 255 },
{ "beige",245,245,220, 255 },
{ "bisque",255,228,196, 255 },
{ "black",0,0,0, 255 },
{ "blanchedalmond",255,235,205, 255 },
{ "blue",0,0,255, 255 },
{ "blueviolet",138,43,226, 255 },
{ "brown",165,42,42, 255 },
{ "burlywood",222,184,135, 255 },
{ "cadetblue",95,158,160, 255 },
{ "chartreuse",127,255,0, 255 },
{ "chocolate",210,105,30, 255 },
{ "coral",255,127,80, 255 },
{ "cornflowerblue",100,149,237, 255 },
{ "cornsilk",255,248,220, 255 },
{ "crimson",220,20,60, 255 },
{ "cyan",0,255,255, 255 },
{ "darkblue",0,0,139, 255 },
{ "darkcyan",0,139,139, 255 },
{ "darkgoldenrod",184,134,11, 255 },
{ "darkgray",169,169,169, 255 },
{ "darkgreen",0,100,0, 255 },
{ "darkgrey",169,169,169, 255 },
{ "darkkhaki",189,183,107, 255 },
{ "darkmagenta",139,0,139, 255 },
{ "darkolivegreen",85,107,47, 255 },
{ "darkorange",255,140,0, 255 },
{ "darkorchid",153,50,204, 255 },
{ "darkred",139,0,0, 255 },
{ "darksalmon",233,150,122, 255 },
{ "darkseagreen",143,188,143, 255 },
{ "darkslateblue",72,61,139, 255 },
{ "darkslategray",47,79,79, 255 },
{ "darkslategrey",47,79,79, 255 },
{ "darkturquoise",0,206,209, 255 },
{ "darkviolet",148,0,211, 255 },
{ "deeppink",255,20,147, 255 },
{ "deepskyblue",0,191,255, 255 },
{ "dimgray",105,105,105, 255 },
{ "dimgrey",105,105,105, 255 },
{ "dodgerblue",30,144,255, 255 },
{ "firebrick",178,34,34, 255 },
{ "floralwhite",255,250,240, 255 },
{ "forestgreen",34,139,34, 255 },
{ "fuchsia",255,0,255, 255 },
{ "gainsboro",220,220,220, 255 },
{ "ghostwhite",248,248,255, 255 },
{ "gold",255,215,0, 255 },
{ "goldenrod",218,165,32, 255 },
{ "gray",128,128,128, 255 },
{ "green",0,128,0, 255 },
{ "greenyellow",173,255,47, 255 },
{ "grey",128,128,128, 255 },
{ "honeydew",240,255,240, 255 },
{ "hotpink",255,105,180, 255 },
{ "indianred",205,92,92, 255 },
{ "indigo",75,0,130, 255 },
{ "ivory",255,255,240, 255 },
{ "khaki",240,230,140, 255 },
{ "lavender",230,230,250, 255 },
{ "lavenderblush",255,240,245, 255 },
{ "lawngreen",124,252,0, 255 },
{ "lemonchiffon",255,250,205, 255 },
{ "lightblue",173,216,230, 255 },
{ "lightcoral",240,128,128, 255 },
{ "lightcyan",224,255,255, 255 },
{ "lightgoldenrodyellow",250,250,210, 255 },
{ "lightgray",211,211,211, 255 },
{ "lightgreen",144,238,144, 255 },
{ "lightgrey",211,211,211, 255 },
{ "lightpink",255,182,193, 255 },
{ "lightsalmon",255,160,122, 255 },
{ "lightseagreen",32,178,170, 255 },
{ "lightskyblue",135,206,250, 255 },
{ "lightslategray",119,136,153, 255 },
{ "lightslategrey",119,136,153, 255 },
{ "lightsteelblue",176,196,222, 255 },
{ "lightyellow",255,255,224, 255 },
{ "lime",0,255,0, 255 },
{ "limegreen",50,205,50, 255 },
{ "linen",250,240,230, 255 },
{ "magenta",255,0,255, 255 },
{ "maroon",128,0,0, 255 },
{ "mediumaquamarine",102,205,170, 255 },
{ "mediumblue",0,0,205, 255 },
{ "mediumorchid",186,85,211, 255 },
{ "mediumpurple",147,112,219, 255 },
{ "mediumseagreen",60,179,113, 255 },
{ "mediumslateblue",123,104,238, 255 },
{ "mediumspringgreen",0,250,154, 255 },
{ "mediumturquoise",72,209,204, 255 },
{ "mediumvioletred",199,21,133, 255 },
{ "midnightblue",25,25,112, 255 },
{ "mintcream",245,255,250, 255 },
{ "mistyrose",255,228,225, 255 },
{ "moccasin",255,228,181, 255 },
{ "navajowhite",255,222,173, 255 },
{ "navy",0,0,128, 255 },
{ "oldlace",253,245,230, 255 },
{ "olive",128,128,0, 255 },
{ "olivedrab",107,142,35, 255 },
{ "orange",255,165,0, 255 },
{ "orangered",255,69,0, 255 },
{ "orchid",218,112,214, 255 },
{ "palegoldenrod",238,232,170, 255 },
{ "palegreen",152,251,152, 255 },
{ "paleturquoise",175,238,238, 255 },
{ "palevioletred",219,112,147, 255 },
{ "papayawhip",255,239,213, 255 },
{ "peachpuff",255,218,185, 255 },
{ "peru",205,133,63, 255 },
{ "pink",255,192,203, 255 },
{ "plum",221,160,221, 255 },
{ "powderblue",176,224,230, 255 },
{ "purple",128,0,128, 255 },
{ "red",255,0,0, 255 },
{ "rosybrown",188,143,143, 255 },
{ "royalblue",65,105,225, 255 },
{ "saddlebrown",139,69,19, 255 },
{ "salmon",250,128,114, 255 },
{ "sandybrown",244,164,96, 255 },
{ "seagreen",46,139,87, 255 },
{ "seashell",255,245,238, 255 },
{ "sienna",160,82,45, 255 },
{ "silver",192,192,192, 255 },
{ "skyblue",135,206,235, 255 },
{ "slateblue",106,90,205, 255 },
{ "slategray",112,128,144, 255 },
{ "slategrey",112,128,144, 255 },
{ "snow",255,250,250, 255 },
{ "springgreen",0,255,127, 255 },
{ "steelblue",70,130,180, 255 },
{ "tan",210,180,140, 255 },
{ "teal",0,128,128, 255 },
{ "thistle",216,191,216, 255 },
{ "tomato",255,99,71, 255 },
{ "turquoise",64,224,208, 255 },
{ "violet",238,130,238, 255 },
{ "wheat",245,222,179, 255 },
{ "white",255,255,255, 255 },
{ "whitesmoke",245,245,245, 255 },
{ "yellow",255,255,0, 255 },
{ "yellowgreen",154,205,50, 255 },
{ "zzzzzzzzzzz",0,0,0, 0 }
};
int
cmp_color(const void* p1, const void* p2)
{
return strcmp(((named_color*)p1)->name, ((named_color*)p2)->name);
}
rgba8
parse_color(const char* str)
{
while(*str == ' ') ++str;
if (*str == '#') {
str++;
int32 length = strlen(str);
unsigned c = 0;
if (length == 3) {
char expanded[7];
expanded[0] = *str;
expanded[1] = *str++;
expanded[2] = *str;
expanded[3] = *str++;
expanded[4] = *str;
expanded[5] = *str++;
expanded[6] = 0;
sscanf(expanded, "%x", &c);
} else {
sscanf(str, "%x", &c);
}
return rgb8_packed(c);
} else {
named_color c;
unsigned len = strlen(str);
if(len > sizeof(c.name) - 1)
{
throw exception("parse_color: Invalid color name '%s'", str);
}
strcpy(c.name, str);
const void* p = bsearch(&c,
colors,
sizeof(colors) / sizeof(colors[0]),
sizeof(colors[0]),
cmp_color);
if(p == 0)
{
throw exception("parse_color: Invalid color name '%s'", str);
}
const named_color* pc = (const named_color*)p;
return rgba8(pc->r, pc->g, pc->b, pc->a);
}
}
double
parse_double(const char* str)
{
while(*str == ' ') ++str;
double value = atof(str);
int32 length = strlen(str);
if (str[length - 1] == '%')
value /= 100.0;
return value;
}
char*
parse_url(const char* str)
{
const char* begin = str;
while (*begin != '#')
begin++;
begin++;
const char* end = begin;
while (*end != ')')
end++;
end--;
int32 length = end - begin + 2;
char* result = new char[length];
memcpy(result, begin, length - 1);
result[length - 1] = 0;
return result;
}
Parser::Parser(DocumentBuilder& builder)
: fBuilder(builder),
fPathTokenizer(),
fBuffer(new char[buf_size]),
fTitle(new char[256]),
fTitleLength(0),
fTitleFlag(false),
fPathFlag(false),
fAttrName(new char[128]),
fAttrValue(new char[1024]),
fAttrNameLength(127),
fAttrValueLength(1023),
fTagsIgnored(false)
{
fTitle[0] = 0;
}
Parser::~Parser()
{
delete[] fAttrValue;
delete[] fAttrName;
delete[] fBuffer;
delete[] fTitle;
}
void
Parser::parse(const char* pathToFile)
{
char msg[1024];
XML_Parser p = XML_ParserCreate(NULL);
if (p == 0) {
throw exception("Couldn't allocate memory for Parser");
}
XML_SetUserData(p, this);
XML_SetElementHandler(p, start_element, end_element);
XML_SetCharacterDataHandler(p, content);
FILE* fd = fopen(pathToFile, "r");
if (fd == 0) {
sprintf(msg, "Couldn't open file %s", pathToFile);
XML_ParserFree(p);
throw exception(msg);
}
bool done = false;
do {
size_t len = fread(fBuffer, 1, buf_size, fd);
done = len < buf_size;
if (!XML_Parse(p, fBuffer, len, done)) {
sprintf(msg, "%s at line %ld\n",
XML_ErrorString(XML_GetErrorCode(p)),
XML_GetCurrentLineNumber(p));
fclose(fd);
XML_ParserFree(p);
throw exception(msg);
}
} while (!done);
fclose(fd);
XML_ParserFree(p);
char* ts = fTitle;
while (*ts) {
if (*ts < ' ') *ts = ' ';
++ts;
}
}
void
Parser::start_element(void* data, const char* el, const char** attr)
{
Parser& self = *(Parser*)data;
if (strcmp(el, "svg") == 0)
{
self.parse_svg(attr);
}
else
if (strcmp(el, "title") == 0)
{
self.fTitleFlag = true;
}
else
if (strcmp(el, "g") == 0)
{
self.fBuilder.push_attr();
self.parse_attr(attr);
}
else
if (strcmp(el, "path") == 0)
{
if (self.fPathFlag) {
throw exception("start_element: Nested path");
}
self.fBuilder.begin_path();
self.parse_path(attr);
self.fBuilder.end_path();
self.fPathFlag = true;
}
else
if (strcmp(el, "circle") == 0)
{
self.parse_circle(attr);
}
else
if (strcmp(el, "ellipse") == 0)
{
self.parse_ellipse(attr);
}
else
if (strcmp(el, "rect") == 0)
{
self.parse_rect(attr);
}
else
if (strcmp(el, "line") == 0)
{
self.parse_line(attr);
}
else
if (strcmp(el, "polyline") == 0)
{
self.parse_poly(attr, false);
}
else
if (strcmp(el, "polygon") == 0)
{
self.parse_poly(attr, true);
}
else
if (strcmp(el, "linearGradient") == 0 || strcmp(el, "radialGradient") == 0)
{
self.parse_gradient(attr, strcmp(el, "radialGradient") == 0);
}
else
if (strcmp(el, "stop") == 0)
{
self.parse_gradient_stop(attr);
}
else
{
fprintf(stderr, "SVGParser igoring tag: \"%s\"\n", el);
self.fTagsIgnored = true;
}
}
void
Parser::end_element(void* data, const char* el)
{
Parser& self = *(Parser*)data;
if (strcmp(el, "title") == 0)
{
self.fTitleFlag = false;
self.fBuilder.SetTitle(self.fTitle);
}
else
if (strcmp(el, "g") == 0)
{
self.fBuilder.pop_attr();
}
else
if (strcmp(el, "path") == 0)
{
self.fPathFlag = false;
}
else
if (strcmp(el, "linearGradient") == 0 || strcmp(el, "radialGradient") == 0)
{
self.fBuilder.EndGradient();
}
}
void
Parser::content(void* data, const char* s, int len)
{
Parser& self = *(Parser*)data;
if(self.fTitleFlag)
{
if(len + self.fTitleLength > 255) len = 255 - self.fTitleLength;
if(len > 0)
{
memcpy(self.fTitle + self.fTitleLength, s, len);
self.fTitleLength += len;
self.fTitle[self.fTitleLength] = 0;
}
}
}
void Parser::parse_svg(const char** attr)
{
double width = 0.0;
double height = 0.0;
BRect viewBox(0.0, 0.0, -1.0, -1.0);
for (int i = 0; attr[i]; i += 2) {
if (strcmp(attr[i], "width") == 0)
{
width = parse_double(attr[i + 1]);
}
else
if (strcmp(attr[i], "height") == 0)
{
height = parse_double(attr[i + 1]);
}
else
if (strcmp(attr[i], "viewBox") == 0)
{
fPathTokenizer.set_path_str(attr[i + 1]);
if(!fPathTokenizer.next())
{
throw exception("parse_svg (viewBox): Too few coordinates");
}
viewBox.left = fPathTokenizer.last_number();
if(!fPathTokenizer.next())
{
throw exception("parse_svg (viewBox): Too few coordinates");
}
viewBox.top = fPathTokenizer.last_number();
if(!fPathTokenizer.next())
{
throw exception("parse_svg (viewBox): Too few coordinates");
}
viewBox.right = fPathTokenizer.last_number();
if(!fPathTokenizer.next())
{
throw exception("parse_svg (viewBox): Too few coordinates");
}
viewBox.bottom = fPathTokenizer.last_number();
}
}
if (width >= 0.0 && height >= 0.0) {
fBuilder.SetDimensions((uint32)ceil(width), (uint32)ceil(height), viewBox);
} else {
throw exception("parse_svg: Invalid width or height\n");
}
}
void Parser::parse_attr(const char** attr)
{
for (int i = 0; attr[i]; i += 2) {
if (strcmp(attr[i], "style") == 0) {
parse_style(attr[i + 1]);
} else {
parse_attr(attr[i], attr[i + 1]);
}
}
}
void Parser::parse_path(const char** attr)
{
int i;
for(i = 0; attr[i]; i += 2)
{
if(strcmp(attr[i], "d") == 0)
{
fPathTokenizer.set_path_str(attr[i + 1]);
fBuilder.parse_path(fPathTokenizer);
}
else
{
const char* tmp[4];
tmp[0] = attr[i];
tmp[1] = attr[i + 1];
tmp[2] = 0;
tmp[3] = 0;
parse_attr(tmp);
}
}
}
bool
Parser::parse_attr(const char* name, const char* value)
{
if(strcmp(name, "style") == 0) {
parse_style(value);
} else
if(strcmp(name, "opacity") == 0) {
fBuilder.opacity(parse_double(value));
} else
if(strcmp(name, "fill") == 0) {
if(strcmp(value, "none") == 0) {
fBuilder.fill_none();
} else if (strncmp(value, "url", 3) == 0) {
char* url = parse_url(value);
fBuilder.fill_url(url);
delete[] url;
} else {
fBuilder.fill(parse_color(value));
}
} else
if(strcmp(name, "fill-opacity") == 0) {
fBuilder.fill_opacity(parse_double(value));
} else
if(strcmp(name, "fill-rule") == 0) {
fBuilder.even_odd(strcmp(value, "evenodd") == 0);
} else
if(strcmp(name, "stroke") == 0) {
if(strcmp(value, "none") == 0) {
fBuilder.stroke_none();
} else if (strncmp(value, "url", 3) == 0) {
char* url = parse_url(value);
fBuilder.stroke_url(url);
delete[] url;
} else {
fBuilder.stroke(parse_color(value));
}
} else
if(strcmp(name, "stroke-width") == 0) {
fBuilder.stroke_width(parse_double(value));
} else
if(strcmp(name, "stroke-linecap") == 0) {
if(strcmp(value, "butt") == 0) fBuilder.line_cap(butt_cap);
else if(strcmp(value, "round") == 0) fBuilder.line_cap(round_cap);
else if(strcmp(value, "square") == 0) fBuilder.line_cap(square_cap);
} else
if(strcmp(name, "stroke-linejoin") == 0) {
if(strcmp(value, "miter") == 0) fBuilder.line_join(miter_join);
else if(strcmp(value, "round") == 0) fBuilder.line_join(round_join);
else if(strcmp(value, "bevel") == 0) fBuilder.line_join(bevel_join);
} else
if(strcmp(name, "stroke-miterlimit") == 0) {
fBuilder.miter_limit(parse_double(value));
} else
if(strcmp(name, "stroke-opacity") == 0) {
fBuilder.stroke_opacity(parse_double(value));
} else
if(strcmp(name, "transform") == 0) {
fBuilder.transform().premultiply(parse_transform(value));
} else
if (strcmp(name, "stop-color") == 0) {
fGradientStopColor = parse_color(value);
} else
if (strcmp(name, "stop-opacity") == 0) {
fGradientStopColor.opacity(parse_double(value));
}
else
{
return false;
}
return true;
}
void Parser::copy_name(const char* start, const char* end)
{
unsigned len = unsigned(end - start);
if(fAttrNameLength == 0 || len > fAttrNameLength)
{
delete [] fAttrName;
fAttrName = new char[len + 1];
fAttrNameLength = len;
}
if(len) memcpy(fAttrName, start, len);
fAttrName[len] = 0;
}
void Parser::copy_value(const char* start, const char* end)
{
unsigned len = unsigned(end - start);
if(fAttrValueLength == 0 || len > fAttrValueLength)
{
delete [] fAttrValue;
fAttrValue = new char[len + 1];
fAttrValueLength = len;
}
if(len) memcpy(fAttrValue, start, len);
fAttrValue[len] = 0;
}
bool Parser::parse_name_value(const char* nv_start, const char* nv_end)
{
const char* str = nv_start;
while(str < nv_end && *str != ':') ++str;
const char* val = str;
while(str > nv_start &&
(*str == ':' || isspace(*str))) --str;
++str;
copy_name(nv_start, str);
while(val < nv_end && (*val == ':' || isspace(*val))) ++val;
copy_value(val, nv_end);
return parse_attr(fAttrName, fAttrValue);
}
void Parser::parse_style(const char* str)
{
while(*str)
{
while(*str && isspace(*str)) ++str;
const char* nv_start = str;
while(*str && *str != ';') ++str;
const char* nv_end = str;
while(nv_end > nv_start &&
(*nv_end == ';' || isspace(*nv_end))) --nv_end;
++nv_end;
parse_name_value(nv_start, nv_end);
if(*str) ++str;
}
}
void
Parser::parse_circle(const char** attr)
{
int i;
double cx = 0.0;
double cy = 0.0;
double r = 0.0;
fBuilder.begin_path();
for(i = 0; attr[i]; i += 2) {
if (!parse_attr(attr[i], attr[i + 1])) {
if(strcmp(attr[i], "cx") == 0) cx = parse_double(attr[i + 1]);
if(strcmp(attr[i], "cy") == 0) cy = parse_double(attr[i + 1]);
if(strcmp(attr[i], "r") == 0) r = parse_double(attr[i + 1]);
}
}
if (r != 0.0) {
if (r < 0.0) throw exception("parse_circle: Invalid radius: %f", r);
fBuilder.move_to(cx, cy - r);
fBuilder.curve4(cx + r * 0.56, cy - r,
cx + r, cy - r * 0.56,
cx + r, cy);
fBuilder.curve4(cx + r, cy + r * 0.56,
cx + r * 0.56, cy + r,
cx, cy + r);
fBuilder.curve4(cx - r * 0.56, cy + r,
cx - r, cy + r * 0.56,
cx - r, cy);
fBuilder.curve4(cx - r, cy - r * 0.56,
cx - r * 0.56, cy - r,
cx, cy - r);
fBuilder.close_subpath();
}
fBuilder.end_path();
}
void
Parser::parse_ellipse(const char** attr)
{
int i;
double cx = 0.0;
double cy = 0.0;
double rx = 0.0;
double ry = 0.0;
fBuilder.begin_path();
for(i = 0; attr[i]; i += 2) {
if (!parse_attr(attr[i], attr[i + 1])) {
if(strcmp(attr[i], "cx") == 0) cx = parse_double(attr[i + 1]);
if(strcmp(attr[i], "cy") == 0) cy = parse_double(attr[i + 1]);
if(strcmp(attr[i], "rx") == 0) rx = parse_double(attr[i + 1]);
if(strcmp(attr[i], "ry") == 0) ry = parse_double(attr[i + 1]);
}
}
if (rx != 0.0 && ry != 0.0) {
if (rx < 0.0) throw exception("parse_ellipse: Invalid x-radius: %f", rx);
if (ry < 0.0) throw exception("parse_ellipse: Invalid y-radius: %f", ry);
fBuilder.move_to(cx, cy - ry);
fBuilder.curve4(cx + rx * 0.56, cy - ry,
cx + rx, cy - ry * 0.56,
cx + rx, cy);
fBuilder.curve4(cx + rx, cy + ry * 0.56,
cx + rx * 0.56, cy + ry,
cx, cy + ry);
fBuilder.curve4(cx - rx * 0.56, cy + ry,
cx - rx, cy + ry * 0.56,
cx - rx, cy);
fBuilder.curve4(cx - rx, cy - ry * 0.56,
cx - rx * 0.56, cy - ry,
cx, cy - ry);
fBuilder.close_subpath();
}
fBuilder.end_path();
}
void
Parser::parse_rect(const char** attr)
{
int i;
double x = 0.0;
double y = 0.0;
double w = 0.0;
double h = 0.0;
fBuilder.begin_path();
for(i = 0; attr[i]; i += 2)
{
if(!parse_attr(attr[i], attr[i + 1]))
{
if(strcmp(attr[i], "x") == 0) x = parse_double(attr[i + 1]);
if(strcmp(attr[i], "y") == 0) y = parse_double(attr[i + 1]);
if(strcmp(attr[i], "width") == 0) w = parse_double(attr[i + 1]);
if(strcmp(attr[i], "height") == 0) h = parse_double(attr[i + 1]);
}
}
if(w != 0.0 && h != 0.0)
{
if(w < 0.0) throw exception("parse_rect: Invalid width: %f", w);
if(h < 0.0) throw exception("parse_rect: Invalid height: %f", h);
fBuilder.move_to(x, y);
fBuilder.line_to(x + w, y);
fBuilder.line_to(x + w, y + h);
fBuilder.line_to(x, y + h);
fBuilder.close_subpath();
}
fBuilder.end_path();
}
void
Parser::parse_line(const char** attr)
{
int i;
double x1 = 0.0;
double y1 = 0.0;
double x2 = 0.0;
double y2 = 0.0;
fBuilder.begin_path();
for(i = 0; attr[i]; i += 2)
{
if(!parse_attr(attr[i], attr[i + 1]))
{
if(strcmp(attr[i], "x1") == 0) x1 = parse_double(attr[i + 1]);
if(strcmp(attr[i], "y1") == 0) y1 = parse_double(attr[i + 1]);
if(strcmp(attr[i], "x2") == 0) x2 = parse_double(attr[i + 1]);
if(strcmp(attr[i], "y2") == 0) y2 = parse_double(attr[i + 1]);
}
}
fBuilder.move_to(x1, y1);
fBuilder.line_to(x2, y2);
fBuilder.end_path();
}
void
Parser::parse_poly(const char** attr, bool close_flag)
{
int i;
double x = 0.0;
double y = 0.0;
fBuilder.begin_path();
for (i = 0; attr[i]; i += 2) {
if (!parse_attr(attr[i], attr[i + 1])) {
if (strcmp(attr[i], "points") == 0) {
fPathTokenizer.set_path_str(attr[i + 1]);
if (!fPathTokenizer.next())
throw exception("parse_poly: Too few coordinates");
x = fPathTokenizer.last_number();
if (!fPathTokenizer.next())
throw exception("parse_poly: Too few coordinates");
y = fPathTokenizer.last_number();
fBuilder.move_to(x, y);
while (fPathTokenizer.next()) {
x = fPathTokenizer.last_number();
if (!fPathTokenizer.next())
throw exception("parse_poly: Odd number of coordinates");
y = fPathTokenizer.last_number();
fBuilder.line_to(x, y);
}
}
}
}
if (close_flag)
fBuilder.close_subpath();
fBuilder.end_path();
}
trans_affine
Parser::parse_transform(const char* str)
{
trans_affine transform;
while (*str) {
if (islower(*str)) {
if (strncmp(str, "matrix", 6) == 0) str += parse_matrix(str, transform); else
if (strncmp(str, "translate", 9) == 0) str += parse_translate(str, transform); else
if (strncmp(str, "rotate", 6) == 0) str += parse_rotate(str, transform); else
if (strncmp(str, "scale", 5) == 0) str += parse_scale(str, transform); else
if (strncmp(str, "skewX", 5) == 0) str += parse_skew_x(str, transform); else
if (strncmp(str, "skewY", 5) == 0) str += parse_skew_y(str, transform); else
{
++str;
}
}
else
{
++str;
}
}
return transform;
}
void
Parser::parse_gradient(const char** attr, bool radial)
{
fBuilder.StartGradient(radial);
for (int32 i = 0; attr[i]; i += 2)
{
/* if(!parse_attr(attr[i], attr[i + 1]))
{*/
if (strcmp(attr[i], "id") == 0)
fBuilder.CurrentGradient()->SetID(attr[i + 1]);
else if(strcmp(attr[i], "gradientTransform") == 0) {
fBuilder.CurrentGradient()->SetTransformation(parse_transform(attr[i + 1]));
} else
fBuilder.CurrentGradient()->AddString(attr[i], attr[i + 1]);
}
}
void
Parser::parse_gradient_stop(const char** attr)
{
float offset = 0.0;
rgba8 color;
for (int32 i = 0; attr[i]; i += 2) {
if (strcmp(attr[i], "offset") == 0) {
offset = parse_double(attr[i + 1]);
} else
if (strcmp(attr[i], "style") == 0) {
parse_style(attr[i + 1]);
color = fGradientStopColor;
} else
if (strcmp(attr[i], "stop-color") == 0) {
color = parse_color(attr[i + 1]);
} else
if (strcmp(attr[i], "stop-opacity") == 0) {
color.opacity(parse_double(attr[i + 1]));
}
}
if (SVGGradient* gradient = fBuilder.CurrentGradient()) {
gradient->AddStop(offset, color);
} else {
throw exception("parse_gradient_stop() outside of gradient tag!\n");
}
}
static bool
is_numeric(char c)
{
return strchr("0123456789+-.eE", c) != 0;
}
static unsigned
parse_transform_args(const char* str,
double* args,
unsigned max_na,
unsigned* na)
{
*na = 0;
const char* ptr = str;
while(*ptr && *ptr != '(') ++ptr;
if(*ptr == 0)
{
throw exception("parse_transform_args: Invalid syntax");
}
const char* end = ptr;
while(*end && *end != ')') ++end;
if(*end == 0)
{
throw exception("parse_transform_args: Invalid syntax");
}
while(ptr < end)
{
if(is_numeric(*ptr))
{
if(*na >= max_na)
{
throw exception("parse_transform_args: Too many arguments");
}
args[(*na)++] = atof(ptr);
while(ptr < end && is_numeric(*ptr)) ++ptr;
}
else
{
++ptr;
}
}
return unsigned(end - str);
}
unsigned
Parser::parse_matrix(const char* str, trans_affine& transform)
{
double args[6];
unsigned na = 0;
unsigned len = parse_transform_args(str, args, 6, &na);
if(na != 6)
{
throw exception("parse_matrix: Invalid number of arguments");
}
transform.premultiply(trans_affine(args[0], args[1], args[2], args[3], args[4], args[5]));
return len;
}
unsigned
Parser::parse_translate(const char* str, trans_affine& transform)
{
double args[2];
unsigned na = 0;
unsigned len = parse_transform_args(str, args, 2, &na);
if(na == 1) args[1] = 0.0;
transform.premultiply(trans_affine_translation(args[0], args[1]));
return len;
}
unsigned
Parser::parse_rotate(const char* str, trans_affine& transform)
{
double args[3];
unsigned na = 0;
unsigned len = parse_transform_args(str, args, 3, &na);
if(na == 1)
{
transform.premultiply(trans_affine_rotation(deg2rad(args[0])));
}
else if(na == 3)
{
trans_affine t = trans_affine_translation(-args[1], -args[2]);
t *= trans_affine_rotation(deg2rad(args[0]));
t *= trans_affine_translation(args[1], args[2]);
transform.premultiply(t);
}
else
{
throw exception("parse_rotate: Invalid number of arguments");
}
return len;
}
unsigned Parser::parse_scale(const char* str, trans_affine& transform)
{
double args[2];
unsigned na = 0;
unsigned len = parse_transform_args(str, args, 2, &na);
if(na == 1) args[1] = args[0];
transform.premultiply(trans_affine_scaling(args[0], args[1]));
return len;
}
unsigned
Parser::parse_skew_x(const char* str, trans_affine& transform)
{
double arg;
unsigned na = 0;
unsigned len = parse_transform_args(str, &arg, 1, &na);
transform.premultiply(trans_affine_skewing(deg2rad(arg), 0.0));
return len;
}
unsigned
Parser::parse_skew_y(const char* str, trans_affine& transform)
{
double arg;
unsigned na = 0;
unsigned len = parse_transform_args(str, &arg, 1, &na);
transform.premultiply(trans_affine_skewing(0.0, deg2rad(arg)));
return len;
}
}
}
@@ -1,105 +1,0 @@
/*
* Copyright 2006, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#ifndef SVG_PARSER_H
#define SVG_PARSER_H
#include "PathTokenizer.h"
#include "DocumentBuilder.h"
namespace agg {
namespace svg {
class Parser {
enum { buf_size = BUFSIZ };
public:
Parser(DocumentBuilder& builder);
virtual ~Parser();
void parse(const char* pathToFile);
const char* title() const
{ return fTitle; }
bool TagsIgnored() const
{ return fTagsIgnored; }
private:
static void start_element(void* data, const char* el,
const char** attr);
static void end_element(void* data, const char* el);
static void content(void* data, const char* s, int len);
void parse_svg(const char** attr);
void parse_attr(const char** attr);
void parse_path(const char** attr);
void parse_poly(const char** attr, bool close_flag);
void parse_circle(const char** attr);
void parse_ellipse(const char** attr);
void parse_rect(const char** attr);
void parse_line(const char** attr);
void parse_style(const char* str);
trans_affine parse_transform(const char* str);
void parse_gradient(const char** attr, bool radial);
void parse_gradient_stop(const char** attr);
unsigned parse_matrix(const char* str, trans_affine& transform);
unsigned parse_translate(const char* str, trans_affine& transform);
unsigned parse_rotate(const char* str, trans_affine& transform);
unsigned parse_scale(const char* str, trans_affine& transform);
unsigned parse_skew_x(const char* str, trans_affine& transform);
unsigned parse_skew_y(const char* str, trans_affine& transform);
bool parse_attr(const char* name,
const char* value);
bool parse_name_value(const char* nv_start,
const char* nv_end);
void copy_name(const char* start, const char* end);
void copy_value(const char* start, const char* end);
private:
DocumentBuilder& fBuilder;
PathTokenizer fPathTokenizer;
char* fBuffer;
char* fTitle;
unsigned fTitleLength;
bool fTitleFlag;
bool fPathFlag;
char* fAttrName;
char* fAttrValue;
unsigned fAttrNameLength;
unsigned fAttrValueLength;
rgba8 fGradientStopColor;
bool fTagsIgnored;
};
}
}
#endif
@@ -1,0 +1,2975 @@
/*
* Copyright (c) 2013-14 Mikko Mononen memon@inside.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
* Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http:
*
* Arc calculation code based on canvg (https:
*
* Bounding box calculation based on http:
*
*/
#ifndef NANOSVG_H
#define NANOSVG_H
#ifndef NANOSVG_CPLUSPLUS
#ifdef __cplusplus
extern "C" {
#endif
#endif
/* Example Usage:
NSVGimage* image;
image = nsvgParseFromFile("test.svg", "px", 96);
printf("size: %f x %f\n", image->width, image->height);
for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {
for (NSVGpath *path = shape->paths; path != NULL; path = path->next) {
for (int i = 0; i < path->npts-1; i += 3) {
float* p = &path->pts[i*2];
drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]);
}
}
}
nsvgDelete(image);
*/
enum NSVGpaintType {
NSVG_PAINT_NONE = 0,
NSVG_PAINT_COLOR = 1,
NSVG_PAINT_LINEAR_GRADIENT = 2,
NSVG_PAINT_RADIAL_GRADIENT = 3
};
enum NSVGspreadType {
NSVG_SPREAD_PAD = 0,
NSVG_SPREAD_REFLECT = 1,
NSVG_SPREAD_REPEAT = 2
};
enum NSVGlineJoin {
NSVG_JOIN_MITER = 0,
NSVG_JOIN_ROUND = 1,
NSVG_JOIN_BEVEL = 2
};
enum NSVGlineCap {
NSVG_CAP_BUTT = 0,
NSVG_CAP_ROUND = 1,
NSVG_CAP_SQUARE = 2
};
enum NSVGfillRule {
NSVG_FILLRULE_NONZERO = 0,
NSVG_FILLRULE_EVENODD = 1
};
enum NSVGflags {
NSVG_FLAGS_VISIBLE = 0x01
};
typedef struct NSVGgradientStop {
unsigned int color;
float offset;
} NSVGgradientStop;
typedef struct NSVGgradient {
float xform[6];
char spread;
float fx, fy;
int nstops;
NSVGgradientStop stops[1];
} NSVGgradient;
typedef struct NSVGpaint {
char type;
union {
unsigned int color;
NSVGgradient* gradient;
};
} NSVGpaint;
typedef struct NSVGpath
{
float* pts;
int npts;
char closed;
float bounds[4];
struct NSVGpath* next;
} NSVGpath;
typedef struct NSVGshape
{
char id[64];
NSVGpaint fill;
NSVGpaint stroke;
float opacity;
float strokeWidth;
float strokeDashOffset;
float strokeDashArray[8];
char strokeDashCount;
char strokeLineJoin;
char strokeLineCap;
float miterLimit;
char fillRule;
unsigned char flags;
float bounds[4];
NSVGpath* paths;
struct NSVGshape* next;
} NSVGshape;
typedef struct NSVGimage
{
float width;
float height;
NSVGshape* shapes;
} NSVGimage;
NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi);
NSVGimage* nsvgParse(char* input, const char* units, float dpi);
NSVGpath* nsvgDuplicatePath(NSVGpath* p);
void nsvgDelete(NSVGimage* image);
#ifndef NANOSVG_CPLUSPLUS
#ifdef __cplusplus
}
#endif
#endif
#endif
#ifdef NANOSVG_IMPLEMENTATION
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define NSVG_PI (3.14159265358979323846264338327f)
#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs.
#define NSVG_ALIGN_MIN 0
#define NSVG_ALIGN_MID 1
#define NSVG_ALIGN_MAX 2
#define NSVG_ALIGN_NONE 0
#define NSVG_ALIGN_MEET 1
#define NSVG_ALIGN_SLICE 2
#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0)
#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16))
#ifdef _MSC_VER
#pragma warning (disable: 4996) // Switch off security warnings
#pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings
#ifdef __cplusplus
#define NSVG_INLINE inline
#else
#define NSVG_INLINE
#endif
#else
#define NSVG_INLINE inline
#endif
static int nsvg__isspace(char c)
{
return strchr(" \t\n\v\f\r", c) != 0;
}
static int nsvg__isdigit(char c)
{
return c >= '0' && c <= '9';
}
static int nsvg__isnum(char c)
{
return strchr("0123456789+-.eE", c) != 0;
}
static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; }
static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; }
#define NSVG_XML_TAG 1
#define NSVG_XML_CONTENT 2
#define NSVG_XML_MAX_ATTRIBS 256
static void nsvg__parseContent(char* s,
void (*contentCb)(void* ud, const char* s),
void* ud)
{
while (*s && nsvg__isspace(*s)) s++;
if (!*s) return;
if (contentCb)
(*contentCb)(ud, s);
}
static void nsvg__parseElement(char* s,
void (*startelCb)(void* ud, const char* el, const char** attr),
void (*endelCb)(void* ud, const char* el),
void* ud)
{
const char* attr[NSVG_XML_MAX_ATTRIBS];
int nattr = 0;
char* name;
int start = 0;
int end = 0;
char quote;
while (*s && nsvg__isspace(*s)) s++;
if (*s == '/') {
s++;
end = 1;
} else {
start = 1;
}
if (!*s || *s == '?' || *s == '!')
return;
name = s;
while (*s && !nsvg__isspace(*s)) s++;
if (*s) { *s++ = '\0'; }
while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) {
char* name = NULL;
char* value = NULL;
while (*s && nsvg__isspace(*s)) s++;
if (!*s) break;
if (*s == '/') {
end = 1;
break;
}
name = s;
while (*s && !nsvg__isspace(*s) && *s != '=') s++;
if (*s) { *s++ = '\0'; }
while (*s && *s != '\"' && *s != '\'') s++;
if (!*s) break;
quote = *s;
s++;
value = s;
while (*s && *s != quote) s++;
if (*s) { *s++ = '\0'; }
if (name && value) {
attr[nattr++] = name;
attr[nattr++] = value;
}
}
attr[nattr++] = 0;
attr[nattr++] = 0;
if (start && startelCb)
(*startelCb)(ud, name, attr);
if (end && endelCb)
(*endelCb)(ud, name);
}
int nsvg__parseXML(char* input,
void (*startelCb)(void* ud, const char* el, const char** attr),
void (*endelCb)(void* ud, const char* el),
void (*contentCb)(void* ud, const char* s),
void* ud)
{
char* s = input;
char* mark = s;
int state = NSVG_XML_CONTENT;
while (*s) {
if (*s == '<' && state == NSVG_XML_CONTENT) {
*s++ = '\0';
nsvg__parseContent(mark, contentCb, ud);
mark = s;
state = NSVG_XML_TAG;
} else if (*s == '>' && state == NSVG_XML_TAG) {
*s++ = '\0';
nsvg__parseElement(mark, startelCb, endelCb, ud);
mark = s;
state = NSVG_XML_CONTENT;
} else {
s++;
}
}
return 1;
}
#define NSVG_MAX_ATTR 128
enum NSVGgradientUnits {
NSVG_USER_SPACE = 0,
NSVG_OBJECT_SPACE = 1
};
#define NSVG_MAX_DASHES 8
enum NSVGunits {
NSVG_UNITS_USER,
NSVG_UNITS_PX,
NSVG_UNITS_PT,
NSVG_UNITS_PC,
NSVG_UNITS_MM,
NSVG_UNITS_CM,
NSVG_UNITS_IN,
NSVG_UNITS_PERCENT,
NSVG_UNITS_EM,
NSVG_UNITS_EX
};
typedef struct NSVGcoordinate {
float value;
int units;
} NSVGcoordinate;
typedef struct NSVGlinearData {
NSVGcoordinate x1, y1, x2, y2;
} NSVGlinearData;
typedef struct NSVGradialData {
NSVGcoordinate cx, cy, r, fx, fy;
} NSVGradialData;
typedef struct NSVGgradientData
{
char id[64];
char ref[64];
char type;
union {
NSVGlinearData linear;
NSVGradialData radial;
};
char spread;
char units;
float xform[6];
int nstops;
NSVGgradientStop* stops;
struct NSVGgradientData* next;
} NSVGgradientData;
typedef struct NSVGattrib
{
char id[64];
float xform[6];
unsigned int fillColor;
unsigned int strokeColor;
float opacity;
float fillOpacity;
float strokeOpacity;
char fillGradient[64];
char strokeGradient[64];
float strokeWidth;
float strokeDashOffset;
float strokeDashArray[NSVG_MAX_DASHES];
int strokeDashCount;
char strokeLineJoin;
char strokeLineCap;
float miterLimit;
char fillRule;
float fontSize;
unsigned int stopColor;
float stopOpacity;
float stopOffset;
char hasFill;
char hasStroke;
char visible;
} NSVGattrib;
typedef struct NSVGparser
{
NSVGattrib attr[NSVG_MAX_ATTR];
int attrHead;
float* pts;
int npts;
int cpts;
NSVGpath* plist;
NSVGimage* image;
NSVGgradientData* gradients;
NSVGshape* shapesTail;
float viewMinx, viewMiny, viewWidth, viewHeight;
int alignX, alignY, alignType;
float dpi;
char pathFlag;
char defsFlag;
} NSVGparser;
static void nsvg__xformIdentity(float* t)
{
t[0] = 1.0f; t[1] = 0.0f;
t[2] = 0.0f; t[3] = 1.0f;
t[4] = 0.0f; t[5] = 0.0f;
}
static void nsvg__xformSetTranslation(float* t, float tx, float ty)
{
t[0] = 1.0f; t[1] = 0.0f;
t[2] = 0.0f; t[3] = 1.0f;
t[4] = tx; t[5] = ty;
}
static void nsvg__xformSetScale(float* t, float sx, float sy)
{
t[0] = sx; t[1] = 0.0f;
t[2] = 0.0f; t[3] = sy;
t[4] = 0.0f; t[5] = 0.0f;
}
static void nsvg__xformSetSkewX(float* t, float a)
{
t[0] = 1.0f; t[1] = 0.0f;
t[2] = tanf(a); t[3] = 1.0f;
t[4] = 0.0f; t[5] = 0.0f;
}
static void nsvg__xformSetSkewY(float* t, float a)
{
t[0] = 1.0f; t[1] = tanf(a);
t[2] = 0.0f; t[3] = 1.0f;
t[4] = 0.0f; t[5] = 0.0f;
}
static void nsvg__xformSetRotation(float* t, float a)
{
float cs = cosf(a), sn = sinf(a);
t[0] = cs; t[1] = sn;
t[2] = -sn; t[3] = cs;
t[4] = 0.0f; t[5] = 0.0f;
}
static void nsvg__xformMultiply(float* t, float* s)
{
float t0 = t[0] * s[0] + t[1] * s[2];
float t2 = t[2] * s[0] + t[3] * s[2];
float t4 = t[4] * s[0] + t[5] * s[2] + s[4];
t[1] = t[0] * s[1] + t[1] * s[3];
t[3] = t[2] * s[1] + t[3] * s[3];
t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
t[0] = t0;
t[2] = t2;
t[4] = t4;
}
static void nsvg__xformInverse(float* inv, float* t)
{
double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
if (det > -1e-6 && det < 1e-6) {
nsvg__xformIdentity(t);
return;
}
invdet = 1.0 / det;
inv[0] = (float)(t[3] * invdet);
inv[2] = (float)(-t[2] * invdet);
inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
inv[1] = (float)(-t[1] * invdet);
inv[3] = (float)(t[0] * invdet);
inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
}
static void nsvg__xformPremultiply(float* t, float* s)
{
float s2[6];
memcpy(s2, s, sizeof(float)*6);
nsvg__xformMultiply(s2, t);
memcpy(t, s2, sizeof(float)*6);
}
static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t)
{
*dx = x*t[0] + y*t[2] + t[4];
*dy = x*t[1] + y*t[3] + t[5];
}
static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t)
{
*dx = x*t[0] + y*t[2];
*dy = x*t[1] + y*t[3];
}
#define NSVG_EPSILON (1e-12)
static int nsvg__ptInBounds(float* pt, float* bounds)
{
return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];
}
static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3)
{
double it = 1.0-t;
return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3;
}
static void nsvg__curveBounds(float* bounds, float* curve)
{
int i, j, count;
double roots[2], a, b, c, b2ac, t, v;
float* v0 = &curve[0];
float* v1 = &curve[2];
float* v2 = &curve[4];
float* v3 = &curve[6];
bounds[0] = nsvg__minf(v0[0], v3[0]);
bounds[1] = nsvg__minf(v0[1], v3[1]);
bounds[2] = nsvg__maxf(v0[0], v3[0]);
bounds[3] = nsvg__maxf(v0[1], v3[1]);
if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds))
return;
for (i = 0; i < 2; i++) {
a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i];
b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i];
c = 3.0 * v1[i] - 3.0 * v0[i];
count = 0;
if (fabs(a) < NSVG_EPSILON) {
if (fabs(b) > NSVG_EPSILON) {
t = -c / b;
if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)
roots[count++] = t;
}
} else {
b2ac = b*b - 4.0*c*a;
if (b2ac > NSVG_EPSILON) {
t = (-b + sqrt(b2ac)) / (2.0 * a);
if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)
roots[count++] = t;
t = (-b - sqrt(b2ac)) / (2.0 * a);
if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)
roots[count++] = t;
}
}
for (j = 0; j < count; j++) {
v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]);
bounds[0+i] = nsvg__minf(bounds[0+i], (float)v);
bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v);
}
}
}
static NSVGparser* nsvg__createParser()
{
NSVGparser* p;
p = (NSVGparser*)malloc(sizeof(NSVGparser));
if (p == NULL) goto error;
memset(p, 0, sizeof(NSVGparser));
p->image = (NSVGimage*)malloc(sizeof(NSVGimage));
if (p->image == NULL) goto error;
memset(p->image, 0, sizeof(NSVGimage));
nsvg__xformIdentity(p->attr[0].xform);
memset(p->attr[0].id, 0, sizeof p->attr[0].id);
p->attr[0].fillColor = NSVG_RGB(0,0,0);
p->attr[0].strokeColor = NSVG_RGB(0,0,0);
p->attr[0].opacity = 1;
p->attr[0].fillOpacity = 1;
p->attr[0].strokeOpacity = 1;
p->attr[0].stopOpacity = 1;
p->attr[0].strokeWidth = 1;
p->attr[0].strokeLineJoin = NSVG_JOIN_MITER;
p->attr[0].strokeLineCap = NSVG_CAP_BUTT;
p->attr[0].miterLimit = 4;
p->attr[0].fillRule = NSVG_FILLRULE_NONZERO;
p->attr[0].hasFill = 1;
p->attr[0].visible = 1;
return p;
error:
if (p) {
if (p->image) free(p->image);
free(p);
}
return NULL;
}
static void nsvg__deletePaths(NSVGpath* path)
{
while (path) {
NSVGpath *next = path->next;
if (path->pts != NULL)
free(path->pts);
free(path);
path = next;
}
}
static void nsvg__deletePaint(NSVGpaint* paint)
{
if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT)
free(paint->gradient);
}
static void nsvg__deleteGradientData(NSVGgradientData* grad)
{
NSVGgradientData* next;
while (grad != NULL) {
next = grad->next;
free(grad->stops);
free(grad);
grad = next;
}
}
static void nsvg__deleteParser(NSVGparser* p)
{
if (p != NULL) {
nsvg__deletePaths(p->plist);
nsvg__deleteGradientData(p->gradients);
nsvgDelete(p->image);
free(p->pts);
free(p);
}
}
static void nsvg__resetPath(NSVGparser* p)
{
p->npts = 0;
}
static void nsvg__addPoint(NSVGparser* p, float x, float y)
{
if (p->npts+1 > p->cpts) {
p->cpts = p->cpts ? p->cpts*2 : 8;
p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float));
if (!p->pts) return;
}
p->pts[p->npts*2+0] = x;
p->pts[p->npts*2+1] = y;
p->npts++;
}
static void nsvg__moveTo(NSVGparser* p, float x, float y)
{
if (p->npts > 0) {
p->pts[(p->npts-1)*2+0] = x;
p->pts[(p->npts-1)*2+1] = y;
} else {
nsvg__addPoint(p, x, y);
}
}
static void nsvg__lineTo(NSVGparser* p, float x, float y)
{
float px,py, dx,dy;
if (p->npts > 0) {
px = p->pts[(p->npts-1)*2+0];
py = p->pts[(p->npts-1)*2+1];
dx = x - px;
dy = y - py;
nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f);
nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f);
nsvg__addPoint(p, x, y);
}
}
static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)
{
nsvg__addPoint(p, cpx1, cpy1);
nsvg__addPoint(p, cpx2, cpy2);
nsvg__addPoint(p, x, y);
}
static NSVGattrib* nsvg__getAttr(NSVGparser* p)
{
return &p->attr[p->attrHead];
}
static void nsvg__pushAttr(NSVGparser* p)
{
if (p->attrHead < NSVG_MAX_ATTR-1) {
p->attrHead++;
memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib));
}
}
static void nsvg__popAttr(NSVGparser* p)
{
if (p->attrHead > 0)
p->attrHead--;
}
static float nsvg__actualOrigX(NSVGparser* p)
{
return p->viewMinx;
}
static float nsvg__actualOrigY(NSVGparser* p)
{
return p->viewMiny;
}
static float nsvg__actualWidth(NSVGparser* p)
{
return p->viewWidth;
}
static float nsvg__actualHeight(NSVGparser* p)
{
return p->viewHeight;
}
static float nsvg__actualLength(NSVGparser* p)
{
float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p);
return sqrtf(w*w + h*h) / sqrtf(2.0f);
}
static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length)
{
NSVGattrib* attr = nsvg__getAttr(p);
switch (c.units) {
case NSVG_UNITS_USER: return c.value;
case NSVG_UNITS_PX: return c.value;
case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi;
case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi;
case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi;
case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi;
case NSVG_UNITS_IN: return c.value * p->dpi;
case NSVG_UNITS_EM: return c.value * attr->fontSize;
case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f;
case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length;
default: return c.value;
}
return c.value;
}
static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id)
{
NSVGgradientData* grad = p->gradients;
while (grad) {
if (strcmp(grad->id, id) == 0)
return grad;
grad = grad->next;
}
return NULL;
}
static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType)
{
NSVGattrib* attr = nsvg__getAttr(p);
NSVGgradientData* data = NULL;
NSVGgradientData* ref = NULL;
NSVGgradientStop* stops = NULL;
NSVGgradient* grad;
float ox, oy, sw, sh, sl;
int nstops = 0;
data = nsvg__findGradientData(p, id);
if (data == NULL) return NULL;
ref = data;
while (ref != NULL) {
if (stops == NULL && ref->stops != NULL) {
stops = ref->stops;
nstops = ref->nstops;
break;
}
ref = nsvg__findGradientData(p, ref->ref);
}
if (stops == NULL) return NULL;
grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1));
if (grad == NULL) return NULL;
if (data->units == NSVG_OBJECT_SPACE) {
ox = localBounds[0];
oy = localBounds[1];
sw = localBounds[2] - localBounds[0];
sh = localBounds[3] - localBounds[1];
} else {
ox = nsvg__actualOrigX(p);
oy = nsvg__actualOrigY(p);
sw = nsvg__actualWidth(p);
sh = nsvg__actualHeight(p);
}
sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f);
if (data->type == NSVG_PAINT_LINEAR_GRADIENT) {
float x1, y1, x2, y2, dx, dy;
x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw);
y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh);
x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw);
y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh);
dx = x2 - x1;
dy = y2 - y1;
grad->xform[0] = dy; grad->xform[1] = -dx;
grad->xform[2] = dx; grad->xform[3] = dy;
grad->xform[4] = x1; grad->xform[5] = y1;
} else {
float cx, cy, fx, fy, r;
cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw);
cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh);
fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw);
fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh);
r = nsvg__convertToPixels(p, data->radial.r, 0, sl);
grad->xform[0] = r; grad->xform[1] = 0;
grad->xform[2] = 0; grad->xform[3] = r;
grad->xform[4] = cx; grad->xform[5] = cy;
grad->fx = fx / r;
grad->fy = fy / r;
}
nsvg__xformMultiply(grad->xform, data->xform);
nsvg__xformMultiply(grad->xform, attr->xform);
grad->spread = data->spread;
memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));
grad->nstops = nstops;
*paintType = data->type;
return grad;
}
static float nsvg__getAverageScale(float* t)
{
float sx = sqrtf(t[0]*t[0] + t[2]*t[2]);
float sy = sqrtf(t[1]*t[1] + t[3]*t[3]);
return (sx + sy) * 0.5f;
}
static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform)
{
NSVGpath* path;
float curve[4*2], curveBounds[4];
int i, first = 1;
for (path = shape->paths; path != NULL; path = path->next) {
nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform);
for (i = 0; i < path->npts-1; i += 3) {
nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform);
nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform);
nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform);
nsvg__curveBounds(curveBounds, curve);
if (first) {
bounds[0] = curveBounds[0];
bounds[1] = curveBounds[1];
bounds[2] = curveBounds[2];
bounds[3] = curveBounds[3];
first = 0;
} else {
bounds[0] = nsvg__minf(bounds[0], curveBounds[0]);
bounds[1] = nsvg__minf(bounds[1], curveBounds[1]);
bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]);
bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]);
}
curve[0] = curve[6];
curve[1] = curve[7];
}
}
}
static void nsvg__addShape(NSVGparser* p)
{
NSVGattrib* attr = nsvg__getAttr(p);
float scale = 1.0f;
NSVGshape* shape;
NSVGpath* path;
int i;
if (p->plist == NULL)
return;
shape = (NSVGshape*)malloc(sizeof(NSVGshape));
if (shape == NULL) goto error;
memset(shape, 0, sizeof(NSVGshape));
memcpy(shape->id, attr->id, sizeof shape->id);
scale = nsvg__getAverageScale(attr->xform);
shape->strokeWidth = attr->strokeWidth * scale;
shape->strokeDashOffset = attr->strokeDashOffset * scale;
shape->strokeDashCount = (char)attr->strokeDashCount;
for (i = 0; i < attr->strokeDashCount; i++)
shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale;
shape->strokeLineJoin = attr->strokeLineJoin;
shape->strokeLineCap = attr->strokeLineCap;
shape->miterLimit = attr->miterLimit;
shape->fillRule = attr->fillRule;
shape->opacity = attr->opacity;
shape->paths = p->plist;
p->plist = NULL;
shape->bounds[0] = shape->paths->bounds[0];
shape->bounds[1] = shape->paths->bounds[1];
shape->bounds[2] = shape->paths->bounds[2];
shape->bounds[3] = shape->paths->bounds[3];
for (path = shape->paths->next; path != NULL; path = path->next) {
shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]);
shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]);
shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]);
shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]);
}
if (attr->hasFill == 0) {
shape->fill.type = NSVG_PAINT_NONE;
} else if (attr->hasFill == 1) {
shape->fill.type = NSVG_PAINT_COLOR;
shape->fill.color = attr->fillColor;
shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24;
} else if (attr->hasFill == 2) {
float inv[6], localBounds[4];
nsvg__xformInverse(inv, attr->xform);
nsvg__getLocalBounds(localBounds, shape, inv);
shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type);
if (shape->fill.gradient == NULL) {
shape->fill.type = NSVG_PAINT_NONE;
}
}
if (attr->hasStroke == 0) {
shape->stroke.type = NSVG_PAINT_NONE;
} else if (attr->hasStroke == 1) {
shape->stroke.type = NSVG_PAINT_COLOR;
shape->stroke.color = attr->strokeColor;
shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24;
} else if (attr->hasStroke == 2) {
float inv[6], localBounds[4];
nsvg__xformInverse(inv, attr->xform);
nsvg__getLocalBounds(localBounds, shape, inv);
shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type);
if (shape->stroke.gradient == NULL)
shape->stroke.type = NSVG_PAINT_NONE;
}
shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00);
if (p->image->shapes == NULL)
p->image->shapes = shape;
else
p->shapesTail->next = shape;
p->shapesTail = shape;
return;
error:
if (shape) free(shape);
}
static void nsvg__addPath(NSVGparser* p, char closed)
{
NSVGattrib* attr = nsvg__getAttr(p);
NSVGpath* path = NULL;
float bounds[4];
float* curve;
int i;
if (p->npts < 4)
return;
if (closed)
nsvg__lineTo(p, p->pts[0], p->pts[1]);
path = (NSVGpath*)malloc(sizeof(NSVGpath));
if (path == NULL) goto error;
memset(path, 0, sizeof(NSVGpath));
path->pts = (float*)malloc(p->npts*2*sizeof(float));
if (path->pts == NULL) goto error;
path->closed = closed;
path->npts = p->npts;
for (i = 0; i < p->npts; ++i)
nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform);
for (i = 0; i < path->npts-1; i += 3) {
curve = &path->pts[i*2];
nsvg__curveBounds(bounds, curve);
if (i == 0) {
path->bounds[0] = bounds[0];
path->bounds[1] = bounds[1];
path->bounds[2] = bounds[2];
path->bounds[3] = bounds[3];
} else {
path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]);
path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]);
path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]);
path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]);
}
}
path->next = p->plist;
p->plist = path;
return;
error:
if (path != NULL) {
if (path->pts != NULL) free(path->pts);
free(path);
}
}
static double nsvg__atof(const char* s)
{
char* cur = (char*)s;
char* end = NULL;
double res = 0.0, sign = 1.0;
long long intPart = 0, fracPart = 0;
char hasIntPart = 0, hasFracPart = 0;
if (*cur == '+') {
cur++;
} else if (*cur == '-') {
sign = -1;
cur++;
}
if (nsvg__isdigit(*cur)) {
intPart = strtoll(cur, &end, 10);
if (cur != end) {
res = (double)intPart;
hasIntPart = 1;
cur = end;
}
}
if (*cur == '.') {
cur++;
if (nsvg__isdigit(*cur)) {
fracPart = strtoll(cur, &end, 10);
if (cur != end) {
res += (double)fracPart / pow(10.0, (double)(end - cur));
hasFracPart = 1;
cur = end;
}
}
}
if (!hasIntPart && !hasFracPart)
return 0.0;
if (*cur == 'e' || *cur == 'E') {
long expPart = 0;
cur++;
expPart = strtol(cur, &end, 10);
if (cur != end) {
res *= pow(10.0, (double)expPart);
}
}
return res * sign;
}
static const char* nsvg__parseNumber(const char* s, char* it, const int size)
{
const int last = size-1;
int i = 0;
if (*s == '-' || *s == '+') {
if (i < last) it[i++] = *s;
s++;
}
while (*s && nsvg__isdigit(*s)) {
if (i < last) it[i++] = *s;
s++;
}
if (*s == '.') {
if (i < last) it[i++] = *s;
s++;
while (*s && nsvg__isdigit(*s)) {
if (i < last) it[i++] = *s;
s++;
}
}
if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) {
if (i < last) it[i++] = *s;
s++;
if (*s == '-' || *s == '+') {
if (i < last) it[i++] = *s;
s++;
}
while (*s && nsvg__isdigit(*s)) {
if (i < last) it[i++] = *s;
s++;
}
}
it[i] = '\0';
return s;
}
static const char* nsvg__getNextPathItem(const char* s, char* it)
{
it[0] = '\0';
while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
if (!*s) return s;
if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) {
s = nsvg__parseNumber(s, it, 64);
} else {
it[0] = *s++;
it[1] = '\0';
return s;
}
return s;
}
static unsigned int nsvg__parseColorHex(const char* str)
{
unsigned int c = 0, r = 0, g = 0, b = 0;
int n = 0;
str++;
while(str[n] && !nsvg__isspace(str[n]))
n++;
if (n == 6) {
sscanf(str, "%x", &c);
} else if (n == 3) {
sscanf(str, "%x", &c);
c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8);
c |= c<<4;
}
r = (c >> 16) & 0xff;
g = (c >> 8) & 0xff;
b = c & 0xff;
return NSVG_RGB(r,g,b);
}
static unsigned int nsvg__parseColorRGB(const char* str)
{
int r = -1, g = -1, b = -1;
char s1[32]="", s2[32]="";
sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b);
if (strchr(s1, '%')) {
return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100);
} else {
return NSVG_RGB(r,g,b);
}
}
typedef struct NSVGNamedColor {
const char* name;
unsigned int color;
} NSVGNamedColor;
NSVGNamedColor nsvg__colors[] = {
{ "red", NSVG_RGB(255, 0, 0) },
{ "green", NSVG_RGB( 0, 128, 0) },
{ "blue", NSVG_RGB( 0, 0, 255) },
{ "yellow", NSVG_RGB(255, 255, 0) },
{ "cyan", NSVG_RGB( 0, 255, 255) },
{ "magenta", NSVG_RGB(255, 0, 255) },
{ "black", NSVG_RGB( 0, 0, 0) },
{ "grey", NSVG_RGB(128, 128, 128) },
{ "gray", NSVG_RGB(128, 128, 128) },
{ "white", NSVG_RGB(255, 255, 255) },
#ifdef NANOSVG_ALL_COLOR_KEYWORDS
{ "aliceblue", NSVG_RGB(240, 248, 255) },
{ "antiquewhite", NSVG_RGB(250, 235, 215) },
{ "aqua", NSVG_RGB( 0, 255, 255) },
{ "aquamarine", NSVG_RGB(127, 255, 212) },
{ "azure", NSVG_RGB(240, 255, 255) },
{ "beige", NSVG_RGB(245, 245, 220) },
{ "bisque", NSVG_RGB(255, 228, 196) },
{ "blanchedalmond", NSVG_RGB(255, 235, 205) },
{ "blueviolet", NSVG_RGB(138, 43, 226) },
{ "brown", NSVG_RGB(165, 42, 42) },
{ "burlywood", NSVG_RGB(222, 184, 135) },
{ "cadetblue", NSVG_RGB( 95, 158, 160) },
{ "chartreuse", NSVG_RGB(127, 255, 0) },
{ "chocolate", NSVG_RGB(210, 105, 30) },
{ "coral", NSVG_RGB(255, 127, 80) },
{ "cornflowerblue", NSVG_RGB(100, 149, 237) },
{ "cornsilk", NSVG_RGB(255, 248, 220) },
{ "crimson", NSVG_RGB(220, 20, 60) },
{ "darkblue", NSVG_RGB( 0, 0, 139) },
{ "darkcyan", NSVG_RGB( 0, 139, 139) },
{ "darkgoldenrod", NSVG_RGB(184, 134, 11) },
{ "darkgray", NSVG_RGB(169, 169, 169) },
{ "darkgreen", NSVG_RGB( 0, 100, 0) },
{ "darkgrey", NSVG_RGB(169, 169, 169) },
{ "darkkhaki", NSVG_RGB(189, 183, 107) },
{ "darkmagenta", NSVG_RGB(139, 0, 139) },
{ "darkolivegreen", NSVG_RGB( 85, 107, 47) },
{ "darkorange", NSVG_RGB(255, 140, 0) },
{ "darkorchid", NSVG_RGB(153, 50, 204) },
{ "darkred", NSVG_RGB(139, 0, 0) },
{ "darksalmon", NSVG_RGB(233, 150, 122) },
{ "darkseagreen", NSVG_RGB(143, 188, 143) },
{ "darkslateblue", NSVG_RGB( 72, 61, 139) },
{ "darkslategray", NSVG_RGB( 47, 79, 79) },
{ "darkslategrey", NSVG_RGB( 47, 79, 79) },
{ "darkturquoise", NSVG_RGB( 0, 206, 209) },
{ "darkviolet", NSVG_RGB(148, 0, 211) },
{ "deeppink", NSVG_RGB(255, 20, 147) },
{ "deepskyblue", NSVG_RGB( 0, 191, 255) },
{ "dimgray", NSVG_RGB(105, 105, 105) },
{ "dimgrey", NSVG_RGB(105, 105, 105) },
{ "dodgerblue", NSVG_RGB( 30, 144, 255) },
{ "firebrick", NSVG_RGB(178, 34, 34) },
{ "floralwhite", NSVG_RGB(255, 250, 240) },
{ "forestgreen", NSVG_RGB( 34, 139, 34) },
{ "fuchsia", NSVG_RGB(255, 0, 255) },
{ "gainsboro", NSVG_RGB(220, 220, 220) },
{ "ghostwhite", NSVG_RGB(248, 248, 255) },
{ "gold", NSVG_RGB(255, 215, 0) },
{ "goldenrod", NSVG_RGB(218, 165, 32) },
{ "greenyellow", NSVG_RGB(173, 255, 47) },
{ "honeydew", NSVG_RGB(240, 255, 240) },
{ "hotpink", NSVG_RGB(255, 105, 180) },
{ "indianred", NSVG_RGB(205, 92, 92) },
{ "indigo", NSVG_RGB( 75, 0, 130) },
{ "ivory", NSVG_RGB(255, 255, 240) },
{ "khaki", NSVG_RGB(240, 230, 140) },
{ "lavender", NSVG_RGB(230, 230, 250) },
{ "lavenderblush", NSVG_RGB(255, 240, 245) },
{ "lawngreen", NSVG_RGB(124, 252, 0) },
{ "lemonchiffon", NSVG_RGB(255, 250, 205) },
{ "lightblue", NSVG_RGB(173, 216, 230) },
{ "lightcoral", NSVG_RGB(240, 128, 128) },
{ "lightcyan", NSVG_RGB(224, 255, 255) },
{ "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) },
{ "lightgray", NSVG_RGB(211, 211, 211) },
{ "lightgreen", NSVG_RGB(144, 238, 144) },
{ "lightgrey", NSVG_RGB(211, 211, 211) },
{ "lightpink", NSVG_RGB(255, 182, 193) },
{ "lightsalmon", NSVG_RGB(255, 160, 122) },
{ "lightseagreen", NSVG_RGB( 32, 178, 170) },
{ "lightskyblue", NSVG_RGB(135, 206, 250) },
{ "lightslategray", NSVG_RGB(119, 136, 153) },
{ "lightslategrey", NSVG_RGB(119, 136, 153) },
{ "lightsteelblue", NSVG_RGB(176, 196, 222) },
{ "lightyellow", NSVG_RGB(255, 255, 224) },
{ "lime", NSVG_RGB( 0, 255, 0) },
{ "limegreen", NSVG_RGB( 50, 205, 50) },
{ "linen", NSVG_RGB(250, 240, 230) },
{ "maroon", NSVG_RGB(128, 0, 0) },
{ "mediumaquamarine", NSVG_RGB(102, 205, 170) },
{ "mediumblue", NSVG_RGB( 0, 0, 205) },
{ "mediumorchid", NSVG_RGB(186, 85, 211) },
{ "mediumpurple", NSVG_RGB(147, 112, 219) },
{ "mediumseagreen", NSVG_RGB( 60, 179, 113) },
{ "mediumslateblue", NSVG_RGB(123, 104, 238) },
{ "mediumspringgreen", NSVG_RGB( 0, 250, 154) },
{ "mediumturquoise", NSVG_RGB( 72, 209, 204) },
{ "mediumvioletred", NSVG_RGB(199, 21, 133) },
{ "midnightblue", NSVG_RGB( 25, 25, 112) },
{ "mintcream", NSVG_RGB(245, 255, 250) },
{ "mistyrose", NSVG_RGB(255, 228, 225) },
{ "moccasin", NSVG_RGB(255, 228, 181) },
{ "navajowhite", NSVG_RGB(255, 222, 173) },
{ "navy", NSVG_RGB( 0, 0, 128) },
{ "oldlace", NSVG_RGB(253, 245, 230) },
{ "olive", NSVG_RGB(128, 128, 0) },
{ "olivedrab", NSVG_RGB(107, 142, 35) },
{ "orange", NSVG_RGB(255, 165, 0) },
{ "orangered", NSVG_RGB(255, 69, 0) },
{ "orchid", NSVG_RGB(218, 112, 214) },
{ "palegoldenrod", NSVG_RGB(238, 232, 170) },
{ "palegreen", NSVG_RGB(152, 251, 152) },
{ "paleturquoise", NSVG_RGB(175, 238, 238) },
{ "palevioletred", NSVG_RGB(219, 112, 147) },
{ "papayawhip", NSVG_RGB(255, 239, 213) },
{ "peachpuff", NSVG_RGB(255, 218, 185) },
{ "peru", NSVG_RGB(205, 133, 63) },
{ "pink", NSVG_RGB(255, 192, 203) },
{ "plum", NSVG_RGB(221, 160, 221) },
{ "powderblue", NSVG_RGB(176, 224, 230) },
{ "purple", NSVG_RGB(128, 0, 128) },
{ "rosybrown", NSVG_RGB(188, 143, 143) },
{ "royalblue", NSVG_RGB( 65, 105, 225) },
{ "saddlebrown", NSVG_RGB(139, 69, 19) },
{ "salmon", NSVG_RGB(250, 128, 114) },
{ "sandybrown", NSVG_RGB(244, 164, 96) },
{ "seagreen", NSVG_RGB( 46, 139, 87) },
{ "seashell", NSVG_RGB(255, 245, 238) },
{ "sienna", NSVG_RGB(160, 82, 45) },
{ "silver", NSVG_RGB(192, 192, 192) },
{ "skyblue", NSVG_RGB(135, 206, 235) },
{ "slateblue", NSVG_RGB(106, 90, 205) },
{ "slategray", NSVG_RGB(112, 128, 144) },
{ "slategrey", NSVG_RGB(112, 128, 144) },
{ "snow", NSVG_RGB(255, 250, 250) },
{ "springgreen", NSVG_RGB( 0, 255, 127) },
{ "steelblue", NSVG_RGB( 70, 130, 180) },
{ "tan", NSVG_RGB(210, 180, 140) },
{ "teal", NSVG_RGB( 0, 128, 128) },
{ "thistle", NSVG_RGB(216, 191, 216) },
{ "tomato", NSVG_RGB(255, 99, 71) },
{ "turquoise", NSVG_RGB( 64, 224, 208) },
{ "violet", NSVG_RGB(238, 130, 238) },
{ "wheat", NSVG_RGB(245, 222, 179) },
{ "whitesmoke", NSVG_RGB(245, 245, 245) },
{ "yellowgreen", NSVG_RGB(154, 205, 50) },
#endif
};
static unsigned int nsvg__parseColorName(const char* str)
{
int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor);
for (i = 0; i < ncolors; i++) {
if (strcmp(nsvg__colors[i].name, str) == 0) {
return nsvg__colors[i].color;
}
}
return NSVG_RGB(128, 128, 128);
}
static unsigned int nsvg__parseColor(const char* str)
{
size_t len = 0;
while(*str == ' ') ++str;
len = strlen(str);
if (len >= 1 && *str == '#')
return nsvg__parseColorHex(str);
else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(')
return nsvg__parseColorRGB(str);
return nsvg__parseColorName(str);
}
static float nsvg__parseOpacity(const char* str)
{
float val = nsvg__atof(str);
if (val < 0.0f) val = 0.0f;
if (val > 1.0f) val = 1.0f;
return val;
}
static float nsvg__parseMiterLimit(const char* str)
{
float val = nsvg__atof(str);
if (val < 0.0f) val = 0.0f;
return val;
}
static int nsvg__parseUnits(const char* units)
{
if (units[0] == 'p' && units[1] == 'x')
return NSVG_UNITS_PX;
else if (units[0] == 'p' && units[1] == 't')
return NSVG_UNITS_PT;
else if (units[0] == 'p' && units[1] == 'c')
return NSVG_UNITS_PC;
else if (units[0] == 'm' && units[1] == 'm')
return NSVG_UNITS_MM;
else if (units[0] == 'c' && units[1] == 'm')
return NSVG_UNITS_CM;
else if (units[0] == 'i' && units[1] == 'n')
return NSVG_UNITS_IN;
else if (units[0] == '%')
return NSVG_UNITS_PERCENT;
else if (units[0] == 'e' && units[1] == 'm')
return NSVG_UNITS_EM;
else if (units[0] == 'e' && units[1] == 'x')
return NSVG_UNITS_EX;
return NSVG_UNITS_USER;
}
static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str)
{
NSVGcoordinate coord = {0, NSVG_UNITS_USER};
char buf[64];
coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64));
coord.value = nsvg__atof(buf);
return coord;
}
static NSVGcoordinate nsvg__coord(float v, int units)
{
NSVGcoordinate coord = {v, units};
return coord;
}
static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length)
{
NSVGcoordinate coord = nsvg__parseCoordinateRaw(str);
return nsvg__convertToPixels(p, coord, orig, length);
}
static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na)
{
const char* end;
const char* ptr;
char it[64];
*na = 0;
ptr = str;
while (*ptr && *ptr != '(') ++ptr;
if (*ptr == 0)
return 1;
end = ptr;
while (*end && *end != ')') ++end;
if (*end == 0)
return 1;
while (ptr < end) {
if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) {
if (*na >= maxNa) return 0;
ptr = nsvg__parseNumber(ptr, it, 64);
args[(*na)++] = (float)nsvg__atof(it);
} else {
++ptr;
}
}
return (int)(end - str);
}
static int nsvg__parseMatrix(float* xform, const char* str)
{
float t[6];
int na = 0;
int len = nsvg__parseTransformArgs(str, t, 6, &na);
if (na != 6) return len;
memcpy(xform, t, sizeof(float)*6);
return len;
}
static int nsvg__parseTranslate(float* xform, const char* str)
{
float args[2];
float t[6];
int na = 0;
int len = nsvg__parseTransformArgs(str, args, 2, &na);
if (na == 1) args[1] = 0.0;
nsvg__xformSetTranslation(t, args[0], args[1]);
memcpy(xform, t, sizeof(float)*6);
return len;
}
static int nsvg__parseScale(float* xform, const char* str)
{
float args[2];
int na = 0;
float t[6];
int len = nsvg__parseTransformArgs(str, args, 2, &na);
if (na == 1) args[1] = args[0];
nsvg__xformSetScale(t, args[0], args[1]);
memcpy(xform, t, sizeof(float)*6);
return len;
}
static int nsvg__parseSkewX(float* xform, const char* str)
{
float args[1];
int na = 0;
float t[6];
int len = nsvg__parseTransformArgs(str, args, 1, &na);
nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI);
memcpy(xform, t, sizeof(float)*6);
return len;
}
static int nsvg__parseSkewY(float* xform, const char* str)
{
float args[1];
int na = 0;
float t[6];
int len = nsvg__parseTransformArgs(str, args, 1, &na);
nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI);
memcpy(xform, t, sizeof(float)*6);
return len;
}
static int nsvg__parseRotate(float* xform, const char* str)
{
float args[3];
int na = 0;
float m[6];
float t[6];
int len = nsvg__parseTransformArgs(str, args, 3, &na);
if (na == 1)
args[1] = args[2] = 0.0f;
nsvg__xformIdentity(m);
if (na > 1) {
nsvg__xformSetTranslation(t, -args[1], -args[2]);
nsvg__xformMultiply(m, t);
}
nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI);
nsvg__xformMultiply(m, t);
if (na > 1) {
nsvg__xformSetTranslation(t, args[1], args[2]);
nsvg__xformMultiply(m, t);
}
memcpy(xform, m, sizeof(float)*6);
return len;
}
static void nsvg__parseTransform(float* xform, const char* str)
{
float t[6];
nsvg__xformIdentity(xform);
while (*str)
{
if (strncmp(str, "matrix", 6) == 0)
str += nsvg__parseMatrix(t, str);
else if (strncmp(str, "translate", 9) == 0)
str += nsvg__parseTranslate(t, str);
else if (strncmp(str, "scale", 5) == 0)
str += nsvg__parseScale(t, str);
else if (strncmp(str, "rotate", 6) == 0)
str += nsvg__parseRotate(t, str);
else if (strncmp(str, "skewX", 5) == 0)
str += nsvg__parseSkewX(t, str);
else if (strncmp(str, "skewY", 5) == 0)
str += nsvg__parseSkewY(t, str);
else{
++str;
continue;
}
nsvg__xformPremultiply(xform, t);
}
}
static void nsvg__parseUrl(char* id, const char* str)
{
int i = 0;
str += 4;
if (*str == '#')
str++;
while (i < 63 && *str != ')') {
id[i] = *str++;
i++;
}
id[i] = '\0';
}
static char nsvg__parseLineCap(const char* str)
{
if (strcmp(str, "butt") == 0)
return NSVG_CAP_BUTT;
else if (strcmp(str, "round") == 0)
return NSVG_CAP_ROUND;
else if (strcmp(str, "square") == 0)
return NSVG_CAP_SQUARE;
return NSVG_CAP_BUTT;
}
static char nsvg__parseLineJoin(const char* str)
{
if (strcmp(str, "miter") == 0)
return NSVG_JOIN_MITER;
else if (strcmp(str, "round") == 0)
return NSVG_JOIN_ROUND;
else if (strcmp(str, "bevel") == 0)
return NSVG_JOIN_BEVEL;
return NSVG_JOIN_MITER;
}
static char nsvg__parseFillRule(const char* str)
{
if (strcmp(str, "nonzero") == 0)
return NSVG_FILLRULE_NONZERO;
else if (strcmp(str, "evenodd") == 0)
return NSVG_FILLRULE_EVENODD;
return NSVG_FILLRULE_NONZERO;
}
static const char* nsvg__getNextDashItem(const char* s, char* it)
{
int n = 0;
it[0] = '\0';
while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
while (*s && (!nsvg__isspace(*s) && *s != ',')) {
if (n < 63)
it[n++] = *s;
s++;
}
it[n++] = '\0';
return s;
}
static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray)
{
char item[64];
int count = 0, i;
float sum = 0.0f;
if (str[0] == 'n')
return 0;
while (*str) {
str = nsvg__getNextDashItem(str, item);
if (!*item) break;
if (count < NSVG_MAX_DASHES)
strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p)));
}
for (i = 0; i < count; i++)
sum += strokeDashArray[i];
if (sum <= 1e-6f)
count = 0;
return count;
}
static void nsvg__parseStyle(NSVGparser* p, const char* str);
static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)
{
float xform[6];
NSVGattrib* attr = nsvg__getAttr(p);
if (!attr) return 0;
if (strcmp(name, "style") == 0) {
nsvg__parseStyle(p, value);
} else if (strcmp(name, "display") == 0) {
if (strcmp(value, "none") == 0)
attr->visible = 0;
} else if (strcmp(name, "fill") == 0) {
if (strcmp(value, "none") == 0) {
attr->hasFill = 0;
} else if (strncmp(value, "url(", 4) == 0) {
attr->hasFill = 2;
nsvg__parseUrl(attr->fillGradient, value);
} else {
attr->hasFill = 1;
attr->fillColor = nsvg__parseColor(value);
}
} else if (strcmp(name, "opacity") == 0) {
attr->opacity = nsvg__parseOpacity(value);
} else if (strcmp(name, "fill-opacity") == 0) {
attr->fillOpacity = nsvg__parseOpacity(value);
} else if (strcmp(name, "stroke") == 0) {
if (strcmp(value, "none") == 0) {
attr->hasStroke = 0;
} else if (strncmp(value, "url(", 4) == 0) {
attr->hasStroke = 2;
nsvg__parseUrl(attr->strokeGradient, value);
} else {
attr->hasStroke = 1;
attr->strokeColor = nsvg__parseColor(value);
}
} else if (strcmp(name, "stroke-width") == 0) {
attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
} else if (strcmp(name, "stroke-dasharray") == 0) {
attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray);
} else if (strcmp(name, "stroke-dashoffset") == 0) {
attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
} else if (strcmp(name, "stroke-opacity") == 0) {
attr->strokeOpacity = nsvg__parseOpacity(value);
} else if (strcmp(name, "stroke-linecap") == 0) {
attr->strokeLineCap = nsvg__parseLineCap(value);
} else if (strcmp(name, "stroke-linejoin") == 0) {
attr->strokeLineJoin = nsvg__parseLineJoin(value);
} else if (strcmp(name, "stroke-miterlimit") == 0) {
attr->miterLimit = nsvg__parseMiterLimit(value);
} else if (strcmp(name, "fill-rule") == 0) {
attr->fillRule = nsvg__parseFillRule(value);
} else if (strcmp(name, "font-size") == 0) {
attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
} else if (strcmp(name, "transform") == 0) {
nsvg__parseTransform(xform, value);
nsvg__xformPremultiply(attr->xform, xform);
} else if (strcmp(name, "stop-color") == 0) {
attr->stopColor = nsvg__parseColor(value);
} else if (strcmp(name, "stop-opacity") == 0) {
attr->stopOpacity = nsvg__parseOpacity(value);
} else if (strcmp(name, "offset") == 0) {
attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);
} else if (strcmp(name, "id") == 0) {
strncpy(attr->id, value, 63);
attr->id[63] = '\0';
} else {
return 0;
}
return 1;
}
static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end)
{
const char* str;
const char* val;
char name[512];
char value[512];
int n;
str = start;
while (str < end && *str != ':') ++str;
val = str;
while (str > start && (*str == ':' || nsvg__isspace(*str))) --str;
++str;
n = (int)(str - start);
if (n > 511) n = 511;
if (n) memcpy(name, start, n);
name[n] = 0;
while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val;
n = (int)(end - val);
if (n > 511) n = 511;
if (n) memcpy(value, val, n);
value[n] = 0;
return nsvg__parseAttr(p, name, value);
}
static void nsvg__parseStyle(NSVGparser* p, const char* str)
{
const char* start;
const char* end;
while (*str) {
while(*str && nsvg__isspace(*str)) ++str;
start = str;
while(*str && *str != ';') ++str;
end = str;
while (end > start && (*end == ';' || nsvg__isspace(*end))) --end;
++end;
nsvg__parseNameValue(p, start, end);
if (*str) ++str;
}
}
static void nsvg__parseAttribs(NSVGparser* p, const char** attr)
{
int i;
for (i = 0; attr[i]; i += 2)
{
if (strcmp(attr[i], "style") == 0)
nsvg__parseStyle(p, attr[i + 1]);
else
nsvg__parseAttr(p, attr[i], attr[i + 1]);
}
}
static int nsvg__getArgsPerElement(char cmd)
{
switch (cmd) {
case 'v':
case 'V':
case 'h':
case 'H':
return 1;
case 'm':
case 'M':
case 'l':
case 'L':
case 't':
case 'T':
return 2;
case 'q':
case 'Q':
case 's':
case 'S':
return 4;
case 'c':
case 'C':
return 6;
case 'a':
case 'A':
return 7;
}
return 0;
}
static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
{
if (rel) {
*cpx += args[0];
*cpy += args[1];
} else {
*cpx = args[0];
*cpy = args[1];
}
nsvg__moveTo(p, *cpx, *cpy);
}
static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
{
if (rel) {
*cpx += args[0];
*cpy += args[1];
} else {
*cpx = args[0];
*cpy = args[1];
}
nsvg__lineTo(p, *cpx, *cpy);
}
static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
{
if (rel)
*cpx += args[0];
else
*cpx = args[0];
nsvg__lineTo(p, *cpx, *cpy);
}
static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
{
if (rel)
*cpy += args[0];
else
*cpy = args[0];
nsvg__lineTo(p, *cpx, *cpy);
}
static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy,
float* cpx2, float* cpy2, float* args, int rel)
{
float x2, y2, cx1, cy1, cx2, cy2;
if (rel) {
cx1 = *cpx + args[0];
cy1 = *cpy + args[1];
cx2 = *cpx + args[2];
cy2 = *cpy + args[3];
x2 = *cpx + args[4];
y2 = *cpy + args[5];
} else {
cx1 = args[0];
cy1 = args[1];
cx2 = args[2];
cy2 = args[3];
x2 = args[4];
y2 = args[5];
}
nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
*cpx2 = cx2;
*cpy2 = cy2;
*cpx = x2;
*cpy = y2;
}
static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy,
float* cpx2, float* cpy2, float* args, int rel)
{
float x1, y1, x2, y2, cx1, cy1, cx2, cy2;
x1 = *cpx;
y1 = *cpy;
if (rel) {
cx2 = *cpx + args[0];
cy2 = *cpy + args[1];
x2 = *cpx + args[2];
y2 = *cpy + args[3];
} else {
cx2 = args[0];
cy2 = args[1];
x2 = args[2];
y2 = args[3];
}
cx1 = 2*x1 - *cpx2;
cy1 = 2*y1 - *cpy2;
nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
*cpx2 = cx2;
*cpy2 = cy2;
*cpx = x2;
*cpy = y2;
}
static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy,
float* cpx2, float* cpy2, float* args, int rel)
{
float x1, y1, x2, y2, cx, cy;
float cx1, cy1, cx2, cy2;
x1 = *cpx;
y1 = *cpy;
if (rel) {
cx = *cpx + args[0];
cy = *cpy + args[1];
x2 = *cpx + args[2];
y2 = *cpy + args[3];
} else {
cx = args[0];
cy = args[1];
x2 = args[2];
y2 = args[3];
}
cx1 = x1 + 2.0f/3.0f*(cx - x1);
cy1 = y1 + 2.0f/3.0f*(cy - y1);
cx2 = x2 + 2.0f/3.0f*(cx - x2);
cy2 = y2 + 2.0f/3.0f*(cy - y2);
nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
*cpx2 = cx;
*cpy2 = cy;
*cpx = x2;
*cpy = y2;
}
static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy,
float* cpx2, float* cpy2, float* args, int rel)
{
float x1, y1, x2, y2, cx, cy;
float cx1, cy1, cx2, cy2;
x1 = *cpx;
y1 = *cpy;
if (rel) {
x2 = *cpx + args[0];
y2 = *cpy + args[1];
} else {
x2 = args[0];
y2 = args[1];
}
cx = 2*x1 - *cpx2;
cy = 2*y1 - *cpy2;
cx1 = x1 + 2.0f/3.0f*(cx - x1);
cy1 = y1 + 2.0f/3.0f*(cy - y1);
cx2 = x2 + 2.0f/3.0f*(cx - x2);
cy2 = y2 + 2.0f/3.0f*(cy - y2);
nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
*cpx2 = cx;
*cpy2 = cy;
*cpx = x2;
*cpy = y2;
}
static float nsvg__sqr(float x) { return x*x; }
static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); }
static float nsvg__vecrat(float ux, float uy, float vx, float vy)
{
return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy));
}
static float nsvg__vecang(float ux, float uy, float vx, float vy)
{
float r = nsvg__vecrat(ux,uy, vx,vy);
if (r < -1.0f) r = -1.0f;
if (r > 1.0f) r = 1.0f;
return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r);
}
static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
{
float rx, ry, rotx;
float x1, y1, x2, y2, cx, cy, dx, dy, d;
float x1p, y1p, cxp, cyp, s, sa, sb;
float ux, uy, vx, vy, a1, da;
float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
float sinrx, cosrx;
int fa, fs;
int i, ndivs;
float hda, kappa;
rx = fabsf(args[0]);
ry = fabsf(args[1]);
rotx = args[2] / 180.0f * NSVG_PI;
fa = fabsf(args[3]) > 1e-6 ? 1 : 0;
fs = fabsf(args[4]) > 1e-6 ? 1 : 0;
x1 = *cpx;
y1 = *cpy;
if (rel) {
x2 = *cpx + args[5];
y2 = *cpy + args[6];
} else {
x2 = args[5];
y2 = args[6];
}
dx = x1 - x2;
dy = y1 - y2;
d = sqrtf(dx*dx + dy*dy);
if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) {
nsvg__lineTo(p, x2, y2);
*cpx = x2;
*cpy = y2;
return;
}
sinrx = sinf(rotx);
cosrx = cosf(rotx);
x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;
y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;
d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry);
if (d > 1) {
d = sqrtf(d);
rx *= d;
ry *= d;
}
s = 0.0f;
sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p);
sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p);
if (sa < 0.0f) sa = 0.0f;
if (sb > 0.0f)
s = sqrtf(sa / sb);
if (fa == fs)
s = -s;
cxp = s * rx * y1p / ry;
cyp = s * -ry * x1p / rx;
cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp;
cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp;
ux = (x1p - cxp) / rx;
uy = (y1p - cyp) / ry;
vx = (-x1p - cxp) / rx;
vy = (-y1p - cyp) / ry;
a1 = nsvg__vecang(1.0f,0.0f, ux,uy);
da = nsvg__vecang(ux,uy, vx,vy);
if (fs == 0 && da > 0)
da -= 2 * NSVG_PI;
else if (fs == 1 && da < 0)
da += 2 * NSVG_PI;
t[0] = cosrx; t[1] = sinrx;
t[2] = -sinrx; t[3] = cosrx;
t[4] = cx; t[5] = cy;
ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f);
hda = (da / (float)ndivs) / 2.0f;
kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
if (da < 0.0f)
kappa = -kappa;
for (i = 0; i <= ndivs; i++) {
a = a1 + da * ((float)i/(float)ndivs);
dx = cosf(a);
dy = sinf(a);
nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t);
nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t);
if (i > 0)
nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y);
px = x;
py = y;
ptanx = tanx;
ptany = tany;
}
*cpx = x2;
*cpy = y2;
}
static void nsvg__parsePath(NSVGparser* p, const char** attr)
{
const char* s = NULL;
char cmd = '\0';
float args[10];
int nargs;
int rargs = 0;
float cpx, cpy, cpx2, cpy2;
const char* tmp[4];
char closedFlag;
int i;
char item[64];
for (i = 0; attr[i]; i += 2) {
if (strcmp(attr[i], "d") == 0) {
s = attr[i + 1];
} else {
tmp[0] = attr[i];
tmp[1] = attr[i + 1];
tmp[2] = 0;
tmp[3] = 0;
nsvg__parseAttribs(p, tmp);
}
}
if (s) {
nsvg__resetPath(p);
cpx = 0; cpy = 0;
cpx2 = 0; cpy2 = 0;
closedFlag = 0;
nargs = 0;
while (*s) {
s = nsvg__getNextPathItem(s, item);
if (!*item) break;
if (nsvg__isnum(item[0])) {
if (nargs < 10)
args[nargs++] = (float)nsvg__atof(item);
if (nargs >= rargs) {
switch (cmd) {
case 'm':
case 'M':
nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
cmd = (cmd == 'm') ? 'l' : 'L';
rargs = nsvg__getArgsPerElement(cmd);
cpx2 = cpx; cpy2 = cpy;
break;
case 'l':
case 'L':
nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);
cpx2 = cpx; cpy2 = cpy;
break;
case 'H':
case 'h':
nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
cpx2 = cpx; cpy2 = cpy;
break;
case 'V':
case 'v':
nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
cpx2 = cpx; cpy2 = cpy;
break;
case 'C':
case 'c':
nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
break;
case 'S':
case 's':
nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
break;
case 'Q':
case 'q':
nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
break;
case 'T':
case 't':
nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0);
break;
case 'A':
case 'a':
nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
cpx2 = cpx; cpy2 = cpy;
break;
default:
if (nargs >= 2) {
cpx = args[nargs-2];
cpy = args[nargs-1];
cpx2 = cpx; cpy2 = cpy;
}
break;
}
nargs = 0;
}
} else {
cmd = item[0];
rargs = nsvg__getArgsPerElement(cmd);
if (cmd == 'M' || cmd == 'm') {
if (p->npts > 0)
nsvg__addPath(p, closedFlag);
nsvg__resetPath(p);
closedFlag = 0;
nargs = 0;
} else if (cmd == 'Z' || cmd == 'z') {
closedFlag = 1;
if (p->npts > 0) {
cpx = p->pts[0];
cpy = p->pts[1];
cpx2 = cpx; cpy2 = cpy;
nsvg__addPath(p, closedFlag);
}
nsvg__resetPath(p);
nsvg__moveTo(p, cpx, cpy);
closedFlag = 0;
nargs = 0;
}
}
}
if (p->npts)
nsvg__addPath(p, closedFlag);
}
nsvg__addShape(p);
}
static void nsvg__parseRect(NSVGparser* p, const char** attr)
{
float x = 0.0f;
float y = 0.0f;
float w = 0.0f;
float h = 0.0f;
float rx = -1.0f;
float ry = -1.0f;
int i;
for (i = 0; attr[i]; i += 2) {
if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p));
if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p));
if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
}
}
if (rx < 0.0f && ry > 0.0f) rx = ry;
if (ry < 0.0f && rx > 0.0f) ry = rx;
if (rx < 0.0f) rx = 0.0f;
if (ry < 0.0f) ry = 0.0f;
if (rx > w/2.0f) rx = w/2.0f;
if (ry > h/2.0f) ry = h/2.0f;
if (w != 0.0f && h != 0.0f) {
nsvg__resetPath(p);
if (rx < 0.00001f || ry < 0.0001f) {
nsvg__moveTo(p, x, y);
nsvg__lineTo(p, x+w, y);
nsvg__lineTo(p, x+w, y+h);
nsvg__lineTo(p, x, y+h);
} else {
nsvg__moveTo(p, x+rx, y);
nsvg__lineTo(p, x+w-rx, y);
nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry);
nsvg__lineTo(p, x+w, y+h-ry);
nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h);
nsvg__lineTo(p, x+rx, y+h);
nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry);
nsvg__lineTo(p, x, y+ry);
nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y);
}
nsvg__addPath(p, 1);
nsvg__addShape(p);
}
}
static void nsvg__parseCircle(NSVGparser* p, const char** attr)
{
float cx = 0.0f;
float cy = 0.0f;
float r = 0.0f;
int i;
for (i = 0; attr[i]; i += 2) {
if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p)));
}
}
if (r > 0.0f) {
nsvg__resetPath(p);
nsvg__moveTo(p, cx+r, cy);
nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r);
nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy);
nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r);
nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy);
nsvg__addPath(p, 1);
nsvg__addShape(p);
}
}
static void nsvg__parseEllipse(NSVGparser* p, const char** attr)
{
float cx = 0.0f;
float cy = 0.0f;
float rx = 0.0f;
float ry = 0.0f;
int i;
for (i = 0; attr[i]; i += 2) {
if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
}
}
if (rx > 0.0f && ry > 0.0f) {
nsvg__resetPath(p);
nsvg__moveTo(p, cx+rx, cy);
nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry);
nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy);
nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry);
nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy);
nsvg__addPath(p, 1);
nsvg__addShape(p);
}
}
static void nsvg__parseLine(NSVGparser* p, const char** attr)
{
float x1 = 0.0;
float y1 = 0.0;
float x2 = 0.0;
float y2 = 0.0;
int i;
for (i = 0; attr[i]; i += 2) {
if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
}
}
nsvg__resetPath(p);
nsvg__moveTo(p, x1, y1);
nsvg__lineTo(p, x2, y2);
nsvg__addPath(p, 0);
nsvg__addShape(p);
}
static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag)
{
int i;
const char* s;
float args[2];
int nargs, npts = 0;
char item[64];
nsvg__resetPath(p);
for (i = 0; attr[i]; i += 2) {
if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "points") == 0) {
s = attr[i + 1];
nargs = 0;
while (*s) {
s = nsvg__getNextPathItem(s, item);
args[nargs++] = (float)nsvg__atof(item);
if (nargs >= 2) {
if (npts == 0)
nsvg__moveTo(p, args[0], args[1]);
else
nsvg__lineTo(p, args[0], args[1]);
nargs = 0;
npts++;
}
}
}
}
}
nsvg__addPath(p, (char)closeFlag);
nsvg__addShape(p);
}
static void nsvg__parseSVG(NSVGparser* p, const char** attr)
{
int i;
for (i = 0; attr[i]; i += 2) {
if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "width") == 0) {
p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
} else if (strcmp(attr[i], "height") == 0) {
p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
} else if (strcmp(attr[i], "viewBox") == 0) {
const char *s = attr[i + 1];
char buf[64];
s = nsvg__parseNumber(s, buf, 64);
p->viewMinx = nsvg__atof(buf);
while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;
if (!*s) return;
s = nsvg__parseNumber(s, buf, 64);
p->viewMiny = nsvg__atof(buf);
while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;
if (!*s) return;
s = nsvg__parseNumber(s, buf, 64);
p->viewWidth = nsvg__atof(buf);
while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;
if (!*s) return;
s = nsvg__parseNumber(s, buf, 64);
p->viewHeight = nsvg__atof(buf);
} else if (strcmp(attr[i], "preserveAspectRatio") == 0) {
if (strstr(attr[i + 1], "none") != 0) {
p->alignType = NSVG_ALIGN_NONE;
} else {
if (strstr(attr[i + 1], "xMin") != 0)
p->alignX = NSVG_ALIGN_MIN;
else if (strstr(attr[i + 1], "xMid") != 0)
p->alignX = NSVG_ALIGN_MID;
else if (strstr(attr[i + 1], "xMax") != 0)
p->alignX = NSVG_ALIGN_MAX;
if (strstr(attr[i + 1], "yMin") != 0)
p->alignY = NSVG_ALIGN_MIN;
else if (strstr(attr[i + 1], "yMid") != 0)
p->alignY = NSVG_ALIGN_MID;
else if (strstr(attr[i + 1], "yMax") != 0)
p->alignY = NSVG_ALIGN_MAX;
p->alignType = NSVG_ALIGN_MEET;
if (strstr(attr[i + 1], "slice") != 0)
p->alignType = NSVG_ALIGN_SLICE;
}
}
}
}
}
static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type)
{
int i;
NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData));
if (grad == NULL) return;
memset(grad, 0, sizeof(NSVGgradientData));
grad->units = NSVG_OBJECT_SPACE;
grad->type = type;
if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) {
grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT);
grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
} else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) {
grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
}
nsvg__xformIdentity(grad->xform);
for (i = 0; attr[i]; i += 2) {
if (strcmp(attr[i], "id") == 0) {
strncpy(grad->id, attr[i+1], 63);
grad->id[63] = '\0';
} else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
if (strcmp(attr[i], "gradientUnits") == 0) {
if (strcmp(attr[i+1], "objectBoundingBox") == 0)
grad->units = NSVG_OBJECT_SPACE;
else
grad->units = NSVG_USER_SPACE;
} else if (strcmp(attr[i], "gradientTransform") == 0) {
nsvg__parseTransform(grad->xform, attr[i + 1]);
} else if (strcmp(attr[i], "cx") == 0) {
grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "cy") == 0) {
grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "r") == 0) {
grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "fx") == 0) {
grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "fy") == 0) {
grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "x1") == 0) {
grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "y1") == 0) {
grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "x2") == 0) {
grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "y2") == 0) {
grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]);
} else if (strcmp(attr[i], "spreadMethod") == 0) {
if (strcmp(attr[i+1], "pad") == 0)
grad->spread = NSVG_SPREAD_PAD;
else if (strcmp(attr[i+1], "reflect") == 0)
grad->spread = NSVG_SPREAD_REFLECT;
else if (strcmp(attr[i+1], "repeat") == 0)
grad->spread = NSVG_SPREAD_REPEAT;
} else if (strcmp(attr[i], "xlink:href") == 0) {
const char *href = attr[i+1];
strncpy(grad->ref, href+1, 62);
grad->ref[62] = '\0';
}
}
}
grad->next = p->gradients;
p->gradients = grad;
}
static void nsvg__parseGradientStop(NSVGparser* p, const char** attr)
{
NSVGattrib* curAttr = nsvg__getAttr(p);
NSVGgradientData* grad;
NSVGgradientStop* stop;
int i, idx;
curAttr->stopOffset = 0;
curAttr->stopColor = 0;
curAttr->stopOpacity = 1.0f;
for (i = 0; attr[i]; i += 2) {
nsvg__parseAttr(p, attr[i], attr[i + 1]);
}
grad = p->gradients;
if (grad == NULL) return;
grad->nstops++;
grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops);
if (grad->stops == NULL) return;
idx = grad->nstops-1;
for (i = 0; i < grad->nstops-1; i++) {
if (curAttr->stopOffset < grad->stops[i].offset) {
idx = i;
break;
}
}
if (idx != grad->nstops-1) {
for (i = grad->nstops-1; i > idx; i--)
grad->stops[i] = grad->stops[i-1];
}
stop = &grad->stops[idx];
stop->color = curAttr->stopColor;
stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24;
stop->offset = curAttr->stopOffset;
}
static void nsvg__startElement(void* ud, const char* el, const char** attr)
{
NSVGparser* p = (NSVGparser*)ud;
if (p->defsFlag) {
if (strcmp(el, "linearGradient") == 0) {
nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
} else if (strcmp(el, "radialGradient") == 0) {
nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
} else if (strcmp(el, "stop") == 0) {
nsvg__parseGradientStop(p, attr);
}
return;
}
if (strcmp(el, "g") == 0) {
nsvg__pushAttr(p);
nsvg__parseAttribs(p, attr);
} else if (strcmp(el, "path") == 0) {
if (p->pathFlag)
return;
nsvg__pushAttr(p);
nsvg__parsePath(p, attr);
nsvg__popAttr(p);
} else if (strcmp(el, "rect") == 0) {
nsvg__pushAttr(p);
nsvg__parseRect(p, attr);
nsvg__popAttr(p);
} else if (strcmp(el, "circle") == 0) {
nsvg__pushAttr(p);
nsvg__parseCircle(p, attr);
nsvg__popAttr(p);
} else if (strcmp(el, "ellipse") == 0) {
nsvg__pushAttr(p);
nsvg__parseEllipse(p, attr);
nsvg__popAttr(p);
} else if (strcmp(el, "line") == 0) {
nsvg__pushAttr(p);
nsvg__parseLine(p, attr);
nsvg__popAttr(p);
} else if (strcmp(el, "polyline") == 0) {
nsvg__pushAttr(p);
nsvg__parsePoly(p, attr, 0);
nsvg__popAttr(p);
} else if (strcmp(el, "polygon") == 0) {
nsvg__pushAttr(p);
nsvg__parsePoly(p, attr, 1);
nsvg__popAttr(p);
} else if (strcmp(el, "linearGradient") == 0) {
nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
} else if (strcmp(el, "radialGradient") == 0) {
nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
} else if (strcmp(el, "stop") == 0) {
nsvg__parseGradientStop(p, attr);
} else if (strcmp(el, "defs") == 0) {
p->defsFlag = 1;
} else if (strcmp(el, "svg") == 0) {
nsvg__parseSVG(p, attr);
}
}
static void nsvg__endElement(void* ud, const char* el)
{
NSVGparser* p = (NSVGparser*)ud;
if (strcmp(el, "g") == 0) {
nsvg__popAttr(p);
} else if (strcmp(el, "path") == 0) {
p->pathFlag = 0;
} else if (strcmp(el, "defs") == 0) {
p->defsFlag = 0;
}
}
static void nsvg__content(void* ud, const char* s)
{
NSVG_NOTUSED(ud);
NSVG_NOTUSED(s);
}
static void nsvg__imageBounds(NSVGparser* p, float* bounds)
{
NSVGshape* shape;
shape = p->image->shapes;
if (shape == NULL) {
bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0;
return;
}
bounds[0] = shape->bounds[0];
bounds[1] = shape->bounds[1];
bounds[2] = shape->bounds[2];
bounds[3] = shape->bounds[3];
for (shape = shape->next; shape != NULL; shape = shape->next) {
bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]);
bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]);
bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]);
bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]);
}
}
static float nsvg__viewAlign(float content, float container, int type)
{
if (type == NSVG_ALIGN_MIN)
return 0;
else if (type == NSVG_ALIGN_MAX)
return container - content;
return (container - content) * 0.5f;
}
static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy)
{
float t[6];
nsvg__xformSetTranslation(t, tx, ty);
nsvg__xformMultiply (grad->xform, t);
nsvg__xformSetScale(t, sx, sy);
nsvg__xformMultiply (grad->xform, t);
}
static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
{
NSVGshape* shape;
NSVGpath* path;
float tx, ty, sx, sy, us, bounds[4], t[6], avgs;
int i;
float* pt;
nsvg__imageBounds(p, bounds);
if (p->viewWidth == 0) {
if (p->image->width > 0) {
p->viewWidth = p->image->width;
} else {
p->viewMinx = bounds[0];
p->viewWidth = bounds[2] - bounds[0];
}
}
if (p->viewHeight == 0) {
if (p->image->height > 0) {
p->viewHeight = p->image->height;
} else {
p->viewMiny = bounds[1];
p->viewHeight = bounds[3] - bounds[1];
}
}
if (p->image->width == 0)
p->image->width = p->viewWidth;
if (p->image->height == 0)
p->image->height = p->viewHeight;
tx = -p->viewMinx;
ty = -p->viewMiny;
sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0;
sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0;
us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);
if (p->alignType == NSVG_ALIGN_MEET) {
sx = sy = nsvg__minf(sx, sy);
tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx;
ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy;
} else if (p->alignType == NSVG_ALIGN_SLICE) {
sx = sy = nsvg__maxf(sx, sy);
tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx;
ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy;
}
sx *= us;
sy *= us;
avgs = (sx+sy) / 2.0f;
for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
shape->bounds[0] = (shape->bounds[0] + tx) * sx;
shape->bounds[1] = (shape->bounds[1] + ty) * sy;
shape->bounds[2] = (shape->bounds[2] + tx) * sx;
shape->bounds[3] = (shape->bounds[3] + ty) * sy;
for (path = shape->paths; path != NULL; path = path->next) {
path->bounds[0] = (path->bounds[0] + tx) * sx;
path->bounds[1] = (path->bounds[1] + ty) * sy;
path->bounds[2] = (path->bounds[2] + tx) * sx;
path->bounds[3] = (path->bounds[3] + ty) * sy;
for (i =0; i < path->npts; i++) {
pt = &path->pts[i*2];
pt[0] = (pt[0] + tx) * sx;
pt[1] = (pt[1] + ty) * sy;
}
}
if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) {
nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy);
memcpy(t, shape->fill.gradient->xform, sizeof(float)*6);
nsvg__xformInverse(shape->fill.gradient->xform, t);
}
if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) {
nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy);
memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6);
nsvg__xformInverse(shape->stroke.gradient->xform, t);
}
shape->strokeWidth *= avgs;
shape->strokeDashOffset *= avgs;
for (i = 0; i < shape->strokeDashCount; i++)
shape->strokeDashArray[i] *= avgs;
}
}
NSVGimage* nsvgParse(char* input, const char* units, float dpi)
{
NSVGparser* p;
NSVGimage* ret = 0;
p = nsvg__createParser();
if (p == NULL) {
return NULL;
}
p->dpi = dpi;
nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p);
nsvg__scaleToViewbox(p, units);
ret = p->image;
p->image = NULL;
nsvg__deleteParser(p);
return ret;
}
NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
{
FILE* fp = NULL;
size_t size;
char* data = NULL;
NSVGimage* image = NULL;
fp = fopen(filename, "rb");
if (!fp) goto error;
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
data = (char*)malloc(size+1);
if (data == NULL) goto error;
if (fread(data, 1, size, fp) != size) goto error;
data[size] = '\0';
fclose(fp);
image = nsvgParse(data, units, dpi);
free(data);
return image;
error:
if (fp) fclose(fp);
if (data) free(data);
if (image) nsvgDelete(image);
return NULL;
}
NSVGpath* nsvgDuplicatePath(NSVGpath* p)
{
NSVGpath* res = NULL;
if (p == NULL)
return NULL;
res = (NSVGpath*)malloc(sizeof(NSVGpath));
if (res == NULL) goto error;
memset(res, 0, sizeof(NSVGpath));
res->pts = (float*)malloc(p->npts*2*sizeof(float));
if (res->pts == NULL) goto error;
memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2);
res->npts = p->npts;
memcpy(res->bounds, p->bounds, sizeof(p->bounds));
res->closed = p->closed;
return res;
error:
if (res != NULL) {
free(res->pts);
free(res);
}
return NULL;
}
void nsvgDelete(NSVGimage* image)
{
NSVGshape *snext, *shape;
if (image == NULL) return;
shape = image->shapes;
while (shape != NULL) {
snext = shape->next;
nsvg__deletePaths(shape->paths);
nsvg__deletePaint(&shape->fill);
nsvg__deletePaint(&shape->stroke);
free(shape);
shape = snext;
}
free(image);
}
#endif