summaryrefslogtreecommitdiffstats
path: root/src/svg
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
commit35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch)
tree657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/svg
parentInitial commit. (diff)
downloadinkscape-upstream.tar.xz
inkscape-upstream.zip
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/svg')
-rw-r--r--src/svg/CMakeLists.txt37
-rw-r--r--src/svg/HACKING7
-rw-r--r--src/svg/README8
-rw-r--r--src/svg/css-ostringstream.cpp90
-rw-r--r--src/svg/css-ostringstream.h84
-rw-r--r--src/svg/path-string.cpp171
-rw-r--r--src/svg/path-string.h266
-rw-r--r--src/svg/sp-svg.def27
-rw-r--r--src/svg/stringstream.cpp112
-rw-r--r--src/svg/stringstream.h107
-rw-r--r--src/svg/strip-trailing-zeros.cpp54
-rw-r--r--src/svg/strip-trailing-zeros.h29
-rw-r--r--src/svg/svg-affine-test.h274
-rw-r--r--src/svg/svg-affine.cpp301
-rw-r--r--src/svg/svg-angle.cpp134
-rw-r--r--src/svg/svg-angle.h70
-rw-r--r--src/svg/svg-color-test.h134
-rw-r--r--src/svg/svg-color.cpp659
-rw-r--r--src/svg/svg-color.h24
-rw-r--r--src/svg/svg-icc-color.h38
-rw-r--r--src/svg/svg-length-test.h206
-rw-r--r--src/svg/svg-length.cpp607
-rw-r--r--src/svg/svg-length.h81
-rw-r--r--src/svg/svg-path-geom-test.h523
-rw-r--r--src/svg/svg-path.cpp137
-rw-r--r--src/svg/svg.h81
-rw-r--r--src/svg/test-stubs.cpp43
-rw-r--r--src/svg/test-stubs.h31
28 files changed, 4335 insertions, 0 deletions
diff --git a/src/svg/CMakeLists.txt b/src/svg/CMakeLists.txt
new file mode 100644
index 0000000..e52febe
--- /dev/null
+++ b/src/svg/CMakeLists.txt
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(svg_SRC
+ css-ostringstream.cpp
+ path-string.cpp
+ # sp-svg.def
+ stringstream.cpp
+ strip-trailing-zeros.cpp
+ svg-affine.cpp
+ svg-color.cpp
+ svg-angle.cpp
+ svg-length.cpp
+ svg-path.cpp
+ # test-stubs.cpp
+
+
+ # -------
+ # Headers
+ css-ostringstream.h
+ path-string.h
+ stringstream.h
+ strip-trailing-zeros.h
+ svg-affine-test.h
+ svg-color-test.h
+ svg-color.h
+ svg-icc-color.h
+ svg-angle.h
+ svg-length-test.h
+ svg-length.h
+ svg-path-geom-test.h
+ svg.h
+ # test-stubs.h
+
+)
+
+# add_inkscape_lib(svg_LIB "${svg_SRC}")
+add_inkscape_source("${svg_SRC}")
diff --git a/src/svg/HACKING b/src/svg/HACKING
new file mode 100644
index 0000000..e3d8b10
--- /dev/null
+++ b/src/svg/HACKING
@@ -0,0 +1,7 @@
+Here are svg specific functions, i.e. value parsing & creation.
+Most of these are written by Raph Levien for gill. I'll include correct
+copyright notices one day too.
+
+Lauris Kaplinski
+<lauris@ariman.ee>
+
diff --git a/src/svg/README b/src/svg/README
new file mode 100644
index 0000000..777f8b8
--- /dev/null
+++ b/src/svg/README
@@ -0,0 +1,8 @@
+
+
+This directory contains SVG utilities.
+
+To do:
+
+* Move to "util/svg".
+* Clean up.
diff --git a/src/svg/css-ostringstream.cpp b/src/svg/css-ostringstream.cpp
new file mode 100644
index 0000000..e476f9f
--- /dev/null
+++ b/src/svg/css-ostringstream.cpp
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "svg/css-ostringstream.h"
+#include "svg/strip-trailing-zeros.h"
+#include "preferences.h"
+
+Inkscape::CSSOStringStream::CSSOStringStream()
+{
+ /* These two are probably unnecessary now that we provide our own operator<< for float and
+ * double. */
+ ostr.imbue(std::locale::classic());
+ ostr.setf(std::ios::showpoint);
+
+ /* This one is (currently) needed though, as we currently use ostr.precision as a sort of
+ variable for storing the desired precision: see our two precision methods and our operator<<
+ methods for float and double. */
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ ostr.precision(prefs->getInt("/options/svgoutput/numericprecision", 8));
+}
+
+static void
+write_num(Inkscape::CSSOStringStream &os, unsigned const prec, double const d)
+{
+ char buf[32]; // haven't thought about how much is really required.
+ switch (prec) {
+ case 9: g_ascii_formatd(buf, sizeof(buf), "%.9f", d); break;
+ case 8: g_ascii_formatd(buf, sizeof(buf), "%.8f", d); break;
+ case 7: g_ascii_formatd(buf, sizeof(buf), "%.7f", d); break;
+ case 6: g_ascii_formatd(buf, sizeof(buf), "%.6f", d); break;
+ case 5: g_ascii_formatd(buf, sizeof(buf), "%.5f", d); break;
+ case 4: g_ascii_formatd(buf, sizeof(buf), "%.4f", d); break;
+ case 3: g_ascii_formatd(buf, sizeof(buf), "%.3f", d); break;
+ case 2: g_ascii_formatd(buf, sizeof(buf), "%.2f", d); break;
+ case 1: g_ascii_formatd(buf, sizeof(buf), "%.1f", d); break;
+ case 0: g_ascii_formatd(buf, sizeof(buf), "%.0f", d); break;
+ case 10: default: g_ascii_formatd(buf, sizeof(buf), "%.10f", d); break;
+ }
+ os << strip_trailing_zeros(buf);
+}
+
+Inkscape::CSSOStringStream &
+operator<<(Inkscape::CSSOStringStream &os, float const d)
+{
+ /* Try as integer first. */
+ {
+ long const n = long(d);
+ if (d == n) {
+ os << n;
+ return os;
+ }
+ }
+
+ write_num(os, os.precision(), d);
+ return os;
+}
+
+Inkscape::CSSOStringStream &
+operator<<(Inkscape::CSSOStringStream &os, double const d)
+{
+ /* Try as integer first. */
+ {
+ long const n = long(d);
+ if (d == n) {
+ os << n;
+ return os;
+ }
+ }
+
+ write_num(os, os.precision(), d);
+ return os;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/css-ostringstream.h b/src/svg/css-ostringstream.h
new file mode 100644
index 0000000..0df0406
--- /dev/null
+++ b/src/svg/css-ostringstream.h
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SVG_CSS_OSTRINGSTREAM_H_INKSCAPE
+#define SVG_CSS_OSTRINGSTREAM_H_INKSCAPE
+
+#include <sstream>
+
+namespace Inkscape {
+
+typedef std::ios_base &(*std_oct_type)(std::ios_base &);
+
+/**
+ * A thin wrapper around std::ostringstream, but writing floating point numbers in the format
+ * required by CSS: `.' as decimal separator, no `e' notation, no nan or inf.
+ */
+class CSSOStringStream {
+private:
+ std::ostringstream ostr;
+
+public:
+ CSSOStringStream();
+
+#define INK_CSS_STR_OP(_t) \
+ CSSOStringStream &operator<<(_t arg) { \
+ ostr << arg; \
+ return *this; \
+ }
+
+ INK_CSS_STR_OP(char)
+ INK_CSS_STR_OP(signed char)
+ INK_CSS_STR_OP(unsigned char)
+ INK_CSS_STR_OP(short)
+ INK_CSS_STR_OP(unsigned short)
+ INK_CSS_STR_OP(int)
+ INK_CSS_STR_OP(unsigned int)
+ INK_CSS_STR_OP(long)
+ INK_CSS_STR_OP(unsigned long)
+ INK_CSS_STR_OP(char const *)
+ INK_CSS_STR_OP(signed char const *)
+ INK_CSS_STR_OP(unsigned char const *)
+ INK_CSS_STR_OP(std::string const &)
+ INK_CSS_STR_OP(std_oct_type)
+
+#undef INK_CSS_STR_OP
+
+ std::string str() const {
+ return ostr.str();
+ }
+
+ std::streamsize precision() const {
+ return ostr.precision();
+ }
+
+ std::streamsize precision(std::streamsize p) {
+ return ostr.precision(p);
+ }
+};
+
+}
+
+Inkscape::CSSOStringStream &operator<<(Inkscape::CSSOStringStream &os, float d);
+
+Inkscape::CSSOStringStream &operator<<(Inkscape::CSSOStringStream &os, double d);
+
+
+#endif /* !SVG_CSS_OSTRINGSTREAM_H_INKSCAPE */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/path-string.cpp b/src/svg/path-string.cpp
new file mode 100644
index 0000000..95771ce
--- /dev/null
+++ b/src/svg/path-string.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::SVG::PathString - builder for SVG path strings
+ *//*
+ * Authors: see git history
+ *
+ * Copyright 2008 Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ * Copyright 2013 Tavmjong Bah <tavmjong@free.fr>
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "svg/path-string.h"
+#include "svg/stringstream.h"
+#include "svg/svg.h"
+#include "preferences.h"
+
+// 1<=numericprecision<=16, doubles are only accurate upto (slightly less than) 16 digits (and less than one digit doesn't make sense)
+// Please note that these constants are used to allocate sufficient space to hold serialized numbers
+static int const minprec = 1;
+static int const maxprec = 16;
+
+int Inkscape::SVG::PathString::numericprecision;
+int Inkscape::SVG::PathString::minimumexponent;
+Inkscape::SVG::PATHSTRING_FORMAT Inkscape::SVG::PathString::format;
+
+Inkscape::SVG::PathString::PathString() :
+ force_repeat_commands(!Inkscape::Preferences::get()->getBool("/options/svgoutput/disable_optimizations" ) && Inkscape::Preferences::get()->getBool("/options/svgoutput/forcerepeatcommands"))
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ format = (PATHSTRING_FORMAT)prefs->getIntLimited("/options/svgoutput/pathstring_format", 1, 0, PATHSTRING_FORMAT_SIZE - 1 );
+ numericprecision = std::max<int>(minprec,std::min<int>(maxprec, prefs->getInt("/options/svgoutput/numericprecision", 8)));
+ minimumexponent = prefs->getInt("/options/svgoutput/minimumexponent", -8);
+}
+
+// For absolute and relative paths... the entire path is kept in the "tail".
+// For optimized path, at a switch between absolute and relative, add tail to commonbase.
+void Inkscape::SVG::PathString::_appendOp(char abs_op, char rel_op) {
+ bool abs_op_repeated = _abs_state.prevop == abs_op && !force_repeat_commands;
+ bool rel_op_repeated = _rel_state.prevop == rel_op && !force_repeat_commands;
+
+ // For absolute and relative paths... do nothing.
+ switch (format) {
+ case PATHSTRING_ABSOLUTE:
+ if ( !abs_op_repeated ) _abs_state.appendOp(abs_op);
+ break;
+ case PATHSTRING_RELATIVE:
+ if ( !rel_op_repeated ) _rel_state.appendOp(rel_op);
+ break;
+ case PATHSTRING_OPTIMIZE:
+ {
+ unsigned int const abs_added_size = abs_op_repeated ? 0 : 2;
+ unsigned int const rel_added_size = rel_op_repeated ? 0 : 2;
+ if ( _rel_state.str.size()+2 < _abs_state.str.size()+abs_added_size ) {
+
+ // Store common prefix
+ commonbase += _rel_state.str;
+ _rel_state.str.clear();
+ // Copy rel to abs
+ _abs_state = _rel_state;
+ _abs_state.switches++;
+ abs_op_repeated = false;
+ // We do not have to copy abs to rel:
+ // _rel_state.str.size()+2 < _abs_state.str.size()+abs_added_size
+ // _rel_state.str.size()+rel_added_size < _abs_state.str.size()+2
+ // _abs_state.str.size()+2 > _rel_state.str.size()+rel_added_size
+ } else if ( _abs_state.str.size()+2 < _rel_state.str.size()+rel_added_size ) {
+
+ // Store common prefix
+ commonbase += _abs_state.str;
+ _abs_state.str.clear();
+ // Copy abs to rel
+ _rel_state = _abs_state;
+ _abs_state.switches++;
+ rel_op_repeated = false;
+ }
+ if ( !abs_op_repeated ) _abs_state.appendOp(abs_op);
+ if ( !rel_op_repeated ) _rel_state.appendOp(rel_op);
+ }
+ break;
+ default:
+ std::cout << "Better not be here!" << std::endl;
+ }
+}
+
+void Inkscape::SVG::PathString::State::append(Geom::Coord v) {
+ str += ' ';
+ appendNumber(v);
+}
+
+void Inkscape::SVG::PathString::State::append(Geom::Point p) {
+ str += ' ';
+ appendNumber(p[Geom::X]);
+ str += ',';
+ appendNumber(p[Geom::Y]);
+}
+
+void Inkscape::SVG::PathString::State::append(Geom::Coord v, Geom::Coord& rv) {
+ str += ' ';
+ appendNumber(v, rv);
+}
+
+void Inkscape::SVG::PathString::State::append(Geom::Point p, Geom::Point &rp) {
+ str += ' ';
+ appendNumber(p[Geom::X], rp[Geom::X]);
+ str += ',';
+ appendNumber(p[Geom::Y], rp[Geom::Y]);
+}
+
+// NOTE: The following appendRelativeCoord function will not be exact if the total number of digits needed
+// to represent the difference exceeds the precision of a double. This is not very likely though, and if
+// it does happen the imprecise value is not likely to be chosen (because it will probably be a lot longer
+// than the absolute value).
+
+// NOTE: This assumes v and r are already rounded (this includes flushing to zero if they are < 10^minexp)
+void Inkscape::SVG::PathString::State::appendRelativeCoord(Geom::Coord v, Geom::Coord r) {
+ int const minexp = minimumexponent-numericprecision+1;
+ int const digitsEnd = (int)floor(log10(std::min(fabs(v),fabs(r)))) - numericprecision; // Position just beyond the last significant digit of the smallest (in absolute sense) number
+ double const roundeddiff = floor((v-r)*pow(10.,-digitsEnd-1)+.5);
+ int const numDigits = (int)floor(log10(fabs(roundeddiff)))+1; // Number of digits in roundeddiff
+ if (r == 0) {
+ appendNumber(v, numericprecision, minexp);
+ } else if (v == 0) {
+ appendNumber(-r, numericprecision, minexp);
+ } else if (numDigits>0) {
+ appendNumber(v-r, numDigits, minexp);
+ } else {
+ // This assumes the input numbers are already rounded to 'precision' digits
+ str += '0';
+ }
+}
+
+void Inkscape::SVG::PathString::State::appendRelative(Geom::Point p, Geom::Point r) {
+ str += ' ';
+ appendRelativeCoord(p[Geom::X], r[Geom::X]);
+ str += ',';
+ appendRelativeCoord(p[Geom::Y], r[Geom::Y]);
+}
+
+void Inkscape::SVG::PathString::State::appendRelative(Geom::Coord v, Geom::Coord r) {
+ str += ' ';
+ appendRelativeCoord(v, r);
+}
+
+void Inkscape::SVG::PathString::State::appendNumber(double v, int precision, int minexp) {
+ size_t const reserve = precision+1+1+1+1+3; // Just large enough to hold the maximum number of digits plus a sign, a period, the letter 'e', another sign and three digits for the exponent
+ size_t const oldsize = str.size();
+ str.append(reserve, (char)0);
+ char* begin_of_num = const_cast<char*>(str.data()+oldsize); // Slightly evil, I know (but std::string should be storing its data in one big block of memory, so...)
+ size_t added = sp_svg_number_write_de(begin_of_num, reserve, v, precision, minexp);
+ str.resize(oldsize+added); // remove any trailing characters
+}
+
+void Inkscape::SVG::PathString::State::appendNumber(double v, double &rv, int precision, int minexp) {
+ size_t const oldsize = str.size();
+ appendNumber(v, precision, minexp);
+ char* begin_of_num = const_cast<char*>(str.data()+oldsize); // Slightly evil, I know (but std::string should be storing its data in one big block of memory, so...)
+ sp_svg_number_read_d(begin_of_num, &rv);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/path-string.h b/src/svg/path-string.h
new file mode 100644
index 0000000..53080ba
--- /dev/null
+++ b/src/svg/path-string.h
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::SVG::PathString - builder for SVG path strings
+ *//*
+ * Authors: see git history
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2008 Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
+ * Copyright 2013 Tavmjong Bah <tavmjong@free.fr>
+ * Copyright (C) 2014 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_SVG_PATH_STRING_H
+#define SEEN_INKSCAPE_SVG_PATH_STRING_H
+
+#include <2geom/point.h>
+#include <cstdio>
+#include <glibmm/ustring.h>
+#include <string>
+
+namespace Inkscape {
+
+namespace SVG {
+
+// Relative vs. absolute coordinates
+enum PATHSTRING_FORMAT {
+ PATHSTRING_ABSOLUTE, // Use only absolute coordinates
+ PATHSTRING_RELATIVE, // Use only relative coordinates
+ PATHSTRING_OPTIMIZE, // Optimize for path string length
+ PATHSTRING_FORMAT_SIZE
+};
+
+/**
+ * Builder for SVG path strings.
+ */
+class PathString {
+public:
+ PathString();
+
+ // default copy
+ // default assign
+
+ std::string const &string() {
+ std::string const &t = tail();
+ final.reserve(commonbase.size()+t.size());
+ final = commonbase;
+ final += tail();
+ // std::cout << " final: " << final << std::endl;
+ return final;
+ }
+
+ operator std::string const &() {
+ return string();
+ }
+
+ operator Glib::ustring const () const {
+ return commonbase + tail();
+ }
+
+ char const *c_str() {
+ return string().c_str();
+ }
+
+ PathString &moveTo(Geom::Coord x, Geom::Coord y) {
+ return moveTo(Geom::Point(x, y));
+ }
+
+ PathString &moveTo(Geom::Point p) {
+ _appendOp('M','m');
+ _appendPoint(p, true);
+
+ _initial_point = _current_point;
+ return *this;
+ }
+
+ PathString &lineTo(Geom::Coord x, Geom::Coord y) {
+ return lineTo(Geom::Point(x, y));
+ }
+
+ PathString &lineTo(Geom::Point p) {
+ _appendOp('L','l');
+ _appendPoint(p, true);
+ return *this;
+ }
+
+ PathString &horizontalLineTo(Geom::Coord x) {
+ _appendOp('H','h');
+ _appendX(x, true);
+ return *this;
+ }
+
+ PathString &verticalLineTo(Geom::Coord y) {
+ _appendOp('V','v');
+ _appendY(y, true);
+ return *this;
+ }
+
+ PathString &quadTo(Geom::Coord cx, Geom::Coord cy, Geom::Coord x, Geom::Coord y) {
+ return quadTo(Geom::Point(cx, cy), Geom::Point(x, y));
+ }
+
+ PathString &quadTo(Geom::Point c, Geom::Point p) {
+ _appendOp('Q','q');
+ _appendPoint(c, false);
+ _appendPoint(p, true);
+ return *this;
+ }
+
+ PathString &curveTo(Geom::Coord x0, Geom::Coord y0,
+ Geom::Coord x1, Geom::Coord y1,
+ Geom::Coord x, Geom::Coord y)
+ {
+ return curveTo(Geom::Point(x0, y0), Geom::Point(x1, y1), Geom::Point(x, y));
+ }
+
+ PathString &curveTo(Geom::Point c0, Geom::Point c1, Geom::Point p) {
+ _appendOp('C','c');
+ _appendPoint(c0, false);
+ _appendPoint(c1, false);
+ _appendPoint(p, true);
+ return *this;
+ }
+
+ /**
+ * \param rot the angle in degrees
+ */
+ PathString &arcTo(Geom::Coord rx, Geom::Coord ry, Geom::Coord rot,
+ bool large_arc, bool sweep,
+ Geom::Point p)
+ {
+ _appendOp('A','a');
+ _appendValue(Geom::Point(rx,ry));
+ _appendValue(rot);
+ _appendFlag(large_arc);
+ _appendFlag(sweep);
+ _appendPoint(p, true);
+ return *this;
+ }
+
+ PathString &closePath() {
+
+ _abs_state.appendOp('Z');
+ _rel_state.appendOp('z');
+
+ _current_point = _initial_point;
+ return *this;
+ }
+
+private:
+
+ void _appendOp(char abs_op, char rel_op);
+
+ void _appendFlag(bool flag) {
+ _abs_state.append(flag);
+ _rel_state.append(flag);
+ }
+
+ void _appendValue(Geom::Coord v) {
+ _abs_state.append(v);
+ _rel_state.append(v);
+ }
+
+ void _appendValue(Geom::Point p) {
+ _abs_state.append(p);
+ _rel_state.append(p);
+ }
+
+ void _appendX(Geom::Coord x, bool sc) {
+ double rx;
+ _abs_state.append(x, rx);
+ _rel_state.appendRelative(rx, _current_point[Geom::X]);
+ if (sc) _current_point[Geom::X] = rx;
+ }
+
+ void _appendY(Geom::Coord y, bool sc) {
+ double ry;
+ _abs_state.append(y, ry);
+ _rel_state.appendRelative(ry, _current_point[Geom::Y]);
+ if (sc) _current_point[Geom::Y] = ry;
+ }
+
+ void _appendPoint(Geom::Point p, bool sc) {
+ Geom::Point rp;
+ _abs_state.append(p, rp);
+ _rel_state.appendRelative(rp, _current_point);
+ if (sc) _current_point = rp;
+ }
+
+ struct State {
+ State() { prevop = 0; switches = 0; }
+
+ void appendOp(char op) {
+ if (prevop != 0) str += ' ';
+ str += op;
+ prevop = ( op == 'M' ? 'L' : op == 'm' ? 'l' : op );
+ }
+
+ void append(bool flag) {
+ str += ' ';
+ str += ( flag ? '1' : '0' );
+ }
+
+ void append(Geom::Coord v);
+ void append(Geom::Point v);
+ void append(Geom::Coord v, Geom::Coord& rv);
+ void append(Geom::Point p, Geom::Point& rp);
+ void appendRelative(Geom::Coord v, Geom::Coord r);
+ void appendRelative(Geom::Point p, Geom::Point r);
+
+ bool operator<=(const State& s) const {
+ if ( str.size() < s.str.size() ) return true;
+ if ( str.size() > s.str.size() ) return false;
+ if ( switches < s.switches ) return true;
+ if ( switches > s.switches ) return false;
+ return true;
+ }
+
+ // Note: changing this to Glib::ustring might cause problems in path-string.cpp because it assumes that
+ // size() returns the size of the string in BYTES (and Glib::ustring::resize is terribly slow)
+ std::string str;
+ unsigned int switches;
+ char prevop;
+
+ private:
+ void appendNumber(double v, int precision=numericprecision, int minexp=minimumexponent);
+ void appendNumber(double v, double &rv, int precision=numericprecision, int minexp=minimumexponent);
+ void appendRelativeCoord(Geom::Coord v, Geom::Coord r);
+ } _abs_state, _rel_state; // State with the last operator being an absolute/relative operator
+
+ Geom::Point _initial_point;
+ Geom::Point _current_point;
+
+ // If both states have a common prefix it is stored here.
+ // Separating out the common prefix prevents repeated copying between the states
+ // to cause a quadratic time complexity (in the number of characters/operators)
+ std::string commonbase;
+ std::string final;
+ std::string const &tail() const {
+ return ( (format == PATHSTRING_ABSOLUTE) ||
+ (format == PATHSTRING_OPTIMIZE && _abs_state <= _rel_state ) ?
+ _abs_state.str : _rel_state.str );
+ }
+
+ static PATHSTRING_FORMAT format;
+ bool const force_repeat_commands;
+ static int numericprecision;
+ static int minimumexponent;
+};
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/sp-svg.def b/src/svg/sp-svg.def
new file mode 100644
index 0000000..6336f9b
--- /dev/null
+++ b/src/svg/sp-svg.def
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+EXPORTS
+ gnome_canvas_bpath_def_art_finish
+ gnome_canvas_bpath_def_closepath
+ gnome_canvas_bpath_def_curveto
+ gnome_canvas_bpath_def_free
+ gnome_canvas_bpath_def_lineto
+ gnome_canvas_bpath_def_moveto
+ gnome_canvas_bpath_def_new
+ gnome_canvas_bpath_def_new_from
+ gnome_canvas_bpath_def_ref
+ sp_svg_length_read
+ sp_svg_length_read_ldd
+ sp_svg_length_unset
+ sp_svg_length_update
+ sp_svg_number_read_d
+ sp_svg_number_read_f
+ sp_svg_number_write_de
+ sp_svg_number_write_f
+ sp_svg_number_write_fe
+ sp_svg_read_color
+ sp_svg_read_path
+ sp_svg_read_percentage
+ sp_svg_transform_read
+ sp_svg_transform_write
+ sp_svg_write_color
+ sp_svg_write_path
diff --git a/src/svg/stringstream.cpp b/src/svg/stringstream.cpp
new file mode 100644
index 0000000..3a56e9f
--- /dev/null
+++ b/src/svg/stringstream.cpp
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "svg/stringstream.h"
+#include "svg/strip-trailing-zeros.h"
+#include "preferences.h"
+#include <2geom/point.h>
+
+Inkscape::SVGOStringStream::SVGOStringStream()
+{
+ /* These two are probably unnecessary now that we provide our own operator<< for float and
+ * double. */
+ ostr.imbue(std::locale::classic());
+ ostr.setf(std::ios::showpoint);
+
+ /* This one is (currently) needed though, as we currently use ostr.precision as a sort of
+ variable for storing the desired precision: see our two precision methods and our operator<<
+ methods for float and double. */
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ ostr.precision(prefs->getInt("/options/svgoutput/numericprecision", 8));
+}
+
+Inkscape::SVGOStringStream &
+operator<<(Inkscape::SVGOStringStream &os, float d)
+{
+ /* Try as integer first. */
+ {
+ int const n = int(d);
+ if (d == n) {
+ os << n;
+ return os;
+ }
+ }
+
+ std::ostringstream s;
+ s.imbue(std::locale::classic());
+ s.flags(os.setf(std::ios::showpoint));
+ s.precision(os.precision());
+ s << d;
+ os << strip_trailing_zeros(s.str());
+ return os;
+}
+
+Inkscape::SVGOStringStream &
+operator<<(Inkscape::SVGOStringStream &os, double d)
+{
+ /* Try as integer first. */
+ {
+ int const n = int(d);
+ if (d == n) {
+ os << n;
+ return os;
+ }
+ }
+
+ std::ostringstream s;
+ s.imbue(std::locale::classic());
+ s.flags(os.setf(std::ios::showpoint));
+ s.precision(os.precision());
+ s << d;
+ os << strip_trailing_zeros(s.str());
+ return os;
+}
+
+Inkscape::SVGOStringStream &
+operator<<(Inkscape::SVGOStringStream &os, Geom::Point const & p)
+{
+ os << p[0] << ',' << p[1];
+ return os;
+}
+
+Inkscape::SVGIStringStream::SVGIStringStream():std::istringstream()
+{
+ this->imbue(std::locale::classic());
+ this->setf(std::ios::showpoint);
+
+ /* This one is (currently) needed though, as we currently use ostr.precision as a sort of
+ variable for storing the desired precision: see our two precision methods and our operator<<
+ methods for float and double. */
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->precision(prefs->getInt("/options/svgoutput/numericprecision", 8));
+}
+
+Inkscape::SVGIStringStream::SVGIStringStream(const std::string& str):std::istringstream(str)
+{
+ this->imbue(std::locale::classic());
+ this->setf(std::ios::showpoint);
+
+ /* This one is (currently) needed though, as we currently use ostr.precision as a sort of
+ variable for storing the desired precision: see our two precision methods and our operator<<
+ methods for float and double. */
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->precision(prefs->getInt("/options/svgoutput/numericprecision", 8));
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/stringstream.h b/src/svg/stringstream.h
new file mode 100644
index 0000000..f5d9073
--- /dev/null
+++ b/src/svg/stringstream.h
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_STRINGSTREAM_H
+#define INKSCAPE_STRINGSTREAM_H
+
+#include <sstream>
+#include <string>
+
+#include <2geom/forward.h>
+
+namespace Inkscape {
+
+typedef std::ios_base &(*std_oct_type)(std::ios_base &);
+
+class SVGOStringStream {
+private:
+ std::ostringstream ostr;
+
+public:
+ SVGOStringStream();
+
+#define INK_SVG_STR_OP(_t) \
+ SVGOStringStream &operator<<(_t arg) { \
+ ostr << arg; \
+ return *this; \
+ }
+
+ INK_SVG_STR_OP(char)
+ INK_SVG_STR_OP(signed char)
+ INK_SVG_STR_OP(unsigned char)
+ INK_SVG_STR_OP(short)
+ INK_SVG_STR_OP(unsigned short)
+ INK_SVG_STR_OP(int)
+ INK_SVG_STR_OP(unsigned int)
+ INK_SVG_STR_OP(long)
+ INK_SVG_STR_OP(unsigned long)
+ INK_SVG_STR_OP(char const *)
+ INK_SVG_STR_OP(signed char const *)
+ INK_SVG_STR_OP(unsigned char const *)
+ INK_SVG_STR_OP(std::string const &)
+ INK_SVG_STR_OP(std_oct_type)
+
+#undef INK_SVG_STR_OP
+
+ std::string str() const {
+ return ostr.str();
+ }
+
+ void str (std::string &s) {
+ ostr.str(s);
+ }
+
+ std::streamsize precision() const {
+ return ostr.precision();
+ }
+
+ std::streamsize precision(std::streamsize p) {
+ return ostr.precision(p);
+ }
+
+ std::ios::fmtflags setf(std::ios::fmtflags fmtfl) {
+ return ostr.setf(fmtfl);
+ }
+
+ std::ios::fmtflags setf(std::ios::fmtflags fmtfl, std::ios::fmtflags mask) {
+ return ostr.setf(fmtfl, mask);
+ }
+
+ void unsetf(std::ios::fmtflags mask) {
+ ostr.unsetf(mask);
+ }
+};
+
+class SVGIStringStream:public std::istringstream {
+
+public:
+ SVGIStringStream();
+ SVGIStringStream(const std::string &str);
+};
+
+}
+
+Inkscape::SVGOStringStream &operator<<(Inkscape::SVGOStringStream &os, float d);
+
+Inkscape::SVGOStringStream &operator<<(Inkscape::SVGOStringStream &os, double d);
+
+Inkscape::SVGOStringStream &operator<<(Inkscape::SVGOStringStream &os, Geom::Point const & p);
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/strip-trailing-zeros.cpp b/src/svg/strip-trailing-zeros.cpp
new file mode 100644
index 0000000..8abe4fa
--- /dev/null
+++ b/src/svg/strip-trailing-zeros.cpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <string>
+#include <glib.h>
+
+#include "svg/strip-trailing-zeros.h"
+
+std::string
+strip_trailing_zeros(std::string str)
+{
+ std::string::size_type p_ix = str.find('.');
+ if (p_ix != std::string::npos) {
+ std::string::size_type e_ix = str.find('e', p_ix);
+ /* N.B. In some contexts (e.g. CSS) it is an error for a number to contain `e'. fixme:
+ * Default to avoiding `e', e.g. using sprintf(str, "%17f", d). Add a new function that
+ * allows use of `e' and use that function only where the spec allows it.
+ */
+ std::string::size_type nz_ix = str.find_last_not_of('0', (e_ix == std::string::npos
+ ? e_ix
+ : e_ix - 1));
+ if (nz_ix == std::string::npos || nz_ix < p_ix || nz_ix >= e_ix) {
+ g_error("have `.' but couldn't find non-0");
+ } else {
+ str.erase(str.begin() + (nz_ix == p_ix
+ ? p_ix
+ : nz_ix + 1),
+ (e_ix == std::string::npos
+ ? str.end()
+ : str.begin() + e_ix));
+ }
+ }
+ return str;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/strip-trailing-zeros.h b/src/svg/strip-trailing-zeros.h
new file mode 100644
index 0000000..1c5f537
--- /dev/null
+++ b/src/svg/strip-trailing-zeros.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SVG_STRIP_TRAILING_ZEROS_H_SEEN
+#define SVG_STRIP_TRAILING_ZEROS_H_SEEN
+
+#include <string>
+
+std::string strip_trailing_zeros(std::string str);
+
+
+#endif /* !SVG_STRIP_TRAILING_ZEROS_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-affine-test.h b/src/svg/svg-affine-test.h
new file mode 100644
index 0000000..fcb9db0
--- /dev/null
+++ b/src/svg/svg-affine-test.h
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2017 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <cxxtest/TestSuite.h>
+
+#include "svg/svg.h"
+#include "streq.h"
+#include <2geom/affine.h>
+#include <algorithm>
+#include <glib.h>
+#include <iostream>
+#include <math.h>
+#include <utility>
+
+class SvgAffineTest : public CxxTest::TestSuite
+{
+private:
+ struct test_t {
+ char const * str;
+ Geom::Affine matrix;
+ };
+ struct approx_equal_pred {
+ bool operator()(Geom::Affine const &ref, Geom::Affine const &cm) const
+ {
+ double maxabsdiff = 0;
+ for(size_t i=0; i<6; i++) {
+ maxabsdiff = std::max(std::abs(ref[i]-cm[i]), maxabsdiff);
+ }
+ return maxabsdiff < 1e-14;
+ }
+ };
+ static test_t const read_matrix_tests[3];
+ static test_t const read_translate_tests[3];
+ static test_t const read_scale_tests[3];
+ static test_t const read_rotate_tests[4];
+ static test_t const read_skew_tests[3];
+ static char const * const read_fail_tests[25];
+ static test_t const write_matrix_tests[2];
+ static test_t const write_translate_tests[3];
+ static test_t const write_scale_tests[3];
+ static test_t const write_rotate_tests[3];
+ static test_t const write_skew_tests[3];
+public:
+ SvgAffineTest() {
+ }
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static SvgAffineTest *createSuite() { return new SvgAffineTest(); }
+ static void destroySuite( SvgAffineTest *suite ) { delete suite; }
+
+ void testReadIdentity()
+ {
+ char const* strs[] = {
+ //0,
+ "",
+ "matrix(1,0,0,1,0,0)",
+ "translate(0,0)",
+ "scale(1,1)",
+ "rotate(0,0,0)",
+ "skewX(0)",
+ "skewY(0)"};
+ size_t n = G_N_ELEMENTS(strs);
+ for(size_t i=0; i<n; i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(strs[i] , sp_svg_transform_read(strs[i], &cm));
+ TSM_ASSERT_EQUALS(strs[i] , Geom::identity() , cm);
+ }
+ }
+
+ void testWriteIdentity()
+ {
+ gchar str = sp_svg_transform_write(Geom::identity());
+ TS_ASSERT_EQUALS(str, NULL);
+ g_free(str);
+ }
+
+ void testReadMatrix()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(read_matrix_tests); i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(read_matrix_tests[i].str , sp_svg_transform_read(read_matrix_tests[i].str, &cm));
+ TSM_ASSERT_RELATION(read_matrix_tests[i].str , approx_equal_pred , read_matrix_tests[i].matrix , cm);
+ }
+ }
+
+ void testReadTranslate()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(read_translate_tests); i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(read_translate_tests[i].str , sp_svg_transform_read(read_translate_tests[i].str, &cm));
+ TSM_ASSERT_RELATION(read_translate_tests[i].str , approx_equal_pred , read_translate_tests[i].matrix , cm);
+ }
+ }
+
+ void testReadScale()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(read_scale_tests); i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(read_scale_tests[i].str , sp_svg_transform_read(read_scale_tests[i].str, &cm));
+ TSM_ASSERT_RELATION(read_scale_tests[i].str , approx_equal_pred , read_scale_tests[i].matrix , cm);
+ }
+ }
+
+ void testReadRotate()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(read_rotate_tests); i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(read_rotate_tests[i].str , sp_svg_transform_read(read_rotate_tests[i].str, &cm));
+ TSM_ASSERT_RELATION(read_rotate_tests[i].str , approx_equal_pred , read_rotate_tests[i].matrix , cm);
+ }
+ }
+
+ void testReadSkew()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(read_skew_tests); i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(read_skew_tests[i].str , sp_svg_transform_read(read_skew_tests[i].str, &cm));
+ TSM_ASSERT_RELATION(read_skew_tests[i].str , approx_equal_pred , read_skew_tests[i].matrix , cm);
+ }
+ }
+
+ void testWriteMatrix()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(write_matrix_tests); i++) {
+ char * str = sp_svg_transform_write(write_matrix_tests[i].matrix);
+ TS_ASSERT_RELATION(streq_rel , str , write_matrix_tests[i].str);
+ g_free(str);
+ }
+ }
+
+ void testWriteTranslate()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(write_translate_tests); i++) {
+ char * str = sp_svg_transform_write(write_translate_tests[i].matrix);
+ TS_ASSERT_RELATION(streq_rel , str , write_translate_tests[i].str);
+ g_free(str);
+ }
+ }
+
+ void testWriteScale()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(write_scale_tests); i++) {
+ char * str = sp_svg_transform_write(write_scale_tests[i].matrix);
+ TS_ASSERT_RELATION(streq_rel , str , write_scale_tests[i].str);
+ g_free(str);
+ }
+ }
+
+ void testWriteRotate()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(write_rotate_tests); i++) {
+ char * str = sp_svg_transform_write(write_rotate_tests[i].matrix);
+ TS_ASSERT_RELATION(streq_rel , str , write_rotate_tests[i].str);
+ g_free(str);
+ }
+ }
+
+ void testWriteSkew()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(write_skew_tests); i++) {
+ char * str = sp_svg_transform_write(write_skew_tests[i].matrix);
+ TS_ASSERT_RELATION(streq_rel , str , write_skew_tests[i].str);
+ g_free(str);
+ }
+ }
+
+ void testReadConcatenation()
+ {
+ // NOTE: According to the SVG specification (see the syntax at http://www.w3.org/TR/SVG/coords.html#TransformAttribute
+ // there should be 1 or more comma-wsp sequences between transforms... This doesn't make sense and it seems
+ // likely that instead of a + they meant a ? (zero or one comma-wsp sequences).
+ char const * str = "skewY(17)skewX(9)translate(7,13)scale(2)rotate(13)translate(3,5)";
+ Geom::Affine ref(2.0199976232558053, 1.0674773585906016, -0.14125199392774669, 1.9055550612095459, 14.412730624347654, 28.499820929377454); // Precomputed using Mathematica
+ Geom::Affine cm;
+ TS_ASSERT(sp_svg_transform_read(str, &cm));
+ TS_ASSERT_RELATION(approx_equal_pred , ref , cm);
+ }
+
+ void testReadFailures()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(read_fail_tests); i++) {
+ Geom::Affine cm;
+ TSM_ASSERT(read_fail_tests[i] , !sp_svg_transform_read(read_fail_tests[i], &cm));
+ }
+ }
+};
+
+static double const DEGREE = M_PI/180.;
+
+SvgAffineTest::test_t const SvgAffineTest::read_matrix_tests[3] = {
+ {"matrix(0,0,0,0,0,0)",Geom::Affine(0,0,0,0,0,0)},
+ {" matrix(1,2,3,4,5,6)",Geom::Affine(1,2,3,4,5,6)},
+ {"matrix (1 2 -3,-4,5e6,-6e-7)",Geom::Affine(1,2,-3,-4,5e6,-6e-7)}};
+SvgAffineTest::test_t const SvgAffineTest::read_translate_tests[3] = {
+ {"translate(1)",Geom::Affine(1,0,0,1,1,0)},
+ {"translate(1,1)",Geom::Affine(1,0,0,1,1,1)},
+ {"translate(-1e3 .123e2)",Geom::Affine(1,0,0,1,-1e3,.123e2)}};
+SvgAffineTest::test_t const SvgAffineTest::read_scale_tests[3] = {
+ {"scale(2)",Geom::Affine(2,0,0,2,0,0)},
+ {"scale(2,3)",Geom::Affine(2,0,0,3,0,0)},
+ {"scale(0.1e-2 -.475e0)",Geom::Affine(0.1e-2,0,0,-.475e0,0,0)}};
+SvgAffineTest::test_t const SvgAffineTest::read_rotate_tests[4] = {
+ {"rotate(13 )",Geom::Affine(cos(13.*DEGREE),sin(13.*DEGREE),-sin(13.*DEGREE),cos(13.*DEGREE),0,0)},
+ {"rotate(-13)",Geom::Affine(cos(-13.*DEGREE),sin(-13.*DEGREE),-sin(-13.*DEGREE),cos(-13.*DEGREE),0,0)},
+ {"rotate(373)",Geom::Affine(cos(13.*DEGREE),sin(13.*DEGREE),-sin(13.*DEGREE),cos(13.*DEGREE),0,0)},
+ {"rotate(13,7,11)",Geom::Affine(cos(13.*DEGREE),sin(13.*DEGREE),-sin(13.*DEGREE),cos(13.*DEGREE),(1-cos(13.*DEGREE))*7+sin(13.*DEGREE)*11,(1-cos(13.*DEGREE))*11-sin(13.*DEGREE)*7)}};
+SvgAffineTest::test_t const SvgAffineTest::read_skew_tests[3] = {
+ {"skewX( 30)",Geom::Affine(1,0,tan(30.*DEGREE),1,0,0)},
+ {"skewX(-30)",Geom::Affine(1,0,tan(-30.*DEGREE),1,0,0)},
+ {"skewY(390)",Geom::Affine(1,tan(30.*DEGREE),0,1,0,0)}};
+char const * const SvgAffineTest::read_fail_tests[25] = {
+ "matrix((1,2,3,4,5,6)",
+ "matrix((1,2,3,4,5,6))",
+ "matrix(1,2,3,4,5,6))",
+ "matrix(,1,2,3,4,5,6)",
+ "matrix(1,2,3,4,5,6,)",
+ "matrix(1,2,3,4,5,)",
+ "matrix(1,2,3,4,5)",
+ "matrix(1,2,3,4,5e6-3)", // Here numbers HAVE to be separated by a comma-wsp sequence
+ "matrix(1,2,3,4,5e6.3)", // Here numbers HAVE to be separated by a comma-wsp sequence
+ "translate()",
+ "translate(,)",
+ "translate(1,)",
+ "translate(1,6,)",
+ "translate(1,6,0)",
+ "scale()",
+ "scale(1,6,2)",
+ "rotate()",
+ "rotate(1,6)",
+ "rotate(1,6,)",
+ "rotate(1,6,3,4)",
+ "skewX()",
+ "skewX(-)",
+ "skewX(.)",
+ "skewY(,)",
+ "skewY(1,2)"};
+
+SvgAffineTest::test_t const SvgAffineTest::write_matrix_tests[2] = {
+ {"matrix(1,2,3,4,5,6)",Geom::Affine(1,2,3,4,5,6)},
+ {"matrix(-1,2123,3,0.4,1e-8,1e20)",Geom::Affine(-1,2.123e3,3+1e-14,0.4,1e-8,1e20)}};
+SvgAffineTest::test_t const SvgAffineTest::write_translate_tests[3] = {
+ {"translate(1,1)",Geom::Affine(1,0,0,1,1,1)},
+ {"translate(1)",Geom::Affine(1,0,0,1,1,0)},
+ {"translate(-1345,0.123)",Geom::Affine(1,0,0,1,-1.345e3,.123)}};
+SvgAffineTest::test_t const SvgAffineTest::write_scale_tests[3] = {
+ {"scale(0)",Geom::Affine(0,0,0,0,0,0)},
+ {"scale(7)",Geom::Affine(7,0,0,7,0,0)},
+ {"scale(2,3)",Geom::Affine(2,0,0,3,0,0)}};
+SvgAffineTest::test_t const SvgAffineTest::write_rotate_tests[3] = {
+ {"rotate(13)",Geom::Affine(cos(13.*DEGREE),sin(13.*DEGREE),-sin(13.*DEGREE),cos(13.*DEGREE),0,0)},
+ {"rotate(-13,7,11)",Geom::Affine(cos(-13.*DEGREE),sin(-13.*DEGREE),-sin(-13.*DEGREE),cos(-13.*DEGREE),(1-cos(-13.*DEGREE))*7+sin(-13.*DEGREE)*11,(1-cos(-13.*DEGREE))*11-sin(-13.*DEGREE)*7)},
+ {"rotate(-34.5,6.7,89)",Geom::Affine(cos(-34.5*DEGREE),sin(-34.5*DEGREE),-sin(-34.5*DEGREE),cos(-34.5*DEGREE),(1-cos(-34.5*DEGREE))*6.7+sin(-34.5*DEGREE)*89,(1-cos(-34.5*DEGREE))*89-sin(-34.5*DEGREE)*6.7)}};
+SvgAffineTest::test_t const SvgAffineTest::write_skew_tests[3] = {
+ {"skewX(30)",Geom::Affine(1,0,tan(30.*DEGREE),1,0,0)},
+ {"skewX(-30)",Geom::Affine(1,0,tan(-30.*DEGREE),1,0,0)},
+ {"skewY(30)",Geom::Affine(1,tan(30.*DEGREE),0,1,0,0)}};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-affine.cpp b/src/svg/svg-affine.cpp
new file mode 100644
index 0000000..e5fcce6
--- /dev/null
+++ b/src/svg/svg-affine.cpp
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVG data parser
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Raph Levien <raph@acm.org>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 1999 Raph Levien
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <glib.h>
+#include <2geom/transforms.h>
+#include "svg.h"
+#include "preferences.h"
+
+bool
+sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
+{
+ int idx;
+ char keyword[32];
+ double args[6];
+ int n_args;
+ size_t key_len;
+
+ if (str == nullptr) return false;
+
+ Geom::Affine a(Geom::identity());
+
+ idx = 0;
+ while (str[idx]) {
+ /* skip initial whitespace */
+ while (g_ascii_isspace (str[idx])) idx++;
+
+ // SVG2: allow commas in separation of transforms
+ if (str[idx] == ',') {
+ ++idx;
+ while (g_ascii_isspace(str[idx]))
+ ++idx;
+ }
+
+ /* parse keyword */
+ for (key_len = 0; key_len < sizeof (keyword); key_len++) {
+ char c;
+
+ c = str[idx];
+ if (g_ascii_isalpha (c) || c == '-') {
+ keyword[key_len] = str[idx++];
+ } else {
+ break;
+ }
+ }
+ if (key_len >= sizeof (keyword)) return false;
+ keyword[key_len] = '\0';
+
+ /* skip whitespace */
+ while (g_ascii_isspace (str[idx])) idx++;
+
+ if (str[idx] != '(') return false;
+ idx++;
+
+ for (n_args = 0; n_args < 6; n_args++) {
+ char c;
+ char *end_ptr;
+
+ /* skip whitespace */
+ while (g_ascii_isspace (str[idx])) idx++;
+ c = str[idx];
+ if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.') {
+ if (n_args == sizeof (args) / sizeof (args[0])) return false; /* Too many args */
+ args[n_args] = g_ascii_strtod (str + idx, &end_ptr);
+
+ //printf("took %d chars from '%s' to make %f\n",
+ // end_ptr-(str+idx),
+ // str+idx,
+ // args[n_args]);
+
+ idx = end_ptr - (char *) str;
+
+ while (g_ascii_isspace (str[idx])) idx++;
+
+ /* skip optional comma */
+ if (str[idx] == ',') idx++;
+ } else if (c == ')') {
+ break;
+ } else {
+ return false;
+ }
+ }
+ idx++;
+
+ /* ok, have parsed keyword and args, now modify the transform */
+ if (!strcmp (keyword, "matrix")) {
+ if (n_args != 6) return false;
+ a = (*((Geom::Affine *) &(args)[0])) * a;
+ } else if (!strcmp (keyword, "translate")) {
+ if (n_args == 1) {
+ args[1] = 0;
+ } else if (n_args != 2) {
+ return false;
+ }
+ a = Geom::Translate(args[0], args[1]) * a;
+ } else if (!strcmp (keyword, "scale")) {
+ if (n_args == 1) {
+ args[1] = args[0];
+ } else if (n_args != 2) {
+ return false;
+ }
+ a = Geom::Scale(args[0], args[1]) * a;
+ } else if (!strcmp (keyword, "rotate")) {
+ if (n_args != 1 && n_args != 3) {
+ return false;
+ }
+ Geom::Rotate const rot(Geom::rad_from_deg(args[0]));
+ if (n_args == 3) {
+ a = ( Geom::Translate(-args[1], -args[2])
+ * rot
+ * Geom::Translate(args[1], args[2])
+ * Geom::Affine(a) );
+ } else {
+ a = rot * a;
+ }
+ } else if (!strcmp (keyword, "skewX")) {
+ if (n_args != 1) return false;
+ a = ( Geom::Affine(1, 0,
+ tan(args[0] * M_PI / 180.0), 1,
+ 0, 0)
+ * a );
+ } else if (!strcmp (keyword, "skewY")) {
+ if (n_args != 1) return false;
+ a = ( Geom::Affine(1, tan(args[0] * M_PI / 180.0),
+ 0, 1,
+ 0, 0)
+ * a );
+ } else {
+ return false; /* unknown keyword */
+ }
+ /* Skip trailing whitespace */
+ while (g_ascii_isspace (str[idx])) idx++;
+ }
+
+ *transform = a;
+ return true;
+}
+
+#define EQ(a,b) (fabs ((a) - (b)) < 1e-9)
+
+gchar *
+sp_svg_transform_write(Geom::Affine const &transform)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // this must be a bit grater than EPSILON
+ double e = 1e-5 * transform.descrim();
+ int prec = prefs->getInt("/options/svgoutput/numericprecision", 8);
+ int min_exp = prefs->getInt("/options/svgoutput/minimumexponent", -8);
+
+ // Special case: when all fields of the affine are zero,
+ // the optimized transformation is scale(0)
+ if (transform[0] == 0 && transform[1] == 0 && transform[2] == 0 &&
+ transform[3] == 0 && transform[4] == 0 && transform[5] == 0)
+ {
+ return g_strdup("scale(0)");
+ }
+
+ // FIXME legacy C code!
+ // the function sp_svg_number_write_de is stopping me from using a proper C++ string
+
+ gchar c[256]; // string buffer
+ unsigned p = 0; // position in the buffer
+
+ if (transform.isIdentity()) {
+ // We are more or less identity, so no transform attribute needed:
+ return nullptr;
+ } else if (transform.isScale()) {
+ // We are more or less a uniform scale
+ strcpy (c + p, "scale(");
+ p += 6;
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp );
+ if (Geom::are_near(transform[0], transform[3], e)) {
+ c[p++] = ')';
+ c[p] = '\000';
+ } else {
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp );
+ c[p++] = ')';
+ c[p] = '\000';
+ }
+ } else if (transform.isTranslation()) {
+ // We are more or less a pure translation
+ strcpy (c + p, "translate(");
+ p += 10;
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp );
+ if (Geom::are_near(transform[5], 0.0, e)) {
+ c[p++] = ')';
+ c[p] = '\000';
+ } else {
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp );
+ c[p++] = ')';
+ c[p] = '\000';
+ }
+ } else if (transform.isRotation()) {
+ // We are more or less a pure rotation
+ strcpy(c + p, "rotate(");
+ p += 7;
+
+ double angle = std::atan2(transform[1], transform[0]) * (180 / M_PI);
+ p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);
+
+ c[p++] = ')';
+ c[p] = '\000';
+ } else if (transform.withoutTranslation().isRotation()) {
+ // Solution found by Johan Engelen
+ // Refer to the matrix in svg-affine-test.h
+
+ // We are a rotation about a special axis
+ strcpy(c + p, "rotate(");
+ p += 7;
+
+ double angle = std::atan2(transform[1], transform[0]) * (180 / M_PI);
+ p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);
+ c[p++] = ',';
+
+ Geom::Affine const& m = transform;
+ double tx = (m[2]*m[5]+m[4]-m[4]*m[3]) / (1-m[3]-m[0]+m[0]*m[3]-m[2]*m[1]);
+ p += sp_svg_number_write_de(c + p, sizeof(c) - p, tx, prec, min_exp);
+
+ c[p++] = ',';
+
+ double ty = (m[1]*tx + m[5]) / (1 - m[3]);
+ p += sp_svg_number_write_de(c + p, sizeof(c) - p, ty, prec, min_exp);
+
+ c[p++] = ')';
+ c[p] = '\000';
+ } else if (transform.isHShear()) {
+ // We are more or less a pure skewX
+ strcpy(c + p, "skewX(");
+ p += 6;
+
+ double angle = atan(transform[2]) * (180 / M_PI);
+ p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);
+
+ c[p++] = ')';
+ c[p] = '\000';
+ } else if (transform.isVShear()) {
+ // We are more or less a pure skewY
+ strcpy(c + p, "skewY(");
+ p += 6;
+
+ double angle = atan(transform[1]) * (180 / M_PI);
+ p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);
+
+ c[p++] = ')';
+ c[p] = '\000';
+ } else {
+ strcpy (c + p, "matrix(");
+ p += 7;
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp );
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[1], prec, min_exp );
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[2], prec, min_exp );
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp );
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp );
+ c[p++] = ',';
+ p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp );
+ c[p++] = ')';
+ c[p] = '\000';
+ }
+
+ assert(p <= sizeof(c));
+ return g_strdup(c);
+}
+
+
+gchar *
+sp_svg_transform_write(Geom::Affine const *transform)
+{
+ return sp_svg_transform_write(*transform);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-angle.cpp b/src/svg/svg-angle.cpp
new file mode 100644
index 0000000..e82d0de
--- /dev/null
+++ b/src/svg/svg-angle.cpp
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * \file src/svg/svg-angle.cpp
+ * \brief SVG angle type
+ */
+/*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <string>
+#include <glib.h>
+
+#include "svg/svg-angle.h"
+#include "util/units.h"
+
+
+static bool sp_svg_angle_read_lff(gchar const *str, SVGAngle::Unit &unit, float &val, float &computed);
+
+SVGAngle::SVGAngle()
+ : _set(false)
+ , unit(NONE)
+ , value(0)
+ , computed(0)
+{
+}
+
+/* Angle */
+
+bool SVGAngle::read(gchar const *str)
+{
+ if (!str) {
+ return false;
+ }
+
+ SVGAngle::Unit u;
+ float v;
+ float c;
+ if (!sp_svg_angle_read_lff(str, u, v, c)) {
+ return false;
+ }
+
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+
+ return true;
+}
+
+void SVGAngle::unset(SVGAngle::Unit u, float v, float c) {
+ _set = false;
+ unit = u;
+ value = v;
+ computed = c;
+}
+
+void SVGAngle::readOrUnset(gchar const *str, Unit u, float v, float c) {
+ if (!read(str)) {
+ unset(u, v, c);
+ }
+}
+
+static bool sp_svg_angle_read_lff(gchar const *str, SVGAngle::Unit &unit, float &val, float &computed)
+{
+ if (!str) {
+ return false;
+ }
+
+ gchar const *e;
+ float const v = g_ascii_strtod(str, (char **) &e);
+ if (e == str) {
+ return false;
+ }
+
+ if (!e[0]) {
+ /* Unitless (defaults to degrees)*/
+ unit = SVGAngle::NONE;
+ val = v;
+ computed = v;
+ return true;
+ } else if (!g_ascii_isalnum(e[0])) {
+ if (g_ascii_isspace(e[0]) && e[1] && g_ascii_isalpha(e[1])) {
+ return false; // spaces between value and unit are not allowed
+ } else {
+ /* Unitless (defaults to degrees)*/
+ unit = SVGAngle::NONE;
+ val = v;
+ computed = v;
+ return true;
+ }
+ } else {
+ if (strncmp(e, "deg", 3) == 0) {
+ unit = SVGAngle::DEG;
+ val = v;
+ computed = v;
+ } else if (strncmp(e, "grad", 4) == 0) {
+ unit = SVGAngle::GRAD;
+ val = v;
+ computed = Inkscape::Util::Quantity::convert(v, "grad", "°");
+ } else if (strncmp(e, "rad", 3) == 0) {
+ unit = SVGAngle::RAD;
+ val = v;
+ computed = Inkscape::Util::Quantity::convert(v, "rad", "°");
+ } else if (strncmp(e, "turn", 4) == 0) {
+ unit = SVGAngle::TURN;
+ val = v;
+ computed = Inkscape::Util::Quantity::convert(v, "turn", "°");
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /* Invalid */
+ return false;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-angle.h b/src/svg/svg-angle.h
new file mode 100644
index 0000000..6ed5915
--- /dev/null
+++ b/src/svg/svg-angle.h
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_SVG_ANGLE_H
+#define SEEN_SP_SVG_ANGLE_H
+
+/**
+ * \file src/svg/svg-angle.h
+ * \brief SVG angle type
+ */
+/*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+
+class SVGAngle
+{
+public:
+ SVGAngle();
+
+ enum Unit {
+ NONE,
+ DEG,
+ GRAD,
+ RAD,
+ TURN,
+ LAST_UNIT = TURN
+ };
+
+ // The object's value is valid / exists in SVG.
+ bool _set;
+
+ // The unit of value.
+ Unit unit;
+
+ // The value of this SVGAngle as found in the SVG.
+ float value;
+
+ // The value in degrees.
+ float computed;
+
+ float operator=(float v) {
+ _set = true;
+ unit = NONE;
+ value = computed = v;
+ return v;
+ }
+
+ bool read(gchar const *str);
+ void unset(Unit u = NONE, float v = 0, float c = 0);
+ void readOrUnset(gchar const *str, Unit u = NONE, float v = 0, float c = 0);
+};
+
+#endif // SEEN_SP_SVG_ANGLE_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-color-test.h b/src/svg/svg-color-test.h
new file mode 100644
index 0000000..b1723cb
--- /dev/null
+++ b/src/svg/svg-color-test.h
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <cxxtest/TestSuite.h>
+#include <cassert>
+#include <cstdlib>
+
+#include "preferences.h"
+#include "svg/svg-color.h"
+#include "svg/svg-icc-color.h"
+
+class SVGColorTest : public CxxTest::TestSuite
+{
+ struct simpleIccCase {
+ unsigned numEntries;
+ bool shouldPass;
+ char const* name;
+ char const* str;
+ };
+
+public:
+ void check_rgb24(unsigned const rgb24)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ char css[8];
+ prefs->setBool("/options/svgoutput/usenamedcolors", false);
+ sp_svg_write_color(css, sizeof(css), rgb24 << 8);
+ TS_ASSERT_EQUALS(sp_svg_read_color(css, 0xff),
+ rgb24 << 8);
+ prefs->setBool("/options/svgoutput/usenamedcolors", true);
+ sp_svg_write_color(css, sizeof(css), rgb24 << 8);
+ TS_ASSERT_EQUALS(sp_svg_read_color(css, 0xff),
+ rgb24 << 8);
+ }
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static SVGColorTest *createSuite() { return new SVGColorTest(); }
+ static void destroySuite( SVGColorTest *suite ) { delete suite; }
+
+ void testWrite()
+ {
+ unsigned const components[] = {0, 0x80, 0xff, 0xc0, 0x77};
+ unsigned const nc = G_N_ELEMENTS(components);
+ for (unsigned i = nc*nc*nc; i--;) {
+ unsigned tmp = i;
+ unsigned rgb24 = 0;
+ for (unsigned c = 0; c < 3; ++c) {
+ unsigned const component = components[tmp % nc];
+ rgb24 = (rgb24 << 8) | component;
+ tmp /= nc;
+ }
+ assert( tmp == 0 );
+ check_rgb24(rgb24);
+ }
+
+ /* And a few completely random ones. */
+ for (unsigned i = 500; i--;) { /* Arbitrary number of iterations. */
+ unsigned const rgb24 = (std::rand() >> 4) & 0xffffff;
+ check_rgb24(rgb24);
+ }
+ }
+
+ void testReadColor()
+ {
+ gchar const* val[] = {"#f0f", "#ff00ff", "rgb(255,0,255)", "fuchsia"};
+ size_t const n = sizeof(val)/sizeof(*val);
+ for(size_t i=0; i<n; i++) {
+ gchar const* end = 0;
+ guint32 result = sp_svg_read_color( val[i], &end, 0x3 );
+ TS_ASSERT_EQUALS( result, 0xff00ff00 );
+ TS_ASSERT_LESS_THAN( val[i], end );
+ }
+ }
+
+ void testIccColor()
+ {
+ simpleIccCase cases[] = {
+ {1, true, "named", "icc-color(named, 3)"},
+ {0, false, "", "foodle"},
+ {1, true, "a", "icc-color(a, 3)"},
+ {4, true, "named", "icc-color(named, 3, 0, 0.1, 2.5)"},
+ {0, false, "", "icc-color(named, 3"},
+ {0, false, "", "icc-color(space named, 3)"},
+ {0, false, "", "icc-color(tab\tnamed, 3)"},
+ {0, false, "", "icc-color(0name, 3)"},
+ {0, false, "", "icc-color(-name, 3)"},
+ {1, true, "positive", "icc-color(positive, +3)"},
+ {1, true, "negative", "icc-color(negative, -3)"},
+ {1, true, "positive", "icc-color(positive, +0.1)"},
+ {1, true, "negative", "icc-color(negative, -0.1)"},
+ {0, false, "", "icc-color(named, value)"},
+ {1, true, "hyphen-name", "icc-color(hyphen-name, 1)"},
+ {1, true, "under_name", "icc-color(under_name, 1)"},
+ };
+
+ for ( size_t i = 0; i < G_N_ELEMENTS(cases); i++ ) {
+ SVGICCColor tmp;
+ gchar const* str = cases[i].str;
+ gchar const* result = 0;
+
+ std::string testDescr( cases[i].str );
+
+ bool parseRet = sp_svg_read_icc_color( str, &result, &tmp );
+ TSM_ASSERT_EQUALS( testDescr, parseRet, cases[i].shouldPass );
+ TSM_ASSERT_EQUALS( testDescr, tmp.colors.size(), cases[i].numEntries );
+ if ( cases[i].shouldPass ) {
+ TSM_ASSERT_DIFFERS( testDescr, str, result );
+ TSM_ASSERT_EQUALS( testDescr, tmp.colorProfile, std::string(cases[i].name) );
+ } else {
+ TSM_ASSERT_EQUALS( testDescr, str, result );
+ TSM_ASSERT( testDescr, tmp.colorProfile.empty() );
+ }
+ }
+ }
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-color.cpp b/src/svg/svg-color.cpp
new file mode 100644
index 0000000..1c8c021
--- /dev/null
+++ b/src/svg/svg-color.cpp
@@ -0,0 +1,659 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * \file
+ * Reading \& writing of SVG/CSS colors.
+ */
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <cstdlib>
+#include <cstdio> // sprintf
+#include <cstring>
+#include <string>
+#include <cmath>
+#include <glib.h> // g_assert
+#include <cerrno>
+
+#include <map>
+
+#include "colorspace.h"
+#include "strneq.h"
+#include "preferences.h"
+#include "svg-color.h"
+#include "svg-icc-color.h"
+
+#include "color.h"
+#if defined(HAVE_LIBLCMS2)
+
+#include <vector>
+#include "object/color-profile.h"
+
+#include "document.h"
+#include "inkscape.h"
+#include "profile-manager.h"
+#endif // defined(HAVE_LIBLCMS2)
+
+#include "cms-system.h"
+
+struct SPSVGColor {
+ unsigned long rgb;
+ const std::string name;
+};
+
+/*
+ * These are the colors defined in the SVG standard
+ */
+static SPSVGColor const sp_svg_color_named[] = {
+ { 0xF0F8FF, "aliceblue" },
+ { 0xFAEBD7, "antiquewhite" },
+ { 0x00FFFF, "aqua" },
+ { 0x7FFFD4, "aquamarine" },
+ { 0xF0FFFF, "azure" },
+ { 0xF5F5DC, "beige" },
+ { 0xFFE4C4, "bisque" },
+ { 0x000000, "black" },
+ { 0xFFEBCD, "blanchedalmond" },
+ { 0x0000FF, "blue" },
+ { 0x8A2BE2, "blueviolet" },
+ { 0xA52A2A, "brown" },
+ { 0xDEB887, "burlywood" },
+ { 0x5F9EA0, "cadetblue" },
+ { 0x7FFF00, "chartreuse" },
+ { 0xD2691E, "chocolate" },
+ { 0xFF7F50, "coral" },
+ { 0x6495ED, "cornflowerblue" },
+ { 0xFFF8DC, "cornsilk" },
+ { 0xDC143C, "crimson" },
+ { 0x00FFFF, "cyan" },
+ { 0x00008B, "darkblue" },
+ { 0x008B8B, "darkcyan" },
+ { 0xB8860B, "darkgoldenrod" },
+ { 0xA9A9A9, "darkgray" },
+ { 0x006400, "darkgreen" },
+ { 0xA9A9A9, "darkgrey" },
+ { 0xBDB76B, "darkkhaki" },
+ { 0x8B008B, "darkmagenta" },
+ { 0x556B2F, "darkolivegreen" },
+ { 0xFF8C00, "darkorange" },
+ { 0x9932CC, "darkorchid" },
+ { 0x8B0000, "darkred" },
+ { 0xE9967A, "darksalmon" },
+ { 0x8FBC8F, "darkseagreen" },
+ { 0x483D8B, "darkslateblue" },
+ { 0x2F4F4F, "darkslategray" },
+ { 0x2F4F4F, "darkslategrey" },
+ { 0x00CED1, "darkturquoise" },
+ { 0x9400D3, "darkviolet" },
+ { 0xFF1493, "deeppink" },
+ { 0x00BFFF, "deepskyblue" },
+ { 0x696969, "dimgray" },
+ { 0x696969, "dimgrey" },
+ { 0x1E90FF, "dodgerblue" },
+ { 0xB22222, "firebrick" },
+ { 0xFFFAF0, "floralwhite" },
+ { 0x228B22, "forestgreen" },
+ { 0xFF00FF, "fuchsia" },
+ { 0xDCDCDC, "gainsboro" },
+ { 0xF8F8FF, "ghostwhite" },
+ { 0xFFD700, "gold" },
+ { 0xDAA520, "goldenrod" },
+ { 0x808080, "gray" },
+ { 0x808080, "grey" },
+ { 0x008000, "green" },
+ { 0xADFF2F, "greenyellow" },
+ { 0xF0FFF0, "honeydew" },
+ { 0xFF69B4, "hotpink" },
+ { 0xCD5C5C, "indianred" },
+ { 0x4B0082, "indigo" },
+ { 0xFFFFF0, "ivory" },
+ { 0xF0E68C, "khaki" },
+ { 0xE6E6FA, "lavender" },
+ { 0xFFF0F5, "lavenderblush" },
+ { 0x7CFC00, "lawngreen" },
+ { 0xFFFACD, "lemonchiffon" },
+ { 0xADD8E6, "lightblue" },
+ { 0xF08080, "lightcoral" },
+ { 0xE0FFFF, "lightcyan" },
+ { 0xFAFAD2, "lightgoldenrodyellow" },
+ { 0xD3D3D3, "lightgray" },
+ { 0x90EE90, "lightgreen" },
+ { 0xD3D3D3, "lightgrey" },
+ { 0xFFB6C1, "lightpink" },
+ { 0xFFA07A, "lightsalmon" },
+ { 0x20B2AA, "lightseagreen" },
+ { 0x87CEFA, "lightskyblue" },
+ { 0x778899, "lightslategray" },
+ { 0x778899, "lightslategrey" },
+ { 0xB0C4DE, "lightsteelblue" },
+ { 0xFFFFE0, "lightyellow" },
+ { 0x00FF00, "lime" },
+ { 0x32CD32, "limegreen" },
+ { 0xFAF0E6, "linen" },
+ { 0xFF00FF, "magenta" },
+ { 0x800000, "maroon" },
+ { 0x66CDAA, "mediumaquamarine" },
+ { 0x0000CD, "mediumblue" },
+ { 0xBA55D3, "mediumorchid" },
+ { 0x9370DB, "mediumpurple" },
+ { 0x3CB371, "mediumseagreen" },
+ { 0x7B68EE, "mediumslateblue" },
+ { 0x00FA9A, "mediumspringgreen" },
+ { 0x48D1CC, "mediumturquoise" },
+ { 0xC71585, "mediumvioletred" },
+ { 0x191970, "midnightblue" },
+ { 0xF5FFFA, "mintcream" },
+ { 0xFFE4E1, "mistyrose" },
+ { 0xFFE4B5, "moccasin" },
+ { 0xFFDEAD, "navajowhite" },
+ { 0x000080, "navy" },
+ { 0xFDF5E6, "oldlace" },
+ { 0x808000, "olive" },
+ { 0x6B8E23, "olivedrab" },
+ { 0xFFA500, "orange" },
+ { 0xFF4500, "orangered" },
+ { 0xDA70D6, "orchid" },
+ { 0xEEE8AA, "palegoldenrod" },
+ { 0x98FB98, "palegreen" },
+ { 0xAFEEEE, "paleturquoise" },
+ { 0xDB7093, "palevioletred" },
+ { 0xFFEFD5, "papayawhip" },
+ { 0xFFDAB9, "peachpuff" },
+ { 0xCD853F, "peru" },
+ { 0xFFC0CB, "pink" },
+ { 0xDDA0DD, "plum" },
+ { 0xB0E0E6, "powderblue" },
+ { 0x800080, "purple" },
+ { 0x663399, "rebeccapurple" },
+ { 0xFF0000, "red" },
+ { 0xBC8F8F, "rosybrown" },
+ { 0x4169E1, "royalblue" },
+ { 0x8B4513, "saddlebrown" },
+ { 0xFA8072, "salmon" },
+ { 0xF4A460, "sandybrown" },
+ { 0x2E8B57, "seagreen" },
+ { 0xFFF5EE, "seashell" },
+ { 0xA0522D, "sienna" },
+ { 0xC0C0C0, "silver" },
+ { 0x87CEEB, "skyblue" },
+ { 0x6A5ACD, "slateblue" },
+ { 0x708090, "slategray" },
+ { 0x708090, "slategrey" },
+ { 0xFFFAFA, "snow" },
+ { 0x00FF7F, "springgreen" },
+ { 0x4682B4, "steelblue" },
+ { 0xD2B48C, "tan" },
+ { 0x008080, "teal" },
+ { 0xD8BFD8, "thistle" },
+ { 0xFF6347, "tomato" },
+ { 0x40E0D0, "turquoise" },
+ { 0xEE82EE, "violet" },
+ { 0xF5DEB3, "wheat" },
+ { 0xFFFFFF, "white" },
+ { 0xF5F5F5, "whitesmoke" },
+ { 0xFFFF00, "yellow" },
+ { 0x9ACD32, "yellowgreen" }
+};
+
+static std::map<std::string, unsigned long> sp_svg_create_color_hash();
+
+guint32 sp_svg_read_color(gchar const *str, guint32 const dfl)
+{
+ return sp_svg_read_color(str, nullptr, dfl);
+}
+
+static guint32 internal_sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 def)
+{
+ static std::map<std::string, unsigned long> colors;
+ guint32 val = 0;
+
+ if (str == nullptr) return def;
+ while ((*str <= ' ') && *str) str++;
+ if (!*str) return def;
+
+ if (str[0] == '#') {
+ gint i;
+ for (i = 1; str[i]; i++) {
+ int hexval;
+ if (str[i] >= '0' && str[i] <= '9')
+ hexval = str[i] - '0';
+ else if (str[i] >= 'A' && str[i] <= 'F')
+ hexval = str[i] - 'A' + 10;
+ else if (str[i] >= 'a' && str[i] <= 'f')
+ hexval = str[i] - 'a' + 10;
+ else
+ break;
+ val = (val << 4) + hexval;
+ }
+ /* handle #rgb case */
+ if (i == 1 + 3) {
+ val = ((val & 0xf00) << 8) |
+ ((val & 0x0f0) << 4) |
+ (val & 0x00f);
+ val |= val << 4;
+ } else if (i != 1 + 6) {
+ /* must be either 3 or 6 digits. */
+ return def;
+ }
+ if (end_ptr) {
+ *end_ptr = str + i;
+ }
+ } else if (strneq(str, "rgb(", 4)) {
+ bool hasp, hasd;
+ gchar *s, *e;
+ gdouble r, g, b;
+
+ s = (gchar *) str + 4;
+ hasp = false;
+ hasd = false;
+
+ r = g_ascii_strtod(s, &e);
+ if (s == e) return def;
+ s = e;
+ if (*s == '%') {
+ hasp = true;
+ s += 1;
+ } else {
+ hasd = true;
+ }
+ while (*s && g_ascii_isspace(*s)) s += 1;
+ if (*s != ',') return def;
+ s += 1;
+ while (*s && g_ascii_isspace(*s)) s += 1;
+ g = g_ascii_strtod(s, &e);
+ if (s == e) return def;
+ s = e;
+ if (*s == '%') {
+ hasp = true;
+ s += 1;
+ } else {
+ hasd = true;
+ }
+ while (*s && g_ascii_isspace(*s)) s += 1;
+ if (*s != ',') return def;
+ s += 1;
+ while (*s && g_ascii_isspace(*s)) s += 1;
+ b = g_ascii_strtod(s, &e);
+ if (s == e) return def;
+ s = e;
+ if (*s == '%') {
+ hasp = true;
+ s += 1;
+ } else {
+ hasd = true;
+ }
+ while(*s && g_ascii_isspace(*s)) s += 1;
+ if (*s != ')') {
+ return def;
+ }
+ ++s;
+ if (hasp && hasd) return def;
+ if (hasp) {
+ val = static_cast<guint>(floor(CLAMP(r, 0.0, 100.0) * 2.559999)) << 24;
+ val |= (static_cast<guint>(floor(CLAMP(g, 0.0, 100.0) * 2.559999)) << 16);
+ val |= (static_cast<guint>(floor(CLAMP(b, 0.0, 100.0) * 2.559999)) << 8);
+ } else {
+ val = static_cast<guint>(CLAMP(r, 0, 255)) << 24;
+ val |= (static_cast<guint>(CLAMP(g, 0, 255)) << 16);
+ val |= (static_cast<guint>(CLAMP(b, 0, 255)) << 8);
+ }
+ if (end_ptr) {
+ *end_ptr = s;
+ }
+ return val;
+ } else if (strneq(str, "hsl(", 4)) {
+
+ gchar *ptr = (gchar *) str + 4;
+
+ gchar *e; // ptr after read
+
+ double h = g_ascii_strtod(ptr, &e); // Read h (0-360)
+ if (ptr == e) return def; // Read failed
+ ptr = e;
+
+ while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space
+ if (*ptr != ',') return def; // Need comma
+ ptr += 1;
+ while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space
+
+ double s = g_ascii_strtod(ptr, &e); // Read s (percent)
+ if (ptr == e) return def; // Read failed
+ ptr = e;
+ while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space
+ if (*ptr != '%') return def; // Need %
+ ptr += 1;
+
+ while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space
+ if (*ptr != ',') return def; // Need comma
+ ptr += 1;
+ while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space
+
+ double l = g_ascii_strtod(ptr, &e); // Read l (percent)
+ if (ptr == e) return def; // Read failed
+ ptr = e;
+ while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space
+ if (*ptr != '%') return def; // Need %
+ ptr += 1;
+
+ if (end_ptr) {
+ *end_ptr = ptr;
+ }
+
+
+ // Normalize to 0..1
+ h /= 360.0;
+ s /= 100.0;
+ l /= 100.0;
+
+ gfloat rgb[3];
+
+ SPColor::hsl_to_rgb_floatv( rgb, h, s, l );
+
+ val = static_cast<guint>(floor(CLAMP(rgb[0], 0.0, 1.0) * 255.9999)) << 24;
+ val |= (static_cast<guint>(floor(CLAMP(rgb[1], 0.0, 1.0) * 255.9999)) << 16);
+ val |= (static_cast<guint>(floor(CLAMP(rgb[2], 0.0, 1.0) * 255.9999)) << 8);
+ return val;
+
+ } else {
+ gint i;
+ if (colors.empty()) {
+ colors = sp_svg_create_color_hash();
+ }
+ gchar c[32];
+ for (i = 0; i < 31; i++) {
+ if (str[i] == ';' || g_ascii_isspace(str[i])) {
+ c[i] = '\0';
+ break;
+ }
+ c[i] = g_ascii_tolower(str[i]);
+ if (!str[i]) break;
+ }
+ c[31] = '\0';
+
+ if (colors.count(std::string(c))) {
+ val = colors[std::string(c)];
+ }
+ else {
+ return def;
+ }
+ if (end_ptr) {
+ *end_ptr = str + i;
+ }
+ }
+
+ return (val << 8);
+}
+
+guint32 sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 dfl)
+{
+ /* I've been rather hurried in editing the above to add support for end_ptr, so I'm adding
+ * this check wrapper. */
+ gchar const *end = str;
+ guint32 const ret = internal_sp_svg_read_color(str, &end, dfl);
+ g_assert(((ret == dfl) && (end == str))
+ || (((ret & 0xff) == 0)
+ && (str < end)));
+ if (str < end) {
+ gchar *buf = (gchar *) g_malloc(end + 1 - str);
+ memcpy(buf, str, end - str);
+ buf[end - str] = '\0';
+ gchar const *buf_end = buf;
+ guint32 const check = internal_sp_svg_read_color(buf, &buf_end, 1);
+ g_assert(check == ret
+ && buf_end - buf == end - str);
+ g_free(buf);
+
+ if ( end_ptr ) {
+ *end_ptr = end;
+ }
+ }
+ return ret;
+}
+
+
+/**
+ * Converts an RGB colour expressed in form 0x00rrggbb to a CSS/SVG representation of that colour.
+ * The result is valid even in SVG Tiny or non-SVG CSS.
+ */
+static void rgb24_to_css(char *const buf, unsigned const rgb24)
+{
+ g_assert(rgb24 < (1u << 24));
+
+ /* SVG 1.1 Full allows additional colour names not supported by SVG Tiny, but we don't bother
+ * with them: it's good for these colours to be copyable to non-SVG CSS stylesheets and for
+ * documents to be more viewable in SVG Tiny/Basic viewers; and some of the SVG Full names are
+ * less meaningful than hex equivalents anyway. And it's easier for a person to map from the
+ * restricted set because the only component values are {00,80,ff}, other than silver 0xc0c0c0.
+ */
+
+ char const *src = nullptr;
+ switch (rgb24) {
+ /* Extracted mechanically from the table at
+ * http://www.w3.org/TR/REC-html40/types.html#h-6.5 .*/
+ case 0x000000: src = "black"; break;
+ case 0xc0c0c0: src = "silver"; break;
+ case 0x808080: src = "gray"; break;
+ case 0xffffff: src = "white"; break;
+ case 0x800000: src = "maroon"; break;
+ case 0xff0000: src = "red"; break;
+ case 0x800080: src = "purple"; break;
+ case 0xff00ff: src = "fuchsia"; break;
+ case 0x008000: src = "green"; break;
+ case 0x00ff00: src = "lime"; break;
+ case 0x808000: src = "olive"; break;
+ case 0xffff00: src = "yellow"; break;
+ case 0x000080: src = "navy"; break;
+ case 0x0000ff: src = "blue"; break;
+ case 0x008080: src = "teal"; break;
+ case 0x00ffff: src = "aqua"; break;
+
+ default: {
+ if ((rgb24 & 0xf0f0f) * 0x11 == rgb24) {
+ /* Can use the shorter three-digit form #rgb instead of #rrggbb. */
+ std::sprintf(buf, "#%x%x%x",
+ (rgb24 >> 16) & 0xf,
+ (rgb24 >> 8) & 0xf,
+ rgb24 & 0xf);
+ } else {
+ std::sprintf(buf, "#%06x", rgb24);
+ }
+ break;
+ }
+ }
+ if (src) {
+ strcpy(buf, src);
+ }
+
+ // assert(sp_svg_read_color(buf, 0xff) == (rgb24 << 8));
+}
+
+/**
+ * Converts an RGBA32 colour to a CSS/SVG representation of the RGB portion of that colour. The
+ * result is valid even in SVG Tiny or non-SVG CSS.
+ *
+ * \param rgba32 Colour expressed in form 0xrrggbbaa.
+ * \pre buflen \>= 8.
+ */
+void sp_svg_write_color(gchar *buf, unsigned const buflen, guint32 const rgba32)
+{
+ g_assert(8 <= buflen);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ unsigned const rgb24 = rgba32 >> 8;
+ if ( prefs->getBool("/options/svgoutput/usenamedcolors") &&
+ !prefs->getBool("/options/svgoutput/disable_optimizations" )) {
+ rgb24_to_css(buf, rgb24);
+ } else {
+ g_snprintf(buf, buflen, "#%06x", rgb24);
+ }
+}
+
+static std::map<std::string, unsigned long>
+sp_svg_create_color_hash()
+{
+ std::map<std::string, unsigned long> colors;
+
+ for (const auto & i : sp_svg_color_named) {
+ colors[i.name] = i.rgb;
+ }
+ return colors;
+}
+
+#if defined(HAVE_LIBLCMS2)
+
+void icc_color_to_sRGB(SVGICCColor* icc, guchar* r, guchar* g, guchar* b)
+{
+ if (icc) {
+ g_message("profile name: %s", icc->colorProfile.c_str());
+ Inkscape::ColorProfile* prof = SP_ACTIVE_DOCUMENT->getProfileManager()->find(icc->colorProfile.c_str());
+ if ( prof ) {
+ guchar color_out[4] = {0,0,0,0};
+ cmsHTRANSFORM trans = prof->getTransfToSRGB8();
+ if ( trans ) {
+ std::vector<colorspace::Component> comps = colorspace::getColorSpaceInfo( prof );
+
+ size_t count = Inkscape::CMSSystem::getChannelCount( prof );
+ size_t cap = std::min(count, comps.size());
+ guchar color_in[4];
+ for (size_t i = 0; i < cap; i++) {
+ color_in[i] = static_cast<guchar>((((gdouble)icc->colors[i]) * 256.0) * (gdouble)comps[i].scale);
+ g_message("input[%d]: %d", (int)i, (int)color_in[i]);
+ }
+
+ Inkscape::CMSSystem::doTransform( trans, color_in, color_out, 1 );
+g_message("transform to sRGB done");
+ }
+ *r = color_out[0];
+ *g = color_out[1];
+ *b = color_out[2];
+ }
+ }
+}
+#endif //defined(HAVE_LIBLCMS2)
+
+/*
+ * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj
+ * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z'
+ * Allowed ASCII remaining chars add: '-', '.', '0'-'9',
+ */
+bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor* dest )
+{
+ bool good = true;
+
+ if ( end_ptr ) {
+ *end_ptr = str;
+ }
+ if ( dest ) {
+ dest->colorProfile.clear();
+ dest->colors.clear();
+ }
+
+ if ( !str ) {
+ // invalid input
+ good = false;
+ } else {
+ while ( g_ascii_isspace(*str) ) {
+ str++;
+ }
+
+ good = strneq( str, "icc-color(", 10 );
+
+ if ( good ) {
+ str += 10;
+ while ( g_ascii_isspace(*str) ) {
+ str++;
+ }
+
+ if ( !g_ascii_isalpha(*str)
+ && ( !(0x080 & *str) )
+ && (*str != '_')
+ && (*str != ':') ) {
+ // Name must start with a certain type of character
+ good = false;
+ } else {
+ while ( g_ascii_isdigit(*str) || g_ascii_isalpha(*str)
+ || (*str == '-') || (*str == ':') || (*str == '_') || (*str == '.') ) {
+ if ( dest ) {
+ dest->colorProfile += *str;
+ }
+ str++;
+ }
+ while ( g_ascii_isspace(*str) || *str == ',' ) {
+ str++;
+ }
+ }
+ }
+
+ if ( good ) {
+ while ( *str && *str != ')' ) {
+ if ( g_ascii_isdigit(*str) || *str == '.' || *str == '-' || *str == '+') {
+ gchar* endPtr = nullptr;
+ gdouble dbl = g_ascii_strtod( str, &endPtr );
+ if ( !errno ) {
+ if ( dest ) {
+ dest->colors.push_back( dbl );
+ }
+ str = endPtr;
+ } else {
+ good = false;
+ break;
+ }
+
+ while ( g_ascii_isspace(*str) || *str == ',' ) {
+ str++;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ // We need to have ended on a closing parenthesis
+ if ( good ) {
+ while ( g_ascii_isspace(*str) ) {
+ str++;
+ }
+ good &= (*str == ')');
+ }
+ }
+
+ if ( good ) {
+ if ( end_ptr ) {
+ *end_ptr = str;
+ }
+ } else {
+ if ( dest ) {
+ dest->colorProfile.clear();
+ dest->colors.clear();
+ }
+ }
+
+ return good;
+}
+
+
+bool sp_svg_read_icc_color( gchar const *str, SVGICCColor* dest )
+{
+ return sp_svg_read_icc_color(str, nullptr, dest);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-color.h b/src/svg/svg-color.h
new file mode 100644
index 0000000..6b1cc02
--- /dev/null
+++ b/src/svg/svg-color.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SVG_SVG_COLOR_H_SEEN
+#define SVG_SVG_COLOR_H_SEEN
+
+typedef unsigned int guint32;
+struct SVGICCColor;
+
+guint32 sp_svg_read_color(char const *str, unsigned int dfl);
+guint32 sp_svg_read_color(char const *str, char const **end_ptr, guint32 def);
+void sp_svg_write_color(char *buf, unsigned int buflen, unsigned int rgba32);
+
+bool sp_svg_read_icc_color( char const *str, char const **end_ptr, SVGICCColor* dest );
+bool sp_svg_read_icc_color( char const *str, SVGICCColor* dest );
+void icc_color_to_sRGB(SVGICCColor* dest, unsigned char* r, unsigned char* g, unsigned char* b);
+
+#endif /* !SVG_SVG_COLOR_H_SEEN */
diff --git a/src/svg/svg-icc-color.h b/src/svg/svg-icc-color.h
new file mode 100644
index 0000000..2a1f89e
--- /dev/null
+++ b/src/svg/svg-icc-color.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SVG_ICC_COLOR_H_SEEN
+#define SVG_ICC_COLOR_H_SEEN
+
+#include <string>
+#include <vector>
+
+/**
+ * An icc-color specification. Corresponds to the DOM interface of the same name.
+ *
+ * Referenced by SPIPaint.
+ */
+struct SVGICCColor {
+ std::string colorProfile;
+ std::vector<double> colors;
+};
+
+
+#endif /* !SVG_ICC_COLOR_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-length-test.h b/src/svg/svg-length-test.h
new file mode 100644
index 0000000..38f2046
--- /dev/null
+++ b/src/svg/svg-length-test.h
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <cxxtest/TestSuite.h>
+
+#include "svg/svg-length.h"
+#include <glib.h>
+#include <utility>
+
+// function internal to svg-length.cpp:
+gchar const *sp_svg_length_get_css_units(SVGLength::Unit unit);
+
+class SvgLengthTest : public CxxTest::TestSuite
+{
+private:
+ struct test_t {
+ char const* str; SVGLength::Unit unit; float value; float computed;
+ };
+ struct testd_t {
+ char const* str; double val; int prec; int minexp;
+ };
+ static test_t const absolute_tests[12];
+ static test_t const relative_tests[3];
+ static char const * fail_tests[8];
+
+public:
+ SvgLengthTest() {
+ }
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static SvgLengthTest *createSuite() { return new SvgLengthTest(); }
+ static void destroySuite( SvgLengthTest *suite ) { delete suite; }
+
+ void testRead()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(absolute_tests); i++) {
+ SVGLength len;
+ TSM_ASSERT(absolute_tests[i].str , len.read(absolute_tests[i].str));
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.unit , absolute_tests[i].unit);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.value , absolute_tests[i].value);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.computed , absolute_tests[i].computed);
+ }
+ for(size_t i=0; i<G_N_ELEMENTS(relative_tests); i++) {
+ SVGLength len;
+ TSM_ASSERT(relative_tests[i].str , len.read(relative_tests[i].str));
+ len.update(7,13,19);
+ TSM_ASSERT_EQUALS(relative_tests[i].str , len.unit , relative_tests[i].unit);
+ TSM_ASSERT_EQUALS(relative_tests[i].str , len.value , relative_tests[i].value);
+ TSM_ASSERT_EQUALS(relative_tests[i].str , len.computed , relative_tests[i].computed);
+ }
+ for(size_t i=0; i<G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength len;
+ TSM_ASSERT(fail_tests[i] , !len.read(fail_tests[i]));
+ }
+ }
+
+ void testReadOrUnset()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(absolute_tests); i++) {
+ SVGLength len;
+ len.readOrUnset(absolute_tests[i].str);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.unit , absolute_tests[i].unit);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.value , absolute_tests[i].value);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.computed , absolute_tests[i].computed);
+ }
+ for(size_t i=0; i<G_N_ELEMENTS(relative_tests); i++) {
+ SVGLength len;
+ len.readOrUnset(relative_tests[i].str);
+ len.update(7,13,19);
+ TSM_ASSERT_EQUALS(relative_tests[i].str , len.unit , relative_tests[i].unit);
+ TSM_ASSERT_EQUALS(relative_tests[i].str , len.value , relative_tests[i].value);
+ TSM_ASSERT_EQUALS(relative_tests[i].str , len.computed , relative_tests[i].computed);
+ }
+ for(size_t i=0; i<G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength len;
+ len.readOrUnset(fail_tests[i], SVGLength::INCH, 123, 456);
+ TSM_ASSERT_EQUALS(fail_tests[i] , len.unit , SVGLength::INCH);
+ TSM_ASSERT_EQUALS(fail_tests[i] , len.value , 123);
+ TSM_ASSERT_EQUALS(fail_tests[i] , len.computed , 456);
+ }
+ }
+
+ void testReadAbsolute()
+ {
+ for(size_t i=0; i<G_N_ELEMENTS(absolute_tests); i++) {
+ SVGLength len;
+ TSM_ASSERT(absolute_tests[i].str , len.readAbsolute(absolute_tests[i].str));
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.unit , absolute_tests[i].unit);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.value , absolute_tests[i].value);
+ TSM_ASSERT_EQUALS(absolute_tests[i].str , len.computed , absolute_tests[i].computed);
+ }
+ for(size_t i=0; i<G_N_ELEMENTS(relative_tests); i++) {
+ SVGLength len;
+ TSM_ASSERT(relative_tests[i].str , !len.readAbsolute(relative_tests[i].str));
+ }
+ for(size_t i=0; i<G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength len;
+ TSM_ASSERT(fail_tests[i] , !len.readAbsolute(fail_tests[i]));
+ }
+ }
+
+ void testEnumMappedToString()
+ {
+ for ( int i = (static_cast<int>(SVGLength::NONE) + 1); i <= static_cast<int>(SVGLength::LAST_UNIT); i++ ) {
+ SVGLength::Unit target = static_cast<SVGLength::Unit>(i);
+ // PX is a special case where we don't have a unit string
+ if ( (target != SVGLength::PX) ) {
+ gchar const* val = sp_svg_length_get_css_units(target);
+ TSM_ASSERT_DIFFERS(i, val, "");
+ }
+ }
+ }
+
+ // Ensure that all unit suffix strings used are allowed by SVG
+ void testStringsAreValidSVG()
+ {
+ gchar const* valid[] = {"", "em", "ex", "px", "pt", "pc", "cm", "mm", "in", "%"};
+ std::set<std::string> validStrings(valid, valid + G_N_ELEMENTS(valid));
+ for ( int i = (static_cast<int>(SVGLength::NONE) + 1); i <= static_cast<int>(SVGLength::LAST_UNIT); i++ ) {
+ SVGLength::Unit target = static_cast<SVGLength::Unit>(i);
+ gchar const* val = sp_svg_length_get_css_units(target);
+ TSM_ASSERT(i, validStrings.find(std::string(val)) != validStrings.end());
+ }
+ }
+
+ // Ensure that all unit suffix strings allowed by SVG are covered by enum
+ void testValidSVGStringsSupported()
+ {
+ // Note that "px" is omitted from the list, as it will be assumed to be so if not explicitly set.
+ gchar const* valid[] = {"em", "ex", "pt", "pc", "cm", "mm", "in", "%"};
+ std::set<std::string> validStrings(valid, valid + G_N_ELEMENTS(valid));
+ for ( int i = (static_cast<int>(SVGLength::NONE) + 1); i <= static_cast<int>(SVGLength::LAST_UNIT); i++ ) {
+ SVGLength::Unit target = static_cast<SVGLength::Unit>(i);
+ gchar const* val = sp_svg_length_get_css_units(target);
+ std::set<std::string>::iterator iter = validStrings.find(std::string(val));
+ if (iter != validStrings.end()) {
+ validStrings.erase(iter);
+ }
+ }
+ TSM_ASSERT_EQUALS(validStrings, validStrings.size(), 0u);
+ }
+
+ void testPlaces()
+ {
+ testd_t const precTests[] = {
+ {"760", 761.92918978947023, 2, -8},
+ {"761.9", 761.92918978947023, 4, -8},
+ };
+
+ for ( size_t i = 0; i < G_N_ELEMENTS(precTests); i++ ) {
+ char buf[256] = {0};
+ memset(buf, 0xCC, sizeof(buf)); // Make it easy to detect an overrun.
+ unsigned int retval = sp_svg_number_write_de( buf, sizeof(buf), precTests[i].val, precTests[i].prec, precTests[i].minexp );
+ TSM_ASSERT_EQUALS("Number of chars written", retval, strlen(precTests[i].str));
+ TSM_ASSERT_EQUALS("Numeric string written", std::string(buf), std::string(precTests[i].str));
+ TSM_ASSERT_EQUALS(std::string("Buffer overrun ") + precTests[i].str, '\xCC', buf[retval + 1]);
+ }
+ }
+
+ // TODO: More tests
+};
+
+SvgLengthTest::test_t const SvgLengthTest::absolute_tests[12] = {
+ {"0", SVGLength::NONE, 0 , 0},
+ {"1", SVGLength::NONE, 1 , 1},
+ {"1.00001", SVGLength::NONE, 1.00001 , 1.00001},
+ {"1px", SVGLength::PX , 1 , 1},
+ {".1px", SVGLength::PX , 0.1 , 0.1},
+ {"100pt", SVGLength::PT , 100 , 400.0/3.0},
+ {"1e2pt", SVGLength::PT , 100 , 400.0/3.0},
+ {"3pc", SVGLength::PC , 3 , 48},
+ {"-3.5pc", SVGLength::PC , -3.5 , -3.5*16.0},
+ {"1.2345678mm", SVGLength::MM , 1.2345678, 1.2345678f*96.0/25.4}, // TODO: More precise constants? (a 7 digit constant when the default precision is 8 digits?)
+ {"123.45678cm", SVGLength::CM , 123.45678 , 123.45678f*96.0/2.54}, // Note that svg_length_read is casting the result from g_ascii_strtod to float.
+ {"73.162987in", SVGLength::INCH, 73.162987 , 73.162987f*96.0/1.00}};
+SvgLengthTest::test_t const SvgLengthTest::relative_tests[3] = {
+ {"123em", SVGLength::EM, 123, 123. * 7.},
+ {"123ex", SVGLength::EX, 123, 123. * 13.},
+ {"123%", SVGLength::PERCENT, 1.23, 1.23 * 19.}};
+char const * SvgLengthTest::fail_tests[8] = {
+ "123 px",
+ "123e",
+ "123e+m",
+ "123ec",
+ "123pxt",
+ "--123",
+ "",
+ "px"};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-length.cpp b/src/svg/svg-length.cpp
new file mode 100644
index 0000000..a84c38b
--- /dev/null
+++ b/src/svg/svg-length.cpp
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SVG data parser
+ *//*
+ * Authors: see git history
+
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <glib.h>
+#include <iostream>
+#include <vector>
+
+#include "svg.h"
+#include "stringstream.h"
+#include "util/units.h"
+
+static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next);
+
+#ifndef MAX
+# define MAX(a,b) ((a < b) ? (b) : (a))
+#endif
+
+unsigned int sp_svg_number_read_f(gchar const *str, float *val)
+{
+ if (!str) {
+ return 0;
+ }
+
+ char *e;
+ float const v = g_ascii_strtod(str, &e);
+ if ((gchar const *) e == str) {
+ return 0;
+ }
+
+ *val = v;
+ return 1;
+}
+
+unsigned int sp_svg_number_read_d(gchar const *str, double *val)
+{
+ if (!str) {
+ return 0;
+ }
+
+ char *e;
+ double const v = g_ascii_strtod(str, &e);
+ if ((gchar const *) e == str) {
+ return 0;
+ }
+
+ *val = v;
+ return 1;
+}
+
+// TODO must add a buffer length parameter for safety:
+// rewrite using std::string?
+static unsigned int sp_svg_number_write_ui(gchar *buf, unsigned int val)
+{
+ unsigned int i = 0;
+ char c[16u];
+ do {
+ c[16u - (++i)] = '0' + (val % 10u);
+ val /= 10u;
+ } while (val > 0u);
+
+ memcpy(buf, &c[16u - i], i);
+ buf[i] = 0;
+
+ return i;
+}
+
+// TODO unsafe code ignoring bufLen
+// rewrite using std::string?
+static unsigned int sp_svg_number_write_i(gchar *buf, int bufLen, int val)
+{
+ int p = 0;
+ unsigned int uval;
+ if (val < 0) {
+ buf[p++] = '-';
+ uval = (unsigned int)-val;
+ } else {
+ uval = (unsigned int)val;
+ }
+
+ p += sp_svg_number_write_ui(buf+p, uval);
+
+ return p;
+}
+
+// TODO unsafe code ignoring bufLen
+// rewrite using std::string?
+static unsigned sp_svg_number_write_d(gchar *buf, int bufLen, double val, unsigned int tprec, unsigned int fprec)
+{
+ /* Process sign */
+ int i = 0;
+ if (val < 0.0) {
+ buf[i++] = '-';
+ val = fabs(val);
+ }
+
+ /* Determine number of integral digits */
+ int idigits = 0;
+ if (val >= 1.0) {
+ idigits = (int) floor(log10(val)) + 1;
+ }
+
+ /* Determine the actual number of fractional digits */
+ fprec = MAX(static_cast<int>(fprec), static_cast<int>(tprec) - idigits);
+ /* Round value */
+ val += 0.5 / pow(10.0, fprec);
+ /* Extract integral and fractional parts */
+ double dival = floor(val);
+ double fval = val - dival;
+ /* Write integra */
+ if (idigits > (int)tprec) {
+ i += sp_svg_number_write_ui(buf + i, (unsigned int)floor(dival/pow(10.0, idigits-tprec) + .5));
+ for(unsigned int j=0; j<(unsigned int)idigits-tprec; j++) {
+ buf[i+j] = '0';
+ }
+ i += idigits-tprec;
+ } else {
+ i += sp_svg_number_write_ui(buf + i, (unsigned int)dival);
+ }
+ int end_i = i;
+ if (fprec > 0 && fval > 0.0) {
+ buf[i++] = '.';
+ do {
+ fval *= 10.0;
+ dival = floor(fval);
+ fval -= dival;
+ int const int_dival = (int) dival;
+ buf[i++] = '0' + int_dival;
+ if (int_dival != 0) {
+ end_i = i;
+ }
+ fprec -= 1;
+ } while(fprec > 0 && fval > 0.0);
+ }
+ buf[end_i] = 0;
+ return end_i;
+}
+
+unsigned int sp_svg_number_write_de(gchar *buf, int bufLen, double val, unsigned int tprec, int min_exp)
+{
+ int eval = (int)floor(log10(fabs(val)));
+ if (val == 0.0 || eval < min_exp) {
+ return sp_svg_number_write_ui(buf, 0);
+ }
+ unsigned int maxnumdigitsWithoutExp = // This doesn't include the sign because it is included in either representation
+ eval<0?tprec+(unsigned int)-eval+1:
+ eval+1<(int)tprec?tprec+1:
+ (unsigned int)eval+1;
+ unsigned int maxnumdigitsWithExp = tprec + ( eval<0 ? 4 : 3 ); // It's not necessary to take larger exponents into account, because then maxnumdigitsWithoutExp is DEFINITELY larger
+ if (maxnumdigitsWithoutExp <= maxnumdigitsWithExp) {
+ return sp_svg_number_write_d(buf, bufLen, val, tprec, 0);
+ } else {
+ val = eval < 0 ? val * pow(10.0, -eval) : val / pow(10.0, eval);
+ int p = sp_svg_number_write_d(buf, bufLen, val, tprec, 0);
+ buf[p++] = 'e';
+ p += sp_svg_number_write_i(buf + p, bufLen - p, eval);
+ return p;
+ }
+}
+
+SVGLength::SVGLength()
+ : _set(false)
+ , unit(NONE)
+ , value(0)
+ , computed(0)
+{
+}
+
+/* Length */
+
+bool SVGLength::read(gchar const *str)
+{
+ if (!str) {
+ return false;
+ }
+
+ SVGLength::Unit u;
+ float v;
+ float c;
+ if (!sp_svg_length_read_lff(str, &u, &v, &c, nullptr)) {
+ return false;
+ }
+
+ if (!std::isfinite(v)) {
+ return false;
+ }
+
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+
+ return true;
+}
+
+static bool svg_length_absolute_unit(SVGLength::Unit u)
+{
+ return (u != SVGLength::EM && u != SVGLength::EX && u != SVGLength::PERCENT);
+}
+
+bool SVGLength::readAbsolute(gchar const *str)
+{
+ if (!str) {
+ return false;
+ }
+
+ SVGLength::Unit u;
+ float v;
+ float c;
+ if (!sp_svg_length_read_lff(str, &u, &v, &c, nullptr)) {
+ return false;
+ }
+
+ if (svg_length_absolute_unit(u) == false) {
+ return false;
+ }
+
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+
+ return true;
+}
+
+
+unsigned int sp_svg_length_read_computed_absolute(gchar const *str, float *length)
+{
+ if (!str) {
+ return 0;
+ }
+
+ SVGLength::Unit unit;
+ float computed;
+ if (!sp_svg_length_read_lff(str, &unit, nullptr, &computed, nullptr)) {
+ // failed to read
+ return 0;
+ }
+
+ if (svg_length_absolute_unit(unit) == false) {
+ return 0;
+ }
+
+ *length = computed;
+
+ return 1;
+}
+
+std::vector<SVGLength> sp_svg_length_list_read(gchar const *str)
+{
+ if (!str) {
+ return std::vector<SVGLength>();
+ }
+
+ SVGLength::Unit unit;
+ float value;
+ float computed;
+ char *next = (char *) str;
+ std::vector<SVGLength> list;
+
+ while (sp_svg_length_read_lff(next, &unit, &value, &computed, &next)) {
+
+ SVGLength length;
+ length.set(unit, value, computed);
+ list.push_back(length);
+
+ while (next && *next &&
+ (*next == ',' || *next == ' ' || *next == '\n' || *next == '\r' || *next == '\t')) {
+ // the list can be comma- or space-separated, but we will be generous and accept
+ // a mix, including newlines and tabs
+ next++;
+ }
+
+ if (!next || !*next) {
+ break;
+ }
+ }
+
+ return list;
+}
+
+
+#define UVAL(a,b) (((unsigned int) (a) << 8) | (unsigned int) (b))
+
+static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next)
+{
+/* note: this function is sometimes fed a string with several consecutive numbers, e.g. by sp_svg_length_list_read.
+So after the number, the string does not necessarily have a \0 or a unit, it might also contain a space or comma and then the next number!
+*/
+
+ if (!str) {
+ return 0;
+ }
+
+ gchar const *e;
+ float const v = g_ascii_strtod(str, (char **) &e);
+ if (e == str) {
+ return 0;
+ }
+
+ if (!e[0]) {
+ /* Unitless */
+ if (unit) {
+ *unit = SVGLength::NONE;
+ }
+ if (val) {
+ *val = v;
+ }
+ if (computed) {
+ *computed = v;
+ }
+ if (next) {
+ *next = nullptr; // no more values
+ }
+ return 1;
+ } else if (!g_ascii_isalnum(e[0])) {
+ /* Unitless or percent */
+ if (e[0] == '%') {
+ /* Percent */
+ if (e[1] && g_ascii_isalnum(e[1])) {
+ return 0;
+ }
+ if (unit) {
+ *unit = SVGLength::PERCENT;
+ }
+ if (val) {
+ *val = v * 0.01;
+ }
+ if (computed) {
+ *computed = v * 0.01;
+ }
+ if (next) {
+ *next = (char *) e + 1;
+ }
+ return 1;
+ } else if (g_ascii_isspace(e[0]) && e[1] && g_ascii_isalpha(e[1])) {
+ return 0; // spaces between value and unit are not allowed
+ } else {
+ /* Unitless */
+ if (unit) {
+ *unit = SVGLength::NONE;
+ }
+ if (val) {
+ *val = v;
+ }
+ if (computed) {
+ *computed = v;
+ }
+ if (next) {
+ *next = (char *) e;
+ }
+ return 1;
+ }
+ } else if (e[1] && !g_ascii_isalnum(e[2])) {
+ /* TODO: Allow the number of px per inch to vary (document preferences, X server
+ * or whatever). E.g. don't fill in computed here, do it at the same time as
+ * percentage units are done. */
+ unsigned int const uval = UVAL(e[0], e[1]);
+ switch (uval) {
+ case UVAL('p','x'):
+ if (unit) {
+ *unit = SVGLength::PX;
+ }
+ if (computed) {
+ *computed = v;
+ }
+ break;
+ case UVAL('p','t'):
+ if (unit) {
+ *unit = SVGLength::PT;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "pt", "px");
+ }
+ break;
+ case UVAL('p','c'):
+ if (unit) {
+ *unit = SVGLength::PC;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "pc", "px");
+ }
+ break;
+ case UVAL('m','m'):
+ if (unit) {
+ *unit = SVGLength::MM;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "mm", "px");
+ }
+ break;
+ case UVAL('c','m'):
+ if (unit) {
+ *unit = SVGLength::CM;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "cm", "px");
+ }
+ break;
+ case UVAL('i','n'):
+ if (unit) {
+ *unit = SVGLength::INCH;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "in", "px");
+ }
+ break;
+ case UVAL('e','m'):
+ if (unit) {
+ *unit = SVGLength::EM;
+ }
+ break;
+ case UVAL('e','x'):
+ if (unit) {
+ *unit = SVGLength::EX;
+ }
+ break;
+ default:
+ /* Invalid */
+ return 0;
+ break;
+ }
+ if (val) {
+ *val = v;
+ }
+ if (next) {
+ *next = (char *) e + 2;
+ }
+ return 1;
+ }
+
+ /* Invalid */
+ return 0;
+}
+
+unsigned int sp_svg_length_read_ldd(gchar const *str, SVGLength::Unit *unit, double *value, double *computed)
+{
+ float a;
+ float b;
+ unsigned int r = sp_svg_length_read_lff(str, unit, &a, &b, nullptr);
+ if (r) {
+ if (value) {
+ *value = a;
+ }
+ if (computed) {
+ *computed = b;
+ }
+ }
+ return r;
+}
+
+std::string SVGLength::write() const
+{
+ return sp_svg_length_write_with_units(*this);
+}
+
+void SVGLength::set(SVGLength::Unit u, float v)
+{
+ _set = true;
+ unit = u;
+ Glib::ustring hack("px");
+ switch( unit ) {
+ case NONE:
+ case PX:
+ case EM:
+ case EX:
+ case PERCENT:
+ break;
+ case PT:
+ hack = "pt";
+ break;
+ case PC:
+ hack = "pc";
+ break;
+ case MM:
+ hack = "pt";
+ break;
+ case CM:
+ hack = "pt";
+ break;
+ case INCH:
+ hack = "pt";
+ break;
+ default:
+ break;
+ }
+ value = v;
+ computed = Inkscape::Util::Quantity::convert(v, hack, "px");
+}
+
+void SVGLength::set(SVGLength::Unit u, float v, float c)
+{
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+}
+
+void SVGLength::unset(SVGLength::Unit u, float v, float c)
+{
+ _set = false;
+ unit = u;
+ value = v;
+ computed = c;
+}
+
+void SVGLength::scale(double scale)
+{
+ value *= scale;
+ computed *= scale;
+}
+
+void SVGLength::update(double em, double ex, double scale)
+{
+ if (unit == EM) {
+ computed = value * em;
+ } else if (unit == EX) {
+ computed = value * ex;
+ } else if (unit == PERCENT) {
+ computed = value * scale;
+ }
+}
+
+double sp_svg_read_percentage(char const *str, double def)
+{
+ if (str == nullptr) {
+ return def;
+ }
+
+ char *u;
+ double v = g_ascii_strtod(str, &u);
+ while (isspace(*u)) {
+ if (*u == '\0') {
+ return v;
+ }
+ u++;
+ }
+ if (*u == '%') {
+ v /= 100.0;
+ }
+
+ return v;
+}
+
+gchar const *sp_svg_length_get_css_units(SVGLength::Unit unit)
+{
+ switch (unit) {
+ case SVGLength::NONE: return "";
+ case SVGLength::PX: return "";
+ case SVGLength::PT: return "pt";
+ case SVGLength::PC: return "pc";
+ case SVGLength::MM: return "mm";
+ case SVGLength::CM: return "cm";
+ case SVGLength::INCH: return "in";
+ case SVGLength::EM: return "em";
+ case SVGLength::EX: return "ex";
+ case SVGLength::PERCENT: return "%";
+ }
+ return "";
+}
+
+/**
+ * N.B.\ This routine will sometimes return strings with `e' notation, so is unsuitable for CSS
+ * lengths (which don't allow scientific `e' notation).
+ */
+std::string sp_svg_length_write_with_units(SVGLength const &length)
+{
+ Inkscape::SVGOStringStream os;
+ if (length.unit == SVGLength::PERCENT) {
+ os << 100*length.value << sp_svg_length_get_css_units(length.unit);
+ } else {
+ os << length.value << sp_svg_length_get_css_units(length.unit);
+ }
+ return os.str();
+}
+
+
+void SVGLength::readOrUnset(gchar const *str, Unit u, float v, float c)
+{
+ if (!read(str)) {
+ unset(u, v, c);
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/svg/svg-length.h b/src/svg/svg-length.h
new file mode 100644
index 0000000..17e04a2
--- /dev/null
+++ b/src/svg/svg-length.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Carl Hetherington <inkscape@carlh.net>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_SP_SVG_LENGTH_H
+#define SEEN_SP_SVG_LENGTH_H
+
+#include <string>
+
+/**
+ * SVG length type
+ */
+class SVGLength {
+public:
+ SVGLength();
+
+ enum Unit {
+ NONE,
+ PX,
+ PT,
+ PC,
+ MM,
+ CM,
+ INCH,
+ EM,
+ EX,
+ PERCENT,
+ LAST_UNIT = PERCENT
+ };
+
+ // The object's value is valid / exists in SVG.
+ bool _set;
+
+ // The unit of value.
+ Unit unit;
+
+ // The value of this SVGLength as found in the SVG.
+ float value;
+
+ // The value in pixels (value * pixels/unit).
+ float computed;
+
+ float operator=(float v) {
+ _set = true;
+ unit = NONE;
+ value = computed = v;
+ return v;
+ }
+
+ bool read(char const *str);
+ void readOrUnset(char const *str, Unit u = NONE, float v = 0, float c = 0);
+ bool readAbsolute(char const *str);
+ std::string write() const;
+ // To set 'v' use '='
+ void set(Unit u, float v); // Sets computed value based on u and v.
+ void set(Unit u, float v, float c); // Sets all three values.
+ void unset(Unit u = NONE, float v = 0, float c = 0);
+ void scale(double scale); // Scales length (value, computed), leaving unit alone.
+ void update(double em, double ex, double scale); // Updates computed value
+};
+
+#endif // SEEN_SP_SVG_LENGTH_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/svg/svg-path-geom-test.h b/src/svg/svg-path-geom-test.h
new file mode 100644
index 0000000..acfcc28
--- /dev/null
+++ b/src/svg/svg-path-geom-test.h
@@ -0,0 +1,523 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2015 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <cxxtest/TestSuite.h>
+#include "2geom/coord.h"
+#include "2geom/curves.h"
+#include "2geom/pathvector.h"
+#include "svg/svg.h"
+#include "preferences.h"
+#include "streq.h"
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include <glib.h>
+
+class SvgPathGeomTest : public CxxTest::TestSuite
+{
+private:
+ std::vector<std::string> rectanglesAbsoluteClosed;
+ std::vector<std::string> rectanglesRelativeClosed;
+ std::vector<std::string> rectanglesAbsoluteOpen;
+ std::vector<std::string> rectanglesRelativeOpen;
+ std::vector<std::string> rectanglesAbsoluteClosed2;
+ std::vector<std::string> rectanglesRelativeClosed2;
+ Geom::PathVector rectanglepvopen;
+ Geom::PathVector rectanglepvclosed;
+ Geom::PathVector rectanglepvclosed2;
+public:
+ SvgPathGeomTest() {
+ // Lots of ways to define the same rectangle
+ rectanglesAbsoluteClosed.push_back("M 1,2 L 4,2 L 4,8 L 1,8 z");
+ rectanglesAbsoluteClosed.push_back("M 1,2 4,2 4,8 1,8 z");
+ rectanglesAbsoluteClosed.push_back("M 1,2 H 4 V 8 H 1 z");
+ rectanglesRelativeClosed.push_back("m 1,2 l 3,0 l 0,6 l -3,0 z");
+ rectanglesRelativeClosed.push_back("m 1,2 3,0 0,6 -3,0 z");
+ rectanglesRelativeClosed.push_back("m 1,2 h 3 v 6 h -3 z");
+ rectanglesAbsoluteOpen.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2");
+ rectanglesAbsoluteOpen.push_back("M 1,2 4,2 4,8 1,8 1,2");
+ rectanglesAbsoluteOpen.push_back("M 1,2 H 4 V 8 H 1 V 2");
+ rectanglesRelativeOpen.push_back("m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6");
+ rectanglesRelativeOpen.push_back("m 1,2 3,0 0,6 -3,0 0,-6");
+ rectanglesRelativeOpen.push_back("m 1,2 h 3 v 6 h -3 v -6");
+ rectanglesAbsoluteClosed2.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2 z");
+ rectanglesAbsoluteClosed2.push_back("M 1,2 4,2 4,8 1,8 1,2 z");
+ rectanglesAbsoluteClosed2.push_back("M 1,2 H 4 V 8 H 1 V 2 z");
+ rectanglesRelativeClosed2.push_back("m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6 z");
+ rectanglesRelativeClosed2.push_back("m 1,2 3,0 0,6 -3,0 0,-6 z");
+ rectanglesRelativeClosed2.push_back("m 1,2 h 3 v 6 h -3 v -6 z");
+ rectanglepvopen.push_back(Geom::Path(Geom::Point(1,2)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(1,8),Geom::Point(1,2)));
+ rectanglepvclosed.push_back(Geom::Path(Geom::Point(1,2)));
+ rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));
+ rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));
+ rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));
+ rectanglepvclosed.back().close();
+ rectanglepvclosed2.push_back(Geom::Path(Geom::Point(1,2)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(1,8),Geom::Point(1,2)));
+ rectanglepvclosed2.back().close();
+ // TODO: Also test some (smooth) cubic/quadratic beziers and elliptical arcs
+ // TODO: Should we make it mandatory that h/v in the path data results in a H/VLineSegment?
+ // If so, the tests should be modified to reflect this.
+ }
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+ static SvgPathGeomTest *createSuite() { return new SvgPathGeomTest(); }
+ static void destroySuite( SvgPathGeomTest *suite ) { delete suite; }
+
+ void testReadRectanglesAbsoluteClosed()
+ {
+ for(size_t i=0; i<rectanglesAbsoluteClosed.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed[i].c_str());
+ TSM_ASSERT(rectanglesAbsoluteClosed[i].c_str(), bpathEqual(pv,rectanglepvclosed));
+ }
+ }
+
+ void testReadRectanglesRelativeClosed()
+ {
+ for(size_t i=0; i<rectanglesRelativeClosed.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed[i].c_str());
+ TSM_ASSERT(rectanglesRelativeClosed[i].c_str(), bpathEqual(pv,rectanglepvclosed));
+ }
+ }
+
+ void testReadRectanglesAbsoluteOpen()
+ {
+ for(size_t i=0; i<rectanglesAbsoluteOpen.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteOpen[i].c_str());
+ TSM_ASSERT(rectanglesAbsoluteOpen[i].c_str(), bpathEqual(pv,rectanglepvopen));
+ }
+ }
+
+ void testReadRectanglesRelativeOpen()
+ {
+ for(size_t i=0; i<rectanglesRelativeOpen.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeOpen[i].c_str());
+ TSM_ASSERT(rectanglesRelativeOpen[i].c_str(), bpathEqual(pv,rectanglepvopen));
+ }
+ }
+
+ void testReadRectanglesAbsoluteClosed2()
+ {
+ for(size_t i=0; i<rectanglesAbsoluteClosed2.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed2[i].c_str());
+ TSM_ASSERT(rectanglesAbsoluteClosed2[i].c_str(), bpathEqual(pv,rectanglepvclosed2));
+ }
+ }
+
+ void testReadRectanglesRelativeClosed2()
+ {
+ for(size_t i=0; i<rectanglesRelativeClosed2.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed2[i].c_str());
+ TSM_ASSERT(rectanglesRelativeClosed2[i].c_str(), bpathEqual(pv,rectanglepvclosed2));
+ }
+ }
+
+ void testReadConcatenatedPaths()
+ {
+ // Note that finalPoint doesn't actually return the final point of the path, just the last given point... (but since this might be intentional and we're not testing lib2geom here, we just specify the final point explicitly
+ Geom::PathVector pv_good;
+ pv_good.push_back(rectanglepvclosed.back());
+ pv_good.push_back(rectanglepvopen.back() * Geom::Translate(1,2)/* * Geom::Translate(pv_good[0].finalPoint())*/);
+ pv_good.push_back(rectanglepvclosed.back() * Geom::Translate(2,4)/* *Geom::Translate(pv_good[1].finalPoint())*/);
+ pv_good.push_back(rectanglepvopen.back());
+ pv_good[0].close();
+ pv_good[1].close(false);
+ pv_good[2].close();
+ pv_good[3].close(false);
+ std::string path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];
+ Geom::PathVector pv = sp_svg_read_pathv(path_str.c_str());
+ TS_ASSERT(bpathEqual(pv,pv_good));
+ }
+
+ void testReadZeroLengthSubpaths() {
+ // Per the SVG 1.1 specification (section F5) zero-length subpaths are relevant
+ Geom::PathVector pv_good;
+ pv_good.push_back(Geom::Path(Geom::Point(0,0)));
+ pv_good.push_back(Geom::Path(Geom::Point(1,1)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2)));
+ pv_good.push_back(Geom::Path(Geom::Point(3,3)));
+ pv_good.back().close();
+ pv_good.push_back(Geom::Path(Geom::Point(4,4)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(4,4),Geom::Point(5,5)));
+ pv_good.back().close();
+ pv_good.push_back(Geom::Path(Geom::Point(6,6)));
+ { // Test absolute version
+ char const * path_str = "M 0,0 M 1,1 L 2,2 M 3,3 z M 4,4 L 5,5 z M 6,6";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good));
+ }
+ { // Test relative version
+ char const * path_str = "m 0,0 m 1,1 l 1,1 m 1,1 z m 1,1 l 1,1 z m 2,2";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good));
+ }
+ }
+
+ void testReadImplicitMoveto() {
+ TS_WARN("Currently lib2geom (/libnr) has no way of specifying the difference between 'M 0,0 ... z M 0,0 L 1,0' and 'M 0,0 ... z L 1,0', the SVG specification does state that these should be handled differently with respect to markers however, see the description of the 'orient' attribute of the 'marker' element.");
+ Geom::PathVector pv_good;
+ pv_good.push_back(Geom::Path(Geom::Point(1,1)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2)));
+ pv_good.back().close();
+ pv_good.push_back(Geom::Path(Geom::Point(1,1)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(3,3)));
+ pv_good.back().close();
+ { // Test absolute version
+ char const * path_str = "M 1,1 L 2,2 z L 3,3 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good));
+ }
+ { // Test relative version
+ char const * path_str = "M 1,1 l 1,1 z l 2,2 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good));
+ }
+ }
+
+ void testReadFloatingPoint() {
+ Geom::PathVector pv_good1;
+ pv_good1.push_back(Geom::Path(Geom::Point(.01,.02)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(.01,.02),Geom::Point(.04,.02)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(.04,.02),Geom::Point(1.5,1.6)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(1.5,1.6),Geom::Point(.01,.08)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(.01,.08),Geom::Point(.01,.02)));
+ pv_good1.back().close();
+ { // Test decimals
+ char const * path_str = "M .01,.02 L.04.02 L1.5,1.6L0.01,0.08 .01.02 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good1));
+ }
+ Geom::PathVector pv_good2;
+ pv_good2.push_back(Geom::Path(Geom::Point(.01,.02)));
+ pv_good2.back().append(Geom::LineSegment(Geom::Point(.01,.02),Geom::Point(.04,.02)));
+ pv_good2.back().append(Geom::LineSegment(Geom::Point(.04,.02),Geom::Point(1.5,1.6)));
+ pv_good2.back().append(Geom::LineSegment(Geom::Point(1.5,1.6),Geom::Point(.01,.08)));
+ pv_good2.back().close();
+ { // Test exponent
+ char const * path_str = "M 1e-2,.2e-1 L 0.004e1,0.0002e+2 L0150E-2,1.6e0L1.0e-2,80e-3 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good2));
+ }
+ }
+
+ void testReadImplicitSeparation() {
+ // Coordinates need not be separated by whitespace if they can still be read unambiguously
+ Geom::PathVector pv_good;
+ pv_good.push_back(Geom::Path(Geom::Point(.1,.2)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(.1,.2),Geom::Point(.4,.2)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(.4,.2),Geom::Point(.4,.8)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(.4,.8),Geom::Point(.1,.8)));
+ pv_good.back().close();
+ { // Test absolute
+ char const * path_str = "M .1.2+0.4.2e0.4e0+8e-1.1.8 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good));
+ }
+ { // Test relative
+ char const * path_str = "m .1.2+0.3.0e0.0e0+6e-1-.3.0 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good));
+ }
+ }
+
+ void testReadErrorMisplacedCharacter() {
+ char const * path_str;
+ Geom::PathVector pv;
+ // Comma in the wrong place (commas may only appear between parameters)
+ path_str = "M 1,2 4,2 4,8 1,8 z , m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Comma in the wrong place (commas may only appear between parameters)
+ path_str = "M 1,2 4,2 4,8 1,8 z m,13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Period in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z . m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Sign in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z + - m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Digit in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z 9809 m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Digit in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z 9809 876 m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ }
+
+ void testReadErrorUnrecognizedCharacter() {
+ char const * path_str;
+ Geom::PathVector pv;
+ // Unrecognized character
+ path_str = "M 1,2 4,2 4,8 1,8 z&m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Unrecognized character
+ path_str = "M 1,2 4,2 4,8 1,8 z m &13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ }
+
+ void testReadErrorTypo() {
+ char const * path_str;
+ Geom::PathVector pv;
+ // Typo
+ path_str = "M 1,2 4,2 4,8 1,8 z j 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+
+ // Typo
+ path_str = "M 1,2 4,2 4,8 1,8 L 1,2 x m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvopen));
+ }
+
+ void testReadErrorIllformedNumbers() {
+ char const * path_str;
+ Geom::PathVector pv;
+ // Double exponent
+ path_str = "M 1,2 4,2 4,8 1,8 z m 13e4e5,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Double sign
+ path_str = "M 1,2 4,2 4,8 1,8 z m +-13,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Double sign
+ path_str = "M 1,2 4,2 4,8 1,8 z m 13e+-12,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m .e12,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m .,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m +,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m +.e+,15";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ }
+
+ void testReadErrorJunk() {
+ char const * path_str;
+ Geom::PathVector pv;
+ // Junk
+ path_str = "M 1,2 4,2 4,8 1,8 z j 357 hkjh.,34e34 90ih6kj4 h5k6vlh4N.,6,45wikuyi3yere..3487 m 13,23";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ }
+
+ void testReadErrorStopReading() {
+ char const * path_str;
+ Geom::PathVector pv;
+ // Unrecognized parameter
+ path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Invalid parameter
+ path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+ // Illformed parameter
+ path_str = "M 1,2 4,2 4,8 1,8 z m +-12,23,34";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));
+
+ // "Third" parameter
+ path_str = "M 1,2 4,2 4,8 1,8 1,2,3 M 12,23";
+ pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvopen));
+ }
+
+ void testRoundTrip() {
+ // This is the easiest way to (also) test writing path data, as a path can be written in more than one way.
+ Geom::PathVector pv;
+ Geom::PathVector new_pv;
+ std::string org_path_str;
+ char * path_str;
+ // Rectangle (closed)
+ org_path_str = rectanglesAbsoluteClosed[0];
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));
+ g_free(path_str);
+ // Rectangle (open)
+ org_path_str = rectanglesAbsoluteOpen[0];
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));
+ g_free(path_str);
+ // Concatenated rectangles
+ org_path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));
+ g_free(path_str);
+ // Zero-length subpaths
+ org_path_str = "M 0,0 M 1,1 L 2,2 M 3,3 z M 4,4 L 5,5 z M 6,6";
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));
+ g_free(path_str);
+ // Floating-point
+ org_path_str = "M .01,.02 L 0.04,0.02 L.04,.08L0.01,0.08 z""M 1e-2,.2e-1 L 0.004e1,0.0002e+2 L04E-2,.08e0L1.0e-2,80e-3 z";
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv, new_pv, 1e-17));
+ g_free(path_str);
+ }
+
+ void testMinexpPrecision() {
+ Geom::PathVector pv;
+ char * path_str;
+ // Default values
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/options/svgoutput/allowrelativecoordinates", true);
+ prefs->setBool("/options/svgoutput/forcerepeatcommands", false);
+ prefs->setInt("/options/svgoutput/numericprecision", 8);
+ prefs->setInt("/options/svgoutput/minimumexponent", -8);
+ pv = sp_svg_read_pathv("M 123456781,1.23456781e-8 L 123456782,1.23456782e-8 L 123456785,1.23456785e-8 L 10123456400,1.23456785e-8 L 123456789,1.23456789e-8 L 123456789,101.234564e-8 L 123456789,1.23456789e-8");
+ path_str = sp_svg_write_path(pv);
+ TS_ASSERT_RELATION( streq_rel , "m 123456780,1.2345678e-8 0,0 10,1e-15 9999999210,0 -9999999210,0 0,9.99999921e-7 0,-9.99999921e-7" , path_str );
+ g_free(path_str);
+ }
+
+private:
+ bool bpathEqual(Geom::PathVector const &a, Geom::PathVector const &b, double eps = 1e-16) {
+ if (a.size() != b.size()) {
+ char temp[100];
+ sprintf(temp, "PathVectors not the same size: %u != %u", static_cast<unsigned int>(a.size()),static_cast<unsigned int>( b.size()));
+ TS_FAIL(temp);
+ return false;
+ }
+ for(size_t i=0; i<a.size(); i++) {
+ Geom::Path const &pa = a[i];
+ Geom::Path const &pb = b[i];
+ if (pa.closed() && !pb.closed()) {
+ char temp[100];
+ sprintf(temp, "Left subpath is closed, right subpath is open. Subpath: %u", static_cast<unsigned int>(i));
+ TS_FAIL(temp);
+ return false;
+ }
+ if (!pa.closed() && pb.closed()) {
+ char temp[100];
+ sprintf(temp, "Right subpath is closed, left subpath is open. Subpath: %u", static_cast<unsigned int>(i));
+ TS_FAIL(temp);
+ return false;
+ }
+ if (pa.size() != pb.size()) {
+ char temp[100];
+ sprintf(temp, "Not the same number of segments: %u != %u, subpath: %u", static_cast<unsigned int>(pa.size()), static_cast<unsigned int>(pb.size()), static_cast<unsigned int>(i));
+ TS_FAIL(temp);
+ return false;
+ }
+ for(size_t j=0; j<pa.size(); j++) {
+ Geom::Curve const* ca = &pa[j];
+ Geom::Curve const* cb = &pb[j];
+ if (typeid(*ca) == typeid(*cb))
+ {
+ if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))
+ {
+ Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb);
+ if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {
+ char temp[200];
+ sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[0][Geom::X], (*la)[0][Geom::Y], (*lb)[0][Geom::X], (*lb)[0][Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {
+ char temp[200];
+ sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ }
+ else if(Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const*>(ca))
+ {
+ Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const*>(cb);
+ if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {
+ char temp[200];
+ sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[0][Geom::X], (*la)[0][Geom::Y], (*lb)[0][Geom::X], (*lb)[0][Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {
+ char temp[200];
+ sprintf(temp, "Different 1st control point: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ if (!Geom::are_near((*la)[2],(*lb)[2], eps)) {
+ char temp[200];
+ sprintf(temp, "Different 2nd control point: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[2][Geom::X], (*la)[2][Geom::Y], (*lb)[2][Geom::X], (*lb)[2][Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ if (!Geom::are_near((*la)[3],(*lb)[3], eps)) {
+ char temp[200];
+ sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[3][Geom::X], (*la)[3][Geom::Y], (*lb)[3][Geom::X], (*lb)[3][Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ }
+ else
+ {
+ char temp[200];
+ sprintf(temp, "Unknown curve type: %s, subpath: %u, segment: %u", typeid(*ca).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ }
+ }
+ else // not same type
+ {
+ char temp[200];
+ sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ TS_FAIL(temp);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+};
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-path.cpp b/src/svg/svg-path.cpp
new file mode 100644
index 0000000..1414d69
--- /dev/null
+++ b/src/svg/svg-path.cpp
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * svg-path.cpp: Parse SVG path element data into bezier path.
+ * Authors:
+ * Johan Engelen
+ * (old nartbpath code that has been deleted: Raph Levien <raph@artofcode.com>)
+ * (old nartbpath code that has been deleted: Lauris Kaplinski <lauris@ximian.com>)
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ * Copyright (C) 2008 Johan Engelen
+ *
+ * Copyright (C) 2000-2008 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <string>
+#include <glib.h> // g_assert()
+
+#include <2geom/pathvector.h>
+#include <2geom/curves.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/path-sink.h>
+#include <2geom/svg-path-parser.h>
+
+#include "svg/svg.h"
+#include "svg/path-string.h"
+
+/*
+ * Parses the path in str. When an error is found in the pathstring, this method
+ * returns a truncated path up to where the error was found in the pathstring.
+ * Returns an empty PathVector when str==NULL
+ */
+Geom::PathVector sp_svg_read_pathv(char const * str)
+{
+ Geom::PathVector pathv;
+ if (!str)
+ return pathv; // return empty pathvector when str == NULL
+
+ Geom::PathBuilder builder(pathv);
+ Geom::SVGPathParser parser(builder);
+ parser.setZSnapThreshold(Geom::EPSILON);
+
+ try {
+ parser.parse(str);
+ }
+ catch (Geom::SVGPathParseError &e) {
+ builder.flush();
+ // This warning is extremely annoying when testing
+ //g_warning("Malformed SVG path, truncated path up to where error was found.\n Input path=\"%s\"\n Parsed path=\"%s\"", str, sp_svg_write_path(pathv));
+ }
+
+ return pathv;
+}
+
+static void sp_svg_write_curve(Inkscape::SVG::PathString & str, Geom::Curve const * c) {
+ // TODO: this code needs to removed and replaced by appropriate path sink
+ if(Geom::LineSegment const *line_segment = dynamic_cast<Geom::LineSegment const *>(c)) {
+ // don't serialize stitch segments
+ if (!dynamic_cast<Geom::Path::StitchSegment const *>(c)) {
+ if (line_segment->initialPoint()[Geom::X] == line_segment->finalPoint()[Geom::X]) {
+ str.verticalLineTo( line_segment->finalPoint()[Geom::Y] );
+ } else if (line_segment->initialPoint()[Geom::Y] == line_segment->finalPoint()[Geom::Y]) {
+ str.horizontalLineTo( line_segment->finalPoint()[Geom::X] );
+ } else {
+ str.lineTo( (*line_segment)[1][0], (*line_segment)[1][1] );
+ }
+ }
+ }
+ else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast<Geom::QuadraticBezier const *>(c)) {
+ str.quadTo( (*quadratic_bezier)[1][0], (*quadratic_bezier)[1][1],
+ (*quadratic_bezier)[2][0], (*quadratic_bezier)[2][1] );
+ }
+ else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(c)) {
+ str.curveTo( (*cubic_bezier)[1][0], (*cubic_bezier)[1][1],
+ (*cubic_bezier)[2][0], (*cubic_bezier)[2][1],
+ (*cubic_bezier)[3][0], (*cubic_bezier)[3][1] );
+ }
+ else if(Geom::EllipticalArc const *elliptical_arc = dynamic_cast<Geom::EllipticalArc const *>(c)) {
+ str.arcTo( elliptical_arc->ray(Geom::X), elliptical_arc->ray(Geom::Y),
+ Geom::deg_from_rad(elliptical_arc->rotationAngle()),
+ elliptical_arc->largeArc(), elliptical_arc->sweep(),
+ elliptical_arc->finalPoint() );
+ } else {
+ //this case handles sbasis as well as all other curve types
+ Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c->toSBasis(), 0.1);
+
+ //recurse to convert the new path resulting from the sbasis to svgd
+ for(const auto & iter : sbasis_path) {
+ sp_svg_write_curve(str, &iter);
+ }
+ }
+}
+
+static void sp_svg_write_path(Inkscape::SVG::PathString & str, Geom::Path const & p) {
+ str.moveTo( p.initialPoint()[0], p.initialPoint()[1] );
+
+ for(Geom::Path::const_iterator cit = p.begin(); cit != p.end_open(); ++cit) {
+ sp_svg_write_curve(str, &(*cit));
+ }
+
+ if (p.closed()) {
+ str.closePath();
+ }
+}
+
+gchar * sp_svg_write_path(Geom::PathVector const &p) {
+ Inkscape::SVG::PathString str;
+
+ for(const auto & pit : p) {
+ sp_svg_write_path(str, pit);
+ }
+
+ return g_strdup(str.c_str());
+}
+
+gchar * sp_svg_write_path(Geom::Path const &p) {
+ Inkscape::SVG::PathString str;
+
+ sp_svg_write_path(str, p);
+
+ return g_strdup(str.c_str());
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/svg/svg.h b/src/svg/svg.h
new file mode 100644
index 0000000..a65bdf2
--- /dev/null
+++ b/src/svg/svg.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_SVG_H
+#define SEEN_SP_SVG_H
+
+/*
+ * SVG data parser
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include <cstring>
+#include <string>
+
+#include "svg/svg-length.h"
+#include <2geom/forward.h>
+
+/* Generic */
+
+/*
+ * These are very-very simple:
+ * - they accept everything libc strtod accepts
+ * - no valid end character checking
+ * Return FALSE and let val untouched on error
+ */
+
+unsigned int sp_svg_number_read_f( const char *str, float *val );
+unsigned int sp_svg_number_read_d( const char *str, double *val );
+
+/*
+ * No buffer overflow checking is done, so better wrap them if needed
+ */
+unsigned int sp_svg_number_write_de( char *buf, int bufLen, double val, unsigned int tprec, int min_exp );
+
+/* Length */
+
+/*
+ * Parse number with optional unit specifier:
+ * - for px, pt, pc, mm, cm, computed is final value according to SVG spec
+ * - for em, ex, and % computed is left untouched
+ * - % is divided by 100 (i.e. 100% is 1.0)
+ * !isalnum check is done at the end
+ * Any return value pointer can be NULL
+ */
+
+unsigned int sp_svg_length_read_computed_absolute( const char *str, float *length );
+std::vector<SVGLength> sp_svg_length_list_read( const char *str );
+unsigned int sp_svg_length_read_ldd( const char *str, SVGLength::Unit *unit, double *value, double *computed );
+
+std::string sp_svg_length_write_with_units(SVGLength const &length);
+
+bool sp_svg_transform_read(char const *str, Geom::Affine *transform);
+
+char *sp_svg_transform_write(Geom::Affine const &transform);
+char *sp_svg_transform_write(Geom::Affine const *transform);
+
+double sp_svg_read_percentage( const char * str, double def );
+
+/* NB! As paths can be long, we use here dynamic string */
+
+Geom::PathVector sp_svg_read_pathv( char const * str );
+char * sp_svg_write_path( Geom::PathVector const &p );
+char * sp_svg_write_path( Geom::Path const &p );
+
+#endif // SEEN_SP_SVG_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/test-stubs.cpp b/src/svg/test-stubs.cpp
new file mode 100644
index 0000000..b511c7e
--- /dev/null
+++ b/src/svg/test-stubs.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Stub out functions when building tests
+ *
+ * Authors:
+ * Kees Cook <kees@outflux.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "svg/test-stubs.h"
+#include <map>
+#include <string>
+
+std::map<std::string,long long int> int_prefs;
+
+void
+prefs_set_int_attribute(gchar const *path, gchar const *attr, long long int val)
+{
+ int_prefs[std::string(path) + '/' + std::string(attr)] = val;
+}
+
+long long int
+prefs_get_int_attribute(gchar const *path, gchar const *attr, long long int def)
+{
+ std::map<std::string,long long int>::const_iterator it=int_prefs.find(std::string(path) + '/' + std::string(attr));
+ long long int ret = it==int_prefs.end() ? def : it->second;
+ return ret;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/svg/test-stubs.h b/src/svg/test-stubs.h
new file mode 100644
index 0000000..0275f61
--- /dev/null
+++ b/src/svg/test-stubs.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Stub out functions when building tests
+ *
+ * Authors:
+ * Kees Cook <kees@outflux.net>
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_TEST_STUBS_H
+#define SEEN_TEST_STUBS_H
+
+#include <glib.h>
+
+long long int prefs_get_int_attribute(gchar const *path, gchar const *attr, long long int def);
+
+#endif /* !SEEN_TEST_STUBS_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :