Added experimental SVG import

This commit is contained in:
Marius Kintel 2016-07-19 14:44:35 -04:00
parent fdd42098e3
commit bd20fe6bf8
38 changed files with 2329 additions and 6 deletions

View file

@ -73,6 +73,16 @@
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>svg</string>
</array>
<key>CFBundleTypeName</key>
<string>SVG file</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
<key>NSAppleScriptEnabled</key>
<true/>

View file

@ -14,6 +14,7 @@ include(eigen.pri)
include(boost.pri)
include(glib-2.0.pri)
include(gettext.pri)
include(libxml2.pri)
include(sparkle.pri)
include(harfbuzz.pri)
include(freetype.pri)

35
libxml2.pri Normal file
View file

@ -0,0 +1,35 @@
# Detect libxml2, then use this priority list to determine
# which library to use:
#
# Priority
# 1. LIBXML2_INCLUDEPATH / LIBXML2_LIBPATH (qmake parameter, not checked it given on commandline)
# 2. OPENSCAD_LIBRARIES (environment variable)
# 3. system's standard include paths from pkg-config
libxml2 {
# read environment variables
OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES)
LIBXML2_DIR = $$(LIBXML2DIR)
!isEmpty(OPENSCAD_LIBRARIES_DIR) {
isEmpty(LIBXML2_INCLUDEPATH) {
LIBXML2_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/libxml2
LIBXML2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib
}
}
isEmpty(LIBXML2_INCLUDEPATH) {
LIBXML2_CFLAGS = $$system("pkg-config --cflags libxml-2.0")
} else {
LIBXML2_CFLAGS = -I$$LIBXML2_INCLUDEPATH
}
isEmpty(LIBXML2_LIBPATH) {
LIBXML2_LIBS = $$system("pkg-config --libs libxml-2.0")
} else {
LIBXML2_LIBS = -L$$LIBXML2_LIBPATH -lxml2
}
QMAKE_CXXFLAGS += $$LIBXML2_CFLAGS
LIBS += $$LIBXML2_LIBS
}

View file

@ -183,6 +183,7 @@ CONFIG += harfbuzz
CONFIG += freetype
CONFIG += fontconfig
CONFIG += gettext
CONFIG += libxml2
#Uncomment the following line to enable the QScintilla editor
!nogui {
@ -351,7 +352,22 @@ HEADERS += src/version_check.h \
src/legacyeditor.h \
src/LibraryInfoDialog.h
SOURCES += src/version_check.cc \
SOURCES += \
src/libsvg/libsvg.cc \
src/libsvg/circle.cc \
src/libsvg/ellipse.cc \
src/libsvg/line.cc \
src/libsvg/polygon.cc \
src/libsvg/polyline.cc \
src/libsvg/rect.cc \
src/libsvg/group.cc \
src/libsvg/svgpage.cc \
src/libsvg/path.cc \
src/libsvg/shape.cc \
src/libsvg/transformation.cc \
src/libsvg/util.cc \
\
src/version_check.cc \
src/ProgressWidget.cc \
src/linalg.cc \
src/Camera.cc \
@ -436,6 +452,7 @@ SOURCES += src/version_check.cc \
src/import.cc \
src/import_stl.cc \
src/import_off.cc \
src/import_svg.cc \
src/renderer.cc \
src/colormap.cc \
src/ThrownTogetherRenderer.cc \

View file

@ -22,6 +22,7 @@ Feature::list_t Feature::feature_list;
const Feature Feature::ExperimentalEachExpression("lc-each", "Enable <code>each</code> expression in list comprehensions.");
const Feature Feature::ExperimentalElseExpression("lc-else", "Enable <code>else</code> expression in list comprehensions.");
const Feature Feature::ExperimentalForCExpression("lc-for-c", "Enable C-style <code>for</code> expression in list comprehensions.");
const Feature Feature::ExperimentalSvgImport("svg-import", "Enable SVG import.");
Feature::Feature(const std::string &name, const std::string &description)
: enabled(false), name(name), description(description)

View file

@ -17,6 +17,7 @@ public:
static const Feature ExperimentalEachExpression;
static const Feature ExperimentalElseExpression;
static const Feature ExperimentalForCExpression;
static const Feature ExperimentalSvgImport;
const std::string& get_name() const;
const std::string& get_description() const;

View file

@ -24,8 +24,8 @@
*
*/
#include "importnode.h"
#include "import.h"
#include "importnode.h"
#include "module.h"
#include "ModuleInstantiation.h"
@ -36,6 +36,7 @@
#include "dxfdata.h"
#include "printutils.h"
#include "fileutils.h"
#include "feature.h"
#include <sys/types.h>
#include <sstream>
@ -98,6 +99,7 @@ AbstractNode *ImportModule::instantiate(const Context *ctx, const ModuleInstanti
if (ext == ".stl") actualtype = TYPE_STL;
else if (ext == ".off") actualtype = TYPE_OFF;
else if (ext == ".dxf") actualtype = TYPE_DXF;
else if (Feature::ExperimentalSvgImport.is_enabled() && ext == ".svg") actualtype = TYPE_SVG;
}
ImportNode *node = new ImportNode(inst, actualtype);
@ -127,6 +129,11 @@ AbstractNode *ImportModule::instantiate(const Context *ctx, const ModuleInstanti
if (node->scale <= 0) node->scale = 1;
ValuePtr width = c.lookup_variable("width", true);
ValuePtr height = c.lookup_variable("height", true);
node->width = (width->type() == Value::NUMBER) ? width->toDouble() : -1;
node->height = (height->type() == Value::NUMBER) ? height->toDouble() : -1;
return node;
}
@ -148,6 +155,10 @@ const Geometry *ImportNode::createGeometry() const
g = p;
break;
}
case TYPE_SVG: {
g = import_svg(this->filename);
break;
}
case TYPE_DXF: {
DxfData dd(this->fn, this->fs, this->fa, this->filename, this->layername, this->origin_x, this->origin_y, this->scale);
g = dd.toPolygon2d();

View file

@ -4,4 +4,4 @@
class PolySet *import_stl(const std::string &filename);
PolySet *import_off(const std::string &filename);
const class Polygon2d &import_dxf(const std::string &filename);
class Polygon2d *import_svg(const std::string &filename);

62
src/import_svg.cc Normal file
View file

@ -0,0 +1,62 @@
#include "import.h"
#include "Polygon2d.h"
#include "printutils.h"
#include "libsvg/libsvg.h"
#include "clipper-utils.h"
Polygon2d *import_svg(const std::string &filename)
{
libsvg::shapes_list_t *shapes = libsvg::libsvg_read_file(filename.c_str());
double x_min = 1.0/0.0;
double x_max = -1.0/0.0;
double y_min = 1.0/0.0;
double y_max = -1.0/0.0;
for (libsvg::shapes_list_t::iterator it = shapes->begin();it != shapes->end();it++) {
PRINTD("SVG shape");
libsvg::shape *s = (*it);
for (libsvg::path_list_t::iterator it = s->get_path_list().begin();it != s->get_path_list().end();it++) {
PRINTD("SVG path");
libsvg::path_t& p = *it;
for (libsvg::path_t::iterator it2 = p.begin();it2 != p.end();it2++) {
Eigen::Vector3d& v = *it2;
if (v.x() < x_min) {
x_min = v.x();
}
if (v.x() > x_max) {
x_max = v.x();
}
if (v.y() < y_min) {
y_min = v.y();
}
if (v.y() > y_max) {
y_max = v.y();
}
}
}
}
double cx = (x_min + x_max) / 2;
double cy = (y_min + y_max) / 2;
std::vector<const Polygon2d*> polygons;
for (libsvg::shapes_list_t::iterator it = shapes->begin();it != shapes->end();it++) {
Polygon2d *poly = new Polygon2d();
libsvg::shape *s = (*it);
for (libsvg::path_list_t::iterator it = s->get_path_list().begin();it != s->get_path_list().end();it++) {
libsvg::path_t& p = *it;
Outline2d outline;
for (libsvg::path_t::iterator it2 = p.begin();it2 != p.end();it2++) {
Eigen::Vector3d& v = *it2;
double x = v.x() - cx;
double y = -v.y() + cy;
outline.vertices.push_back(Vector2d(x, y));
outline.positive=true;
}
poly->addOutline(outline);
}
polygons.push_back(poly);
}
return ClipperUtils::apply(polygons, ClipperLib::ctUnion);
}

View file

@ -7,6 +7,7 @@ enum import_type_e {
TYPE_UNKNOWN,
TYPE_STL,
TYPE_OFF,
TYPE_SVG,
TYPE_DXF
};
@ -24,5 +25,6 @@ public:
int convexity;
double fn, fs, fa;
double origin_x, origin_y, scale;
double width, height;
virtual const class Geometry *createGeometry() const;
};

43
src/libsvg/circle.cc Normal file
View file

@ -0,0 +1,43 @@
#include "circle.h"
namespace libsvg {
const std::string circle::name("circle");
circle::circle()
{
}
circle::circle(const circle& orig) : shape(orig)
{
r = orig.r;
}
circle::~circle()
{
}
void
circle::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
this->x = parse_double(attrs["cx"]);
this->y = parse_double(attrs["cy"]);
this->r = parse_double(attrs["r"]);
path_t path;
draw_ellipse(path, get_x(), get_y(), get_radius(), get_radius());
path_list.push_back(path);
}
void
circle::dump()
{
std::cout << get_name()
<< ": x = " << this->x
<< ": y = " << this->y
<< ": r = " << this->r
<< std::endl;
}
}

29
src/libsvg/circle.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef LIBSVG_CIRCLE_H
#define LIBSVG_CIRCLE_H
#include "shape.h"
namespace libsvg {
class circle : public shape {
protected:
double r;
public:
circle();
circle(const circle& orig);
virtual ~circle();
virtual double get_radius() { return r; }
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return circle::name; };
static const std::string name;
};
}
#endif /* LIBSVG_CIRCLE_H */

49
src/libsvg/ellipse.cc Normal file
View file

@ -0,0 +1,49 @@
#include <stdlib.h>
#include <iostream>
#include "ellipse.h"
namespace libsvg {
const std::string ellipse::name("ellipse");
ellipse::ellipse()
{
}
ellipse::ellipse(const ellipse& orig) : shape(orig)
{
rx = orig.rx;
ry = orig.ry;
}
ellipse::~ellipse()
{
}
void
ellipse::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
this->x = parse_double(attrs["cx"]);
this->y = parse_double(attrs["cy"]);
this->rx = parse_double(attrs["rx"]);
this->ry = parse_double(attrs["ry"]);
path_t path;
draw_ellipse(path, get_x(), get_y(), get_radius_x(), get_radius_y());
path_list.push_back(path);
}
void
ellipse::dump()
{
std::cout << get_name()
<< ": x = " << this->x
<< ": y = " << this->y
<< ": rx = " << this->rx
<< ": ry = " << this->ry
<< std::endl;
}
}

31
src/libsvg/ellipse.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef LIBSVG_ELLIPSE_H
#define LIBSVG_ELLIPSE_H
#include "shape.h"
namespace libsvg {
class ellipse : public shape {
protected:
double rx;
double ry;
public:
ellipse();
ellipse(const ellipse& orig);
virtual ~ellipse();
virtual double get_radius_x() { return rx; }
virtual double get_radius_y() { return ry; }
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return ellipse::name; };
static const std::string name;
};
}
#endif /* LIBSVG_ELLIPSE_H */

37
src/libsvg/group.cc Normal file
View file

@ -0,0 +1,37 @@
#include <stdlib.h>
#include <iostream>
#include "group.h"
namespace libsvg {
const std::string group::name("g");
group::group()
{
}
group::group(const group& orig) : shape(orig)
{
}
group::~group()
{
}
void
group::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
}
void
group::dump()
{
std::cout << get_name()
<< ": x = " << this->x
<< ": y = " << this->y
<< std::endl;
}
}

28
src/libsvg/group.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef LIBSVG_GROUP_H
#define LIBSVG_GROUP_H
#include "shape.h"
namespace libsvg {
class group : public shape {
protected:
public:
group();
group(const group& orig);
virtual ~group();
virtual bool is_container() { return true; }
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return group::name; };
static const std::string name;
};
}
#endif /* LIBSVG_GROUP_H */

171
src/libsvg/libsvg.cc Normal file
View file

@ -0,0 +1,171 @@
#include <map>
#include <stack>
#include <vector>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <boost/filesystem.hpp>
#include <libxml/xmlreader.h>
#include "libsvg.h"
#include "shape.h"
#include "circle.h"
#include "ellipse.h"
#include "line.h"
#include "polygon.h"
#include "polyline.h"
#include "rect.h"
namespace fs = boost::filesystem;
namespace libsvg {
fs::path path("/");
typedef void (*cb_func)(const xmlChar *);
std::map<const std::string, cb_func> funcs;
std::map<const std::string, cb_func> end_funcs;
static bool in_defs = false;
static std::stack<shape *> shapes;
static shapes_list_t *shape_list;
attr_map_t read_attributes(xmlTextReaderPtr reader)
{
attr_map_t attrs;
int attr_count = xmlTextReaderAttributeCount(reader);
for (int idx = 0;idx < attr_count;idx++) {
xmlTextReaderMoveToAttributeNo(reader, idx);
const char *name = reinterpret_cast<const char *> (xmlTextReaderName(reader));
const char *value = reinterpret_cast<const char *> (xmlTextReaderValue(reader));
attrs[name] = value;
}
return attrs;
}
void processNode(xmlTextReaderPtr reader)
{
const char *name = reinterpret_cast<const char *> (xmlTextReaderName(reader));
if (name == NULL)
name = reinterpret_cast<const char *> (xmlStrdup(BAD_CAST "--"));
bool isEmpty;
xmlChar *value = xmlTextReaderValue(reader);
int node_type = xmlTextReaderNodeType(reader);
switch (node_type) {
case XML_READER_TYPE_ELEMENT:
isEmpty = xmlTextReaderIsEmptyElement(reader);
{
path /= name;
if (std::string("defs") == name) {
in_defs = true;
}
shape *s = shape::create_from_name(name);
if (!in_defs && s) {
attr_map_t attrs = read_attributes(reader);
s->set_attrs(attrs);
shape_list->push_back(s);
if (!shapes.empty()) {
shapes.top()->add_child(s);
}
if (s->is_container()) {
shapes.push(s);
}
s->apply_transform();
}
}
if (!isEmpty) {
break;
}
case XML_READER_TYPE_END_ELEMENT:
{
if (std::string("defs") == name) {
in_defs = false;
} else if (std::string("g") == name) {
shapes.pop();
}
cb_func f1 = end_funcs[path.string()];
if (f1) {
f1(value);
}
path = path.parent_path();
}
break;
case XML_READER_TYPE_TEXT:
{
// printf("%d %d %s %s\n",
// xmlTextReaderDepth(reader),
// xmlTextReaderNodeType(reader),
// path.c_str(),
// value);
cb_func f = funcs[path.string()];
if (f) {
f(value);
}
}
break;
}
xmlFree(value);
xmlFree((void *) (name));
}
int streamFile(const char *filename)
{
xmlTextReaderPtr reader;
in_defs = false;
reader = xmlNewTextReaderFilename(filename);
xmlTextReaderSetParserProp(reader, XML_PARSER_SUBST_ENTITIES, 1);
if (reader != NULL) {
int ret = xmlTextReaderRead(reader);
while (ret == 1) {
processNode(reader);
ret = xmlTextReaderRead(reader);
}
xmlFreeTextReader(reader);
if (ret != 0) {
printf("%s : failed to parse\n", filename);
}
} else {
printf("Unable to open %s\n", filename);
}
return 0;
}
void dump(int idx, shape *s) {
for (int a = 0;a < idx;a++) {
std::cout << " ";
}
std::cout << "=> " << *s << std::endl;
std::vector<shape *> children = s->get_children();
for (std::vector<shape *>::iterator it = children.begin();it != children.end();it++) {
dump(idx + 1, *it);
}
}
shapes_list_t *
libsvg_read_file(const char *filename)
{
shape_list = new shapes_list_t();
streamFile(filename);
//#ifdef DEBUG
// if (!shape_list->empty()) {
// dump(0, (*shape_list)[0]);
// }
//#endif
return shape_list;
}
void
libsvg_free(shapes_list_t *shapes)
{
delete shapes;
}
}

21
src/libsvg/libsvg.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef LIBSVG_LIBSVG_H
#define LIBSVG_LIBSVG_H
#include "shape.h"
#include "rect.h"
#include "path.h"
#include "svgpage.h"
namespace libsvg {
typedef std::vector<shape *> shapes_list_t;
shapes_list_t *
libsvg_read_file(const char *filename);
void
libsvg_free(shapes_list_t *shapes);
}
#endif /* LIBSVG_LIBSVG_H */

47
src/libsvg/line.cc Normal file
View file

@ -0,0 +1,47 @@
#include "line.h"
namespace libsvg {
const std::string line::name("line");
line::line()
{
}
line::line(const line& orig) : shape(orig)
{
x2 = orig.x2;
y2 = orig.y2;
}
line::~line()
{
}
void
line::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
this->x = parse_double(attrs["x1"]);
this->y = parse_double(attrs["y1"]);
this->x2 = parse_double(attrs["x2"]);
this->y2 = parse_double(attrs["y2"]);
path_t path;
path.push_back(Eigen::Vector3d(x, y, 0));
path.push_back(Eigen::Vector3d(x2, y2, 0));
offset_path(path_list, path, get_stroke_width(), get_stroke_linecap());
}
void
line::dump()
{
std::cout << get_name()
<< ": x1 = " << this->x
<< ": y1 = " << this->y
<< ": x2 = " << this->x2
<< ": y2 = " << this->y2
<< std::endl;
}
}

31
src/libsvg/line.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef LIBSVG_LINE_H
#define LIBSVG_LINE_H
#include "shape.h"
namespace libsvg {
class line : public shape {
private:
double x2;
double y2;
public:
line();
line(const line& orig);
virtual ~line();
virtual double get_x2() { return x2; }
virtual double get_y2() { return y2; }
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return line::name; };
static const std::string name;
};
}
#endif /* LIBSVG_LINE_H */

488
src/libsvg/path.cc Normal file
View file

@ -0,0 +1,488 @@
#include <stdlib.h>
#include <string>
#include <iostream>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include "path.h"
namespace libsvg {
const std::string path::name("path");
/*
PATHSEG_CLOSEPATH z
PATHSEG_MOVETO_ABS M
PATHSEG_MOVETO_REL m
PATHSEG_LINETO_ABS L
PATHSEG_LINETO_REL l
PATHSEG_CURVETO_CUBIC_ABS C
PATHSEG_CURVETO_CUBIC_REL c
PATHSEG_CURVETO_QUADRATIC_ABS Q
PATHSEG_CURVETO_QUADRATIC_REL q
PATHSEG_ARC_ABS A
PATHSEG_ARC_REL a
PATHSEG_LINETO_HORIZONTAL_ABS H
PATHSEG_LINETO_HORIZONTAL_REL h
PATHSEG_LINETO_VERTICAL_ABS V
PATHSEG_LINETO_VERTICAL_REL v
PATHSEG_CURVETO_CUBIC_SMOOTH_ABS S
PATHSEG_CURVETO_CUBIC_SMOOTH_REL s
PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS T
PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL t
*/
path::path()
{
}
path::path(const path& orig) : shape(orig)
{
data = orig.data;
}
path::~path()
{
}
static double
vector_angle(double ux, double uy, double vx, double vy)
{
double angle = atan2(vy, vx) - atan2(uy, ux);
if (angle < 0) {
angle += 2 * M_PI;
}
return angle;
}
void
path::arc_to(path_t& path, double x1, double y1, double rx, double ry, double x2, double y2, double angle, bool large, bool sweep)
{
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// (F.6.5.1))
double cos_rad = cos(M_PI * angle / 180.0);
double sin_rad = sin(M_PI * angle / 180.0);
double dx = (x1 - x2) / 2;
double dy = (y1 - y2) / 2;
double x1_ = cos_rad * dx + sin_rad * dy;
double y1_ = -sin_rad * dx + cos_rad * dy;
double d = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
if (d > 1) {
rx = abs(sqrt(d) * rx);
ry = abs(sqrt(d) * ry);
}
// F.6.5.2
double t1 = rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_;
double t2 = rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_;
if (t1 < 0) {
t1 = 0;
}
double t3 = sqrt(t1 / t2);
if (large == sweep) {
t3 = -t3;
}
double cx_ = t3 * rx * y1_ / ry;
double cy_ = t3 * -ry * x1_ / rx;
// F.6.5.3
double cx = cos_rad * cx_ - sin_rad * cy_ + (x1 + x2) / 2.0;
double cy = sin_rad * cx_ + cos_rad * cy_ + (y1 + y2) / 2.0;
// F.6.5.4
double ux = (x1_ - cx_) / rx;
double uy = (y1_ - cy_) / ry;
double vx = (-x1_ - cx_) / rx;
double vy = (-y1_ - cy_) / ry;
double theta = vector_angle(1, 0, ux, uy);
double delta = vector_angle(ux, uy, vx, vy);
if (!sweep) {
delta -= 2 * M_PI;
}
int steps = std::abs(delta) * 10.0 / M_PI + 4;
for (int a = 0;a <= steps;a++) {
double phi = theta + delta * a / steps;
double xx = cos_rad * cos(phi) * rx - sin_rad * sin(phi) * ry;
double yy = sin_rad * cos(phi) * rx + cos_rad * sin(phi) * ry;
path.push_back(Eigen::Vector3d(xx + cx, yy + cy, 0));
}
}
void
path::curve_to(path_t& path, double x, double y, double cx1, double cy1, double x2, double y2)
{
unsigned long fn = 20;
for (unsigned long idx = 1;idx <= fn;idx++) {
const double a = idx * (1.0 / (double)fn);
const double xx = x * t(a, 2) + cx1 * 2 * t(a, 1) * a + x2 * a * a;
const double yy = y * t(a, 2) + cy1 * 2 * t(a, 1) * a + y2 * a * a;
path.push_back(Eigen::Vector3d(xx, yy, 0));
}
}
void
path::curve_to(path_t& path, double x, double y, double cx1, double cy1, double cx2, double cy2, double x2, double y2)
{
unsigned long fn = 20;
for (unsigned long idx = 1;idx <= fn;idx++) {
const double a = idx * (1.0 / (double)fn);
const double xx = x * t(a, 3) + cx1 * 3 * t(a, 2) * a + cx2 * 3 * t(a, 1) * a * a + x2 * a * a * a;
const double yy = y * t(a, 3) + cy1 * 3 * t(a, 2) * a + cy2 * 3 * t(a, 1) * a * a + y2 * a * a * a;
path.push_back(Eigen::Vector3d(xx, yy, 0));
}
}
void
path::set_attrs(attr_map_t& attrs)
{
std::string commands = "-zmlcqahvstZMLCQAHVST";
shape::set_attrs(attrs);
this->data = attrs["d"];
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep(" ,", commands.c_str());
tokenizer tokens(this->data, sep);
double x = 0;
double y = 0;
double xx = 0;
double yy = 0;
double rx = 0;
double ry = 0;
double cx1 = 0;
double cy1 = 0;
double cx2 = 0;
double cy2 = 0;
double angle = 0;
bool large = false;
bool sweep = false;
bool last_cmd_cubic_bezier = false;
bool last_cmd_quadratic_bezier = false;
char cmd = ' ';
int point = 0;
bool negate = false;
bool path_closed = false;
std::string exp;
path_list.push_back(path_t());
for (tokenizer::iterator it = tokens.begin();it != tokens.end();++it) {
std::string v = (*it);
double p = 0;
if ((v.length() == 1) && (commands.find(v) != std::string::npos)) {
if (v[0] == '-') {
negate = true;
continue;
}
point = -1;
cmd = v[0];
} else {
if (*v.rbegin() == 'e') {
exp = negate ? std::string("-") + v : v;
negate = false;
continue;
}
if (exp.empty()) {
p = parse_double(v);
p = negate ? -p : p;
} else {
p = parse_double(exp + (negate ? "-" : "") + v);
exp = "";
}
negate = false;
}
switch (cmd) {
case 'a':
case 'A':
//(rx ry x-axis-rotation large-arc-flag sweep-flag x y)
switch (point) {
case 0:
rx = abs(p);
break;
case 1:
ry = abs(p);
break;
case 2:
angle = p;
break;
case 3:
large = p > 0.5;
break;
case 4:
sweep = p > 0.5;
break;
case 5:
xx = cmd == 'a' ? x + p : p;
break;
case 6:
yy = cmd == 'a' ? y + p : p;
arc_to(path_list.back(), x, y, rx, ry, xx, yy, angle, large, sweep);
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = false;
break;
}
break;
case 'l':
case 'L':
switch (point) {
case 0:
xx = cmd == 'l' ? x + p : p;
break;
case 1:
yy = cmd == 'l' ? y + p : p;
path_list.back().push_back(Eigen::Vector3d(xx, yy, 0));
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = false;
break;
}
break;
case 'c':
case 'C':
switch (point) {
case 0:
cx1 = p;
break;
case 1:
cy1 = p;
break;
case 2:
cx2 = p;
break;
case 3:
cy2 = p;
break;
case 4:
xx = cmd == 'c' ? x + p : p;
break;
case 5:
yy = cmd == 'c' ? y + p : p;
cx1 = cmd == 'c' ? x + cx1 : cx1;
cy1 = cmd == 'c' ? y + cy1 : cy1;
cx2 = cmd == 'c' ? x + cx2 : cx2;
cy2 = cmd == 'c' ? y + cy2 : cy2;
curve_to(path_list.back(), x, y, cx1, cy1, cx2, cy2, xx, yy);
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = true;
last_cmd_quadratic_bezier = false;
break;
}
break;
case 's':
case 'S':
switch (point) {
case 0:
if (last_cmd_cubic_bezier) {
Eigen::Vector2d old_control_point(cx2, cy2);
Eigen::Vector2d current_point(x, y);
Eigen::Vector2d new_control_point = current_point + (current_point - old_control_point);
cx1 = new_control_point.x();
cy1 = new_control_point.y();
} else {
cx1 = x;
cy1 = y;
}
cx2 = p;
break;
case 1:
cy2 = p;
break;
case 2:
xx = cmd == 's' ? x + p : p;
break;
case 3:
yy = cmd == 's' ? y + p : p;
cx2 = cmd == 's' ? x + cx2 : cx2;
cy2 = cmd == 's' ? y + cy2 : cy2;
curve_to(path_list.back(), x, y, cx1, cy1, cx2, cy2, xx, yy);
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = true;
last_cmd_quadratic_bezier = false;
break;
}
break;
case 'q':
case 'Q':
switch (point) {
case 0:
cx1 = p;
break;
case 1:
cy1 = p;
break;
case 2:
xx = cmd == 'q' ? x + p : p;
break;
case 3:
yy = cmd == 'q' ? y + p : p;
cx1 = cmd == 'q' ? x + cx1 : cx1;
cy1 = cmd == 'q' ? y + cy1 : cy1;
curve_to(path_list.back(), x, y, cx1, cy1, xx, yy);
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = true;
break;
}
break;
case 't':
case 'T':
switch (point) {
case 0:
if (last_cmd_quadratic_bezier) {
Eigen::Vector2d old_control_point(cx1, cy1);
Eigen::Vector2d current_point(x, y);
Eigen::Vector2d new_control_point = current_point + (current_point - old_control_point);
cx1 = new_control_point.x();
cy1 = new_control_point.y();
} else {
cx1 = x;
cy1 = y;
}
xx = cmd == 't' ? x + p : p;
break;
case 1:
yy = cmd == 't' ? y + p : p;
curve_to(path_list.back(), x, y, cx1, cy1, xx, yy);
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = true;
break;
}
break;
case 'm':
case 'M':
switch (point) {
case 0:
xx = cmd == 'm' ? x + p : p;
break;
case 1:
yy = cmd == 'm' ? y + p : p;
cmd = cmd == 'm' ? 'l' : 'L';
path_t path = path_list.back();
if (!path_list.back().empty()) {
if (is_open_path(path)) {
path_list.pop_back();
offset_path(path_list, path, get_stroke_width(), get_stroke_linecap());
}
path_list.push_back(path_t());
}
path_list.back().push_back(Eigen::Vector3d(xx, yy, 0));
x = xx;
y = yy;
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = false;
}
break;
case 'v':
case 'V':
switch (point) {
case 0:
y = cmd == 'v' ? y + p : p;
path_list.back().push_back(Eigen::Vector3d(x, y, 0));
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = false;
break;
}
break;
case 'h':
case 'H':
switch (point) {
case 0:
x = cmd == 'h' ? x + p : p;
path_list.back().push_back(Eigen::Vector3d(x, y, 0));
point = -1;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = false;
break;
}
break;
case 'z':
case 'Z':
if (!path_list.back().empty()) {
Eigen::Vector3d p = path_list.back()[0];
path_list.back().push_back(p);
x = p.x();
y = p.y();
}
path_list.push_back(path_t());
path_closed = true;
last_cmd_cubic_bezier = false;
last_cmd_quadratic_bezier = false;
break;
}
point++;
}
while (!path_list.empty() && path_list.back().empty()) {
path_list.pop_back();
}
if (!path_closed && !path_list.empty()) {
path_t path = path_list.back();
if (is_open_path(path)) {
path_list.pop_back();
offset_path(path_list, path, get_stroke_width(), get_stroke_linecap());
}
}
}
bool
path::is_open_path(path_t& path)
{
const Eigen::Vector3d p1 = path[0];
const Eigen::Vector3d p2 = path.back();
double distance = pow(pow(p1.x() - p2.x(), 2) + pow(p1.y() - p2.y(), 2) + pow(p1.z() - p2.z(), 2), 0.5);
return distance > 0.1;
}
void
path::dump()
{
std::cout << get_name()
<< ": x = " << this->x
<< ", y = " << this->y;
for (path_list_t::iterator it = path_list.begin();it != path_list.end();it++) {
path_t& p = *it;
std::cout << "[";
for (path_t::iterator it2 = p.begin();it2 != p.end();it2++) {
Eigen::Vector3d& v = *it2;
std::cout << " (" << v.x() << ", " << v.y() << ")";
}
std::cout << "]";
}
std::cout << std::endl;
}
}

41
src/libsvg/path.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef LIBSVG_PATH_H
#define LIBSVG_PATH_H
#include <math.h>
#include <vector>
#include "shape.h"
namespace libsvg {
class path : public shape {
protected:
std::string data;
private:
inline double t(double t, int exp) const {
return pow(1.0 - t, exp);
}
bool is_open_path(path_t& path);
void arc_to(path_t& path, double x, double y, double rx, double ry, double x2, double y2, double angle, bool large, bool sweep);
void curve_to(path_t& path, double x, double y, double cx1, double cy1, double x2, double y2);
void curve_to(path_t& path, double x, double y, double cx1, double cy1, double cx2, double cy2, double x2, double y2);
public:
path();
path(const path& orig);
virtual ~path();
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return path::name; };
static const std::string name;
};
}
#endif /* LIBSVG_PATH_H */

52
src/libsvg/polygon.cc Normal file
View file

@ -0,0 +1,52 @@
#include <boost/tokenizer.hpp>
#include "polygon.h"
namespace libsvg {
const std::string polygon::name("polygon");
polygon::polygon()
{
}
polygon::polygon(const polygon& orig) : shape(orig)
{
points = orig.points;
}
polygon::~polygon()
{
}
void
polygon::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
this->points = attrs["points"];
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep(" ,");
tokenizer tokens(this->points, sep);
double x = 0.0;
path_t path;
bool first = true;
for (tokenizer::iterator it = tokens.begin();it != tokens.end();++it) {
std::string v = (*it);
double p = parse_double(v);
if (first) {
x = p;
} else {
path.push_back(Eigen::Vector3d(x, p, 0));
}
first = !first;
}
if (!path.empty()) {
path.push_back(path[0]);
}
path_list.push_back(path);
}
}

26
src/libsvg/polygon.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef LIBSVG_POLYGON_H
#define LIBSVG_POLYGON_H
#include "shape.h"
namespace libsvg {
class polygon : public shape {
private:
std::string points;
public:
polygon();
polygon(const polygon& orig);
virtual ~polygon();
virtual void set_attrs(attr_map_t& attrs);
const std::string& get_name() const { return polygon::name; };
static const std::string name;
};
}
#endif /* LIBSVG_POLYGON_H */

50
src/libsvg/polyline.cc Normal file
View file

@ -0,0 +1,50 @@
#include <boost/tokenizer.hpp>
#include "polyline.h"
namespace libsvg {
const std::string polyline::name("polyline");
polyline::polyline()
{
}
polyline::polyline(const polyline& orig) : shape(orig)
{
points = orig.points;
}
polyline::~polyline()
{
}
void
polyline::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
this->points = attrs["points"];
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep(" ,");
tokenizer tokens(this->points, sep);
double x = 0.0;
path_t path;
bool first = true;
for (tokenizer::iterator it = tokens.begin();it != tokens.end();++it) {
std::string v = (*it);
double p = parse_double(v);
if (first) {
x = p;
} else {
path.push_back(Eigen::Vector3d(x, p, 0));
}
first = !first;
}
offset_path(path_list, path, get_stroke_width(), get_stroke_linecap());
}
}

26
src/libsvg/polyline.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef LIBSVG_POLYLINE_H
#define LIBSVG_POLYLINE_H
#include "shape.h"
namespace libsvg {
class polyline : public shape {
private:
std::string points;
public:
polyline();
polyline(const polyline& orig);
virtual ~polyline();
virtual void set_attrs(attr_map_t& attrs);
const std::string& get_name() const { return polyline::name; };
static const std::string name;
};
}
#endif /* LIBSVG_POLYLINE_H */

150
src/libsvg/rect.cc Normal file
View file

@ -0,0 +1,150 @@
#include <stdlib.h>
#include <iostream>
#include <boost/format.hpp>
#include "rect.h"
namespace libsvg {
const std::string rect::name("rect");
rect::rect()
{
}
rect::rect(const rect& orig) : path(orig)
{
rx = orig.rx;
ry = orig.ry;
width = orig.width;
height = orig.height;
}
rect::~rect()
{
}
/**
* Let rx and ry be length values.
*
* If neither rx nor ry are properly specified, then set both rx and
* ry to 0. (This will result in square corners.)
* Otherwise, if a properly specified value is provided for rx, but not
* for ry, then set both rx and ry to the value of rx.
* Otherwise, if a properly specified value is provided for ry, but not
* for rx, then set both rx and ry to the value of ry.
* Otherwise, both rx and ry were specified properly. Set rx to the
* value of rx and ry to the value of ry.
* If rx is greater than half of width, then set rx to half of width.
* If ry is greater than half of height, then set ry to half of height.
* The effective values of rx and ry are rx and ry, respectively.
*
*
* Mathematically, a rect element can be mapped to an equivalent
* path element as follows: (Note: all coordinate and length values
* are first converted into user space coordinates according to Units.)
*
* 1) perform an absolute moveto operation to location (x+rx,y), where x is
* the value of the rect element's x attribute converted to user space,
* rx is the effective value of the rx attribute converted to user space
* and y is the value of the y attribute converted to user space
*
* 2) perform an absolute horizontal lineto operation to location (x+width-rx,y),
* where width is the rect element's width attribute converted to user
* space
*
* 3) perform an absolute elliptical arc operation to coordinate (x+width,y+ry),
* where the effective values for the rx and ry attributes on the rect
* element converted to user space are used as the rx and ry attributes on
* the elliptical arc command, respectively, the x-axis-rotation is set to
* zero, the large-arc-flag is set to zero, and the sweep-flag is set to
* one
*
* 4) perform a absolute vertical lineto to location (x+width,y+height-ry),
* where height is the rect element's height attribute converted to user
* space
*
* 5) perform an absolute elliptical arc operation to coordinate (x+width-rx,y+height)
*
* 6) perform an absolute horizontal lineto to location (x+rx,y+height)
*
* 7) perform an absolute elliptical arc operation to coordinate (x,y+height-ry)
*
* 8) perform an absolute absolute vertical lineto to location (x,y+ry)
*
* 9) perform an absolute elliptical arc operation to coordinate (x+rx,y)
*/
void
rect::set_attrs(attr_map_t& attrs)
{
shape::set_attrs(attrs);
this->x = parse_double(attrs["x"]);
this->y = parse_double(attrs["y"]);
this->width = parse_double(attrs["width"]);
this->height = parse_double(attrs["height"]);
this->rx = parse_double(attrs["rx"]);
this->ry = parse_double(attrs["ry"]);
bool has_rx = !(abs(rx) < 1e-8);
bool has_ry = !(abs(ry) < 1e-8);
if (has_rx || has_ry) {
if (!has_rx) {
this->rx = this->ry;
} else if (!has_ry) {
this->ry = this->rx;
}
if (this->rx > (this->width / 2)) {
this->rx = this->width / 2;
}
if (this->ry > (this->height / 2)) {
this->ry = this->height / 2;
}
std::string path = boost::str(boost::format(""
"M %1%,%2% "
"H %3% "
"A %4%,%5% 0 0,1 %6%,%7% "
"V %8% "
"A %9%,%10% 0 0,1 %11%,%12% "
"H %13% "
"A %14%,%15% 0 0,1 %16%,%17% "
"V %18% "
"A %19%,%20% 0 0,1 %21%,%22% "
"z")
% (x + rx) % y
% (x + width - rx)
% rx % ry % (x + width) % (y + ry)
% (y + height - ry)
% rx % ry % (x + width - rx) % (y + height)
% (x + rx)
% rx % ry % x % (y + height - ry)
% (y + ry)
% rx % ry % (x + rx) % y
);
attrs["d"] = path;
path::set_attrs(attrs);
} else {
path_t path;
path.push_back(Eigen::Vector3d(get_x(), get_y(), 0));
path.push_back(Eigen::Vector3d(get_x() + get_width(), get_y(), 0));
path.push_back(Eigen::Vector3d(get_x() + get_width(), get_y() + get_height(), 0));
path.push_back(Eigen::Vector3d(get_x(), get_y() + get_height(), 0));
path.push_back(Eigen::Vector3d(get_x(), get_y(), 0));
path_list.push_back(path);
}
}
void
rect::dump()
{
std::cout << get_name()
<< ": x = " << this->x
<< ": y = " << this->y
<< ": width = " << this->width
<< ": height = " << this->height
<< std::endl;
}
}

36
src/libsvg/rect.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef LIBSVG_RECT_H
#define LIBSVG_RECT_H
#include "path.h"
namespace libsvg {
class rect : public path
{
protected:
double width;
double height;
double rx;
double ry;
public:
rect();
rect(const rect& orig);
virtual ~rect();
virtual double get_width() { return width; }
virtual double get_height() { return height; }
virtual double get_rx() { return rx; }
virtual double get_ry() { return ry; }
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return rect::name; };
static const std::string name;
};
}
#endif /* LIBSVG_RECT_H */

256
src/libsvg/shape.cc Normal file
View file

@ -0,0 +1,256 @@
#include <stdio.h>
#include <string>
#include <vector>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/spirit/include/qi.hpp>
#include "shape.h"
#include "circle.h"
#include "ellipse.h"
#include "line.h"
#include "polygon.h"
#include "polyline.h"
#include "rect.h"
#include "svgpage.h"
#include "path.h"
#include "group.h"
#include "transformation.h"
namespace libsvg {
shape::shape() : parent(NULL), x(0), y(0)
{
}
shape::shape(const shape& orig)
{
}
shape::~shape()
{
}
shape *
shape::create_from_name(const char *name)
{
if (circle::name == name) {
return new circle();
} else if (ellipse::name == name) {
return new ellipse();
} else if (line::name == name) {
return new line();
} else if (polygon::name == name) {
return new polygon();
} else if (polyline::name == name) {
return new polyline();
} else if (rect::name == name) {
return new rect();
} else if (svgpage::name == name) {
return new svgpage();
} else if (path::name == name) {
return new path();
} else if (group::name == name) {
return new group();
} else {
return NULL;
}
}
void
shape::set_attrs(attr_map_t& attrs)
{
this->id = attrs["id"];
this->transform = attrs["transform"];
this->stroke_width = attrs["stroke-width"];
this->stroke_linecap = attrs["stroke-linecap"];
this->style = attrs["style"];
}
std::string
shape::get_style(std::string name)
{
std::vector<std::string> styles;
boost::split(styles, this->style, boost::is_any_of(";"));
for (std::vector<std::string>::iterator it = styles.begin();it != styles.end();it++) {
std::vector<std::string> values;
boost::split(values, *it, boost::is_any_of(":"));
if (values.size() != 2) {
continue;
}
if (name == values[0]) {
return values[1];
}
}
return std::string();
}
double
shape::get_stroke_width()
{
double stroke_width;
if (this->stroke_width.empty()) {
stroke_width = parse_double(get_style("stroke-width"));
} else {
stroke_width = parse_double(this->stroke_width);
}
return stroke_width < 0.01 ? 1 : stroke_width;
}
ClipperLib::EndType
shape::get_stroke_linecap()
{
std::string cap;
if (this->stroke_linecap.empty()) {
cap = get_style("stroke-linecap");
} else {
cap = this->stroke_linecap;
}
if (cap == "butt") {
return ClipperLib::etOpenButt;
} else if (cap == "round") {
return ClipperLib::etOpenRound;
} else if (cap == "square") {
return ClipperLib::etOpenSquare;
}
return ClipperLib::etOpenSquare;
}
void
shape::collect_transform_matrices(std::vector<Eigen::Matrix3d>& matrices, shape *s)
{
std::string transform_arg(s->transform);
boost::replace_all(transform_arg, "matrix", "m");
boost::replace_all(transform_arg, "translate", "t");
boost::replace_all(transform_arg, "scale", "s");
boost::replace_all(transform_arg, "rotate", "r");
boost::replace_all(transform_arg, "skewX", "x");
boost::replace_all(transform_arg, "skewY", "y");
std::string commands = "mtsrxy";
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep(" ,()", commands.c_str());
tokenizer tokens(transform_arg, sep);
transformation *t = NULL;
std::vector<transformation *> transformations;
for (tokenizer::iterator it = tokens.begin();it != tokens.end();++it) {
std::string v = (*it);
if ((v.length() == 1) && (commands.find(v) != std::string::npos)) {
if (t != NULL) {
transformations.push_back(t);
t = NULL;
}
switch (v[0]) {
case 'm':
t = new matrix();
break;
case 't':
t = new translate();
break;
case 's':
t = new scale();
break;
case 'r':
t = new rotate();
break;
case 'x':
t = new skew_x();
break;
case 'y':
t = new skew_y();
break;
default:
std::cout << "unknown transform op " << v << std::endl;
t = NULL;
}
} else {
if (t) {
t->add_arg(v);
}
}
}
if (t != NULL) {
transformations.push_back(t);
}
for (std::vector<transformation *>::reverse_iterator it = transformations.rbegin();it != transformations.rend();it++) {
transformation *t = *it;
std::vector<Eigen::Matrix3d> m = t->get_matrices();
matrices.insert(matrices.begin(), m.rbegin(), m.rend());
delete t;
}
}
void
shape::apply_transform()
{
std::vector<Eigen::Matrix3d> matrices;
for (shape *s = this;s->get_parent() != NULL;s = s->get_parent()) {
collect_transform_matrices(matrices, s);
}
path_list_t result_list;
for (path_list_t::iterator it = path_list.begin();it != path_list.end();it++) {
path_t& p = *it;
result_list.push_back(path_t());
for (path_t::iterator it2 = p.begin();it2 != p.end();it2++) {
Eigen::Vector3d result((*it2).x(), (*it2).y(), 1);
for (std::vector<Eigen::Matrix3d>::reverse_iterator it3 = matrices.rbegin();it3 != matrices.rend();it3++) {
result = *it3 * result;
}
result_list.back().push_back(result);
}
}
path_list = result_list;
}
void
shape::offset_path(path_list_t& path_list, path_t& path, double stroke_width, ClipperLib::EndType stroke_linecap) {
ClipperLib::Path line;
ClipperLib::Paths result;
for (path_t::iterator it = path.begin();it != path.end();it++) {
Eigen::Vector3d& v = *it;
line << ClipperLib::IntPoint(v.x() * 10000, v.y() * 10000);
}
ClipperLib::ClipperOffset co;
co.AddPath(line, ClipperLib::jtMiter, stroke_linecap);
co.Execute(result, stroke_width * 5000.0);
for (ClipperLib::Paths::iterator it = result.begin();it != result.end();it++) {
ClipperLib::Path& p = *it;
path_list.push_back(path_t());
for (ClipperLib::Path::iterator it2 = p.begin();it2 != p.end();it2++) {
ClipperLib::IntPoint& point = *it2;
path_list.back().push_back(Eigen::Vector3d(point.X / 10000.0, point.Y / 10000.0, 0));
}
path_list.back().push_back(Eigen::Vector3d(p[0].X / 10000.0, p[0].Y / 10000.0, 0));
}
}
void
shape::draw_ellipse(path_t& path, double x, double y, double rx, double ry) {
unsigned long fn = 40;
for (unsigned long idx = 1;idx <= fn;idx++) {
const double a = idx * (2 * M_PI / (double)fn);
const double xx = rx * sin(a) + x;
const double yy = ry * cos(a) + y;
path.push_back(Eigen::Vector3d(xx, yy, 0));
}
}
std::ostream & operator<<(std::ostream &os, const shape& s)
{
return os << s.get_name() << " | id = '" << s.id << "', transform = '" << s.transform << "'";
}
}

77
src/libsvg/shape.h Normal file
View file

@ -0,0 +1,77 @@
#ifndef LIBSVG_SHAPE_H
#define LIBSVG_SHAPE_H
#include <map>
#include <string>
#include <vector>
#include <iostream>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include "util.h"
#include "polyclipping/clipper.hpp"
namespace libsvg {
typedef std::vector<Eigen::Vector3d> path_t;
typedef std::vector<path_t> path_list_t;
typedef std::map<std::string, std::string> attr_map_t;
class shape {
private:
shape *parent;
std::vector<shape *> children;
protected:
std::string id;
double x;
double y;
path_list_t path_list;
std::string transform;
std::string stroke_width;
std::string stroke_linecap;
std::string style;
double get_stroke_width();
ClipperLib::EndType get_stroke_linecap();
std::string get_style(std::string name);
void draw_ellipse(path_t& path, double x, double y, double rx, double ry);
void offset_path(path_list_t& path_list, path_t& path, double stroke_width, ClipperLib::EndType stroke_linecap);
void collect_transform_matrices(std::vector<Eigen::Matrix3d>& matrices, shape *s);
public:
shape();
shape(const shape& orig);
virtual ~shape();
virtual shape * get_parent() { return parent; }
virtual void set_parent(shape *s) { parent = s; }
virtual void add_child(shape *s) { children.push_back(s); s->set_parent(this); }
virtual std::vector<shape *>& get_children() { return children; }
virtual std::string& get_id() { return id; }
virtual double get_x() { return x; }
virtual double get_y() { return y; }
virtual path_list_t& get_path_list() { return path_list; }
virtual bool is_container() { return false; }
virtual void apply_transform();
virtual const std::string& get_name() const = 0;
virtual void set_attrs(attr_map_t& attrs);
virtual void dump() {}
static shape * create_from_name(const char *name);
private:
friend std::ostream & operator<<(std::ostream &os, const shape& s);
};
}
#endif /* LIBSVG_SHAPE_H */

44
src/libsvg/svgpage.cc Normal file
View file

@ -0,0 +1,44 @@
#include <stdlib.h>
#include <iostream>
#include "svgpage.h"
namespace libsvg {
const std::string svgpage::name("svg");
svgpage::svgpage()
{
}
svgpage::svgpage(const svgpage& orig) : shape(orig)
{
width = orig.width;
height = orig.height;
}
svgpage::~svgpage()
{
}
void
svgpage::set_attrs(attr_map_t& attrs)
{
this->x = 0;
this->y = 0;
this->width = parse_double(attrs["width"]);
this->height = parse_double(attrs["height"]);
}
void
svgpage::dump()
{
std::cout << get_name()
<< ": x = " << this->x
<< ": y = " << this->y
<< ": width = " << this->width
<< ": height = " << this->height
<< std::endl;
}
}

31
src/libsvg/svgpage.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef LIBSVG_SVGPAGE_H
#define LIBSVG_SVGPAGE_H
#include "shape.h"
namespace libsvg {
class svgpage : public shape {
protected:
double width;
double height;
public:
svgpage();
svgpage(const svgpage& orig);
virtual ~svgpage();
virtual double get_width() { return width; }
virtual double get_height() { return height; }
virtual bool is_container() { return true; }
virtual void set_attrs(attr_map_t& attrs);
virtual void dump();
const std::string& get_name() const { return svgpage::name; };
static const std::string name;
};
}
#endif /* LIBSVG_SVGPAGE_H */

View file

@ -0,0 +1,274 @@
#include <string>
#include <vector>
#include <iostream>
#include "util.h"
#include "transformation.h"
namespace libsvg {
transformation::transformation(const std::string op, const std::string name) : op(op), name(name)
{
}
transformation::~transformation()
{
}
const std::string&
transformation::get_op()
{
return op;
}
const std::string&
transformation::get_name()
{
return name;
}
void
transformation::add_arg(const std::string arg)
{
double d = parse_double(arg);
args.push_back(d);
}
const std::string
transformation::get_args() {
std::stringstream str;
for (int a = 0;a < args.size();a++) {
str << ((a == 0) ? "(" : ", ") << args[a];
}
str << ")";
return str.str();
}
matrix::matrix() : transformation("m", "matrix")
{
}
matrix::~matrix()
{
}
/**
* matrix(<a> <b> <c> <d> <e> <f>), which specifies a transformation in
* the form of a transformation matrix of six values. matrix(a,b,c,d,e,f)
* is equivalent to applying the transformation matrix [a b c d e f].
*/
std::vector<Eigen::Matrix3d>
matrix::get_matrices()
{
if (args.size() != 6) {
std::cout << "invalid arguments for matrix" << std::endl;
return std::vector<Eigen::Matrix3d>();
}
Eigen::Matrix3d m;
m <<
args[0], args[2], args[4],
args[1], args[3], args[5],
0, 0, 1;
std::vector<Eigen::Matrix3d> result;
result.push_back(m);
return result;
}
translate::translate() : transformation("t", "translate")
{
}
translate::~translate()
{
}
/**
* translate(<tx> [<ty>]), which specifies a translation by tx and ty.
* If <ty> is not provided, it is assumed to be zero.
*/
std::vector<Eigen::Matrix3d>
translate::get_matrices()
{
if ((args.size() < 1) || (args.size() > 2)) {
std::cout << "invalid arguments for " << get_name() << std::endl;
return std::vector<Eigen::Matrix3d>();
}
double tx = args[0];
double ty = args.size() > 1 ? args[1] : 0;
Eigen::Matrix3d m;
m <<
1, 0, tx,
0, 1, ty,
0, 0, 1;
std::vector<Eigen::Matrix3d> result;
result.push_back(m);
return result;
}
scale::scale() : transformation("s", "scale")
{
}
scale::~scale()
{
}
/**
* scale(<sx> [<sy>]), which specifies a scale operation by sx and sy.
* If <sy> is not provided, it is assumed to be equal to <sx>.
*/
std::vector<Eigen::Matrix3d>
scale::get_matrices()
{
if ((args.size() < 1) || (args.size() > 2)) {
std::cout << "invalid arguments for " << get_name() << std::endl;
return std::vector<Eigen::Matrix3d>();
}
double sx = args[0];
double sy = args.size() > 1 ? args[1] : args[0];
Eigen::Matrix3d m;
m <<
sx, 0, 0,
0, sy, 0,
0, 0, 1;
std::vector<Eigen::Matrix3d> result;
result.push_back(m);
return result;
}
rotate::rotate() : transformation("r", "rotate")
{
}
rotate::~rotate()
{
}
/**
* rotate(<rotate-angle> [<cx> <cy>]), which specifies a rotation by
* <rotate-angle> degrees about a given point.
* If optional parameters <cx> and <cy> are not supplied, the rotate is
* about the origin of the current user coordinate system. The operation
* corresponds to the matrix [cos(a) sin(a) -sin(a) cos(a) 0 0].
* If optional parameters <cx> and <cy> are supplied, the rotate is
* about the point (cx, cy). The operation represents the equivalent of
* the following specification: translate(<cx>, <cy>) rotate(<rotate-angle>)
* translate(-<cx>, -<cy>).
*/
std::vector<Eigen::Matrix3d>
rotate::get_matrices()
{
if ((args.size() != 1) && (args.size() != 3)) {
std::cout << "invalid arguments for " << get_name() << std::endl;
return std::vector<Eigen::Matrix3d>();
}
bool has_center = args.size() == 3;
double angle = M_PI * args[0] / 180.0;
double cx = has_center ? args[1] : 0;
double cy = has_center ? args[2] : 0;
std::vector<Eigen::Matrix3d> result;
if (has_center) {
Eigen::Matrix3d t;
t <<
1, 0, -cx,
0, 1, -cy,
0, 0, 1;
result.push_back(t);
}
Eigen::Matrix3d m;
m <<
cos(angle), -sin(angle), 0,
sin(angle), cos(angle), 0,
0, 0, 1;
result.push_back(m);
if (has_center) {
Eigen::Matrix3d t;
t <<
1, 0, cx,
0, 1, cy,
0, 0, 1;
result.push_back(t);
}
return result;
}
skew_x::skew_x() : transformation("x", "skew_x")
{
}
skew_x::~skew_x()
{
}
/**
* skewX(<skew-angle>), which specifies a skew transformation along the x-axis.
*/
std::vector<Eigen::Matrix3d>
skew_x::get_matrices()
{
if (args.size() != 1) {
std::cout << "invalid arguments for " << get_name() << std::endl;
return std::vector<Eigen::Matrix3d>();
}
double angle = M_PI * args[0] / 180.0;
Eigen::Matrix3d m;
m <<
1, tan(angle), 0,
0, 1, 0,
0, 0, 1;
std::vector<Eigen::Matrix3d> result;
result.push_back(m);
return result;
}
skew_y::skew_y() : transformation("y", "skew_y")
{
}
skew_y::~skew_y()
{
}
/**
* skewY(<skew-angle>), which specifies a skew transformation along the y-axis.
*/
std::vector<Eigen::Matrix3d>
skew_y::get_matrices()
{
if (args.size() != 1) {
std::cout << "invalid arguments for " << get_name() << std::endl;
return std::vector<Eigen::Matrix3d>();
}
double angle = M_PI * args[0] / 180.0;
Eigen::Matrix3d m;
m <<
1, 0, 0,
tan(angle), 1, 0,
0, 0, 1;
std::vector<Eigen::Matrix3d> result;
result.push_back(m);
return result;
}
}

View file

@ -0,0 +1,81 @@
#ifndef LIBSVG_TRANSFORM_H
#define LIBSVG_TRANSFORM_H
#include <string>
#include <vector>
#include <Eigen/Core>
#include <Eigen/Geometry>
namespace libsvg {
class transformation {
private:
const std::string op;
const std::string name;
protected:
std::vector<double> args;
public:
transformation(const std::string op, const std::string name);
virtual ~transformation();
virtual const std::string& get_op();
virtual const std::string& get_name();
virtual const std::string get_args();
virtual void add_arg(const std::string arg);
virtual std::vector<Eigen::Matrix3d> get_matrices() = 0;
};
class matrix : public transformation {
public:
matrix();
virtual ~matrix();
virtual std::vector<Eigen::Matrix3d> get_matrices();
};
class translate : public transformation {
public:
translate();
virtual ~translate();
virtual std::vector<Eigen::Matrix3d> get_matrices();
};
class scale : public transformation {
public:
scale();
virtual ~scale();
virtual std::vector<Eigen::Matrix3d> get_matrices();
};
class rotate : public transformation {
public:
rotate();
virtual ~rotate();
virtual std::vector<Eigen::Matrix3d> get_matrices();
};
class skew_x : public transformation {
public:
skew_x();
virtual ~skew_x();
virtual std::vector<Eigen::Matrix3d> get_matrices();
};
class skew_y : public transformation {
public:
skew_y();
virtual ~skew_y();
virtual std::vector<Eigen::Matrix3d> get_matrices();
};
}
#endif /* LIBSVG_TRANSFORM_H */

27
src/libsvg/util.cc Normal file
View file

@ -0,0 +1,27 @@
#include <boost/spirit/include/qi.hpp>
#include "util.h"
namespace libsvg {
double
parse_double(const std::string& number)
{
std::string::const_iterator iter = number.begin(), end = number.end();
boost::spirit::qi::real_parser<double, boost::spirit::qi::real_policies<double> > double_parser;
double d = 0.0;
bool result = boost::spirit::qi::parse(iter, end, double_parser, d);
if(result && iter == end)
{
return d;
}
else
{
return 0;
}
}
}

8
src/libsvg/util.h Normal file
View file

@ -0,0 +1,8 @@
#include <string>
namespace libsvg {
double
parse_double(const std::string& number);
}

View file

@ -248,6 +248,7 @@ MainWindow::MainWindow(const QString &filename)
knownFileExtensions["stl"] = importStatement;
knownFileExtensions["off"] = importStatement;
knownFileExtensions["dxf"] = importStatement;
if (Feature::ExperimentalSvgImport.is_enabled()) knownFileExtensions["svg"] = importStatement;
knownFileExtensions["dat"] = surfaceStatement;
knownFileExtensions["png"] = surfaceStatement;
knownFileExtensions["scad"] = "";

View file

@ -551,6 +551,10 @@ add_definitions(${FONTCONFIG_CFLAGS})
add_definitions(${FREETYPE_CFLAGS})
add_definitions(${HARFBUZZ_CFLAGS})
find_package(LibXml2 2.9 REQUIRED)
add_definitions(${LIBXML2_DEFINITIONS})
inclusion(LIBXML2_DIR LIBXML2_INCLUDE_DIR)
# Image comparison - expected test image vs actual generated image
if (DIFFPNG)
@ -562,6 +566,8 @@ if (DIFFPNG)
message(STATUS "using diffpng for image comparison")
endif()
# Imagemagick
if (SKIP_IMAGEMAGICK)
if (NOT DIFFPNG)
# cross-building depends on this
@ -720,6 +726,21 @@ set(CORE_SOURCES
../src/FreetypeRenderer.cc
../src/lodepng.cpp
../src/PlatformUtils.cc
../src/libsvg/circle.cc
../src/libsvg/ellipse.cc
../src/libsvg/group.cc
../src/libsvg/libsvg.cc
../src/libsvg/line.cc
../src/libsvg/path.cc
../src/libsvg/polygon.cc
../src/libsvg/polyline.cc
../src/libsvg/rect.cc
../src/libsvg/shape.cc
../src/libsvg/svgpage.cc
../src/libsvg/transformation.cc
../src/libsvg/util.cc
../src/clipper-utils.cc
../src/polyclipping/clipper.cpp
../src/${PLATFORMUTILS_SOURCE}
${FLEX_OpenSCADlexer_OUTPUTS}
${BISON_OpenSCADparser_OUTPUTS})
@ -729,6 +750,7 @@ set(NOCGAL_SOURCES
../src/import.cc
../src/import_stl.cc
../src/import_off.cc
../src/import_svg.cc
../src/export.cc
../src/export_stl.cc
../src/export_amf.cc
@ -769,7 +791,8 @@ set(COMMON_SOURCES
../src/libtess2/Source/mesh.c
../src/libtess2/Source/priorityq.c
../src/libtess2/Source/sweep.c
../src/libtess2/Source/tess.c)
../src/libtess2/Source/tess.c
../src/Tree.cc)
#
# Offscreen OpenGL context source code
@ -805,7 +828,7 @@ if(NULLGL)
endif()
add_library(tests-core STATIC ${CORE_SOURCES})
target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${FONTCONFIG_LDFLAGS} ${FREETYPE_LDFLAGS} ${HARFBUZZ_LDFLAGS} ${Boost_LIBRARIES} ${COCOA_LIBRARY})
target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${FONTCONFIG_LDFLAGS} ${FREETYPE_LDFLAGS} ${HARFBUZZ_LDFLAGS} ${LIBXML2_LIBRARIES} ${Boost_LIBRARIES} ${COCOA_LIBRARY})
add_library(tests-common STATIC ${COMMON_SOURCES})
target_link_libraries(tests-common tests-core)
@ -1257,6 +1280,9 @@ disable_tests(
dxfpngtest_text-empty-tests
dxfpngtest_nothing-decimal-comma-separated
dxfpngtest_nullspace-2d
svgpngtest_text-empty-tests
svgpngtest_nothing-decimal-comma-separated
svgpngtest_nullspace-2d
# Not useful
throwntogethertest_internal-cavity
@ -1276,7 +1302,7 @@ disable_tests(
)
# 2D tests
list(APPEND FILES_2D ${FEATURES_2D_FILES} ${SCAD_DXF_FILES} ${EXAMPLE_2D_FILES})
list(APPEND FILES_2D ${FEATURES_2D_FILES} ${SCAD_DXF_FILES} ${SCAD_SVG_FILES} ${EXAMPLE_2D_FILES})
list(APPEND ALL_2D_FILES ${FILES_2D})
# FIXME: This test illustrates a weakness in child() combined with modifiers.
@ -1559,6 +1585,8 @@ add_cmdline_test(offcgalpngtest EXE ${PYTHON_EXECUTABLE} SCRIPT ${CMAKE_SOURCE_D
add_cmdline_test(dxfpngtest EXE ${PYTHON_EXECUTABLE} SCRIPT ${CMAKE_SOURCE_DIR}/export_import_pngtest.py ARGS --openscad=${OPENSCAD_BINPATH} --format=DXF --render=cgal EXPECTEDDIR cgalpngtest SUFFIX png FILES ${FILES_2D})
add_cmdline_test(svgpngtest EXE ${PYTHON_EXECUTABLE} SCRIPT ${CMAKE_SOURCE_DIR}/export_import_pngtest.py ARGS --openscad=${OPENSCAD_BINPATH} --format=SVG --render=cgal --enable=svg-import EXPECTEDDIR cgalpngtest SUFFIX png FILES ${FILES_2D})
#
# Failing tests