summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJasper van de Gronde <jasper.vandegronde@gmail.com>2012-05-07 13:03:17 +0000
committerJasper van de Gronde <th.v.d.gronde@hccnet.nl>2012-05-07 13:03:17 +0000
commit10474ee5b50e0b0f9e9df8b73e597ec1e6130a8a (patch)
tree532d445d71b2beee0be9396f4d31b8168bb34c50 /src
parentC++ification of SPCtrlLine in preparation of visibility improvements. (diff)
downloadinkscape-10474ee5b50e0b0f9e9df8b73e597ec1e6130a8a.tar.gz
inkscape-10474ee5b50e0b0f9e9df8b73e597ec1e6130a8a.zip
Allow shorthand paths
(bzr r11321.1.1)
Diffstat (limited to 'src')
-rw-r--r--src/svg/path-string.cpp5
-rw-r--r--src/svg/path-string.h45
-rw-r--r--src/svg/svg-path-geom-test.h64
3 files changed, 100 insertions, 14 deletions
diff --git a/src/svg/path-string.cpp b/src/svg/path-string.cpp
index 61e9c90a2..493d514d7 100644
--- a/src/svg/path-string.cpp
+++ b/src/svg/path-string.cpp
@@ -25,14 +25,17 @@ static int const maxprec = 16;
int Inkscape::SVG::PathString::numericprecision;
int Inkscape::SVG::PathString::minimumexponent;
+double Inkscape::SVG::PathString::epsilon;
Inkscape::SVG::PathString::PathString() :
allow_relative_coordinates(Inkscape::Preferences::get()->getBool("/options/svgoutput/allowrelativecoordinates", true)),
- force_repeat_commands(Inkscape::Preferences::get()->getBool("/options/svgoutput/forcerepeatcommands"))
+ force_repeat_commands(Inkscape::Preferences::get()->getBool("/options/svgoutput/forcerepeatcommands")),
+ allow_shorthands(Inkscape::Preferences::get()->getBool("/options/svgoutput/allowshorthands", true))
{
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
numericprecision = std::max<int>(minprec,std::min<int>(maxprec, prefs->getInt("/options/svgoutput/numericprecision", 8)));
minimumexponent = prefs->getInt("/options/svgoutput/minimumexponent", -8);
+ epsilon = pow(10, -numericprecision+1); // The +1 is to give a bit more headroom when we have "low" values (we use relative error, while really we should look at digits, so a number starting with 1 requires a tolerance that's about 10 times higher).
}
void Inkscape::SVG::PathString::_appendOp(char abs_op, char rel_op) {
diff --git a/src/svg/path-string.h b/src/svg/path-string.h
index f959b25b7..b361f7416 100644
--- a/src/svg/path-string.h
+++ b/src/svg/path-string.h
@@ -59,9 +59,7 @@ public:
PathString &moveTo(Geom::Point p) {
_appendOp('M','m');
- _appendPoint(p, true);
-
- _initial_point = _current_point;
+ _appendPoint(p, true, true);
return *this;
}
@@ -71,19 +69,21 @@ public:
PathString &lineTo(Geom::Point p) {
_appendOp('L','l');
- _appendPoint(p, true);
+ _appendPoint(p, true, true);
return *this;
}
PathString &horizontalLineTo(Geom::Coord x) {
_appendOp('H','h');
_appendX(x, true);
+ _last_control_point = _current_point;
return *this;
}
PathString &verticalLineTo(Geom::Coord y) {
_appendOp('V','v');
_appendY(y, true);
+ _last_control_point = _current_point;
return *this;
}
@@ -92,9 +92,16 @@ public:
}
PathString &quadTo(Geom::Point c, Geom::Point p) {
- _appendOp('Q','q');
- _appendPoint(c, false);
- _appendPoint(p, true);
+ Geom::Point ncp = 2*_current_point-_last_control_point;
+ if (allow_shorthands && Geom::are_near(ncp[Geom::X], c[Geom::X], fabs(epsilon*c[Geom::X])) && Geom::are_near(ncp[Geom::Y], c[Geom::Y], fabs(epsilon*c[Geom::Y]))) {
+ _appendOp('T', 't');
+ _last_control_point = ncp;
+ _appendPoint(p, true, false);
+ } else {
+ _appendOp('Q','q');
+ _appendPoint(c, false, true);
+ _appendPoint(p, true, false);
+ }
return *this;
}
@@ -106,10 +113,18 @@ public:
}
PathString &curveTo(Geom::Point c0, Geom::Point c1, Geom::Point p) {
- _appendOp('C','c');
- _appendPoint(c0, false);
- _appendPoint(c1, false);
- _appendPoint(p, true);
+ Geom::Point ncp = 2*_current_point-_last_control_point;
+ if (allow_shorthands && Geom::are_near(ncp[Geom::X], c0[Geom::X], fabs(epsilon*c0[Geom::X])) && Geom::are_near(ncp[Geom::Y], c0[Geom::Y], fabs(epsilon*c0[Geom::Y]))) {
+ _appendOp('S','s');
+ //_appendPoint(c0, false, false);
+ _appendPoint(c1, false, true);
+ _appendPoint(p, true, false);
+ } else {
+ _appendOp('C','c');
+ _appendPoint(c0, false, false);
+ _appendPoint(c1, false, true);
+ _appendPoint(p, true, false);
+ }
return *this;
}
@@ -125,7 +140,7 @@ public:
_appendValue(rot);
_appendFlag(large_arc);
_appendFlag(sweep);
- _appendPoint(p, true);
+ _appendPoint(p, true, true);
return *this;
}
@@ -169,11 +184,12 @@ private:
if (sc) _current_point[Geom::Y] = ry;
}
- void _appendPoint(Geom::Point p, bool sc) {
+ void _appendPoint(Geom::Point p, bool sc, bool slcp) {
Geom::Point rp;
_abs_state.append(p, rp);
_rel_state.appendRelative(rp, _current_point);
if (sc) _current_point = rp;
+ if (slcp) _last_control_point = rp;
}
struct State {
@@ -219,6 +235,7 @@ private:
Geom::Point _initial_point;
Geom::Point _current_point;
+ Geom::Point _last_control_point;
// If both states have a common prefix it is stored here.
// Separating out the common prefix prevents repeated copying between the states
@@ -229,8 +246,10 @@ private:
bool const allow_relative_coordinates;
bool const force_repeat_commands;
+ bool const allow_shorthands;
static int numericprecision;
static int minimumexponent;
+ static double epsilon;
};
}
diff --git a/src/svg/svg-path-geom-test.h b/src/svg/svg-path-geom-test.h
index 3558b4e55..88c6ad618 100644
--- a/src/svg/svg-path-geom-test.h
+++ b/src/svg/svg-path-geom-test.h
@@ -179,6 +179,41 @@ public:
}
}
+ void testReadShorthands() {
+ Geom::PathVector pv_good_1;
+ pv_good_1.push_back(Geom::Path(Geom::Point(1,0)));
+ pv_good_1.back().append(Geom::CubicBezier(pv_good_1.back().finalPoint(), Geom::Point(1,0.5),Geom::Point(0.5,1),Geom::Point(0,1)));
+ pv_good_1.back().append(Geom::CubicBezier(pv_good_1.back().finalPoint(), Geom::Point(-0.5,1),Geom::Point(-1,0.5),Geom::Point(-1,0)));
+ pv_good_1.back().append(Geom::QuadraticBezier(pv_good_1.back().finalPoint(), Geom::Point(-1,-1),Geom::Point(0,-1)));
+ pv_good_1.back().append(Geom::QuadraticBezier(pv_good_1.back().finalPoint(), Geom::Point(1,-1),Geom::Point(1,0)));
+ Geom::PathVector pv_good_2;
+ pv_good_2.push_back(Geom::Path(Geom::Point(1,0)));
+ pv_good_2.back().append(Geom::CubicBezier(pv_good_2.back().finalPoint(), Geom::Point(1,0),Geom::Point(0.5,1),Geom::Point(0,1)));
+ pv_good_2.back().append(Geom::CubicBezier(pv_good_2.back().finalPoint(), Geom::Point(-0.5,1),Geom::Point(-1,0.5),Geom::Point(-1,0)));
+ pv_good_2.back().append(Geom::QuadraticBezier(pv_good_2.back().finalPoint(), Geom::Point(-1,0),Geom::Point(0,-1)));
+ pv_good_2.back().append(Geom::QuadraticBezier(pv_good_2.back().finalPoint(), Geom::Point(1,-1),Geom::Point(1,0)));
+ { // Test absolute version without initial smooth point
+ char const * path_str = "M 1,0 C 1,0.5 0.5,1 0,1 S -1,0.5 -1,0 Q -1,-1 0,-1 T 1,0";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good_1));
+ }
+ { // Test relative version without initial smooth point
+ char const * path_str = "M 1,0 c 0,0.5 -0.5,1 -1,1 s -1,-0.5 -1,-1 q 0,-1 1,-1 t 1,1";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good_1));
+ }
+ { // Test absolute version with initial smooth point
+ char const * path_str = "M 1,0 S 0.5,1 0,1 S -1,0.5 -1,0 T 0,-1 T 1,0";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good_2));
+ }
+ { // Test relative version with initial smooth point
+ char const * path_str = "M 1,0 s -0.5,1 -1,1 s -1,-0.5 -1,-1 t 1,-1 t 1,1";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ TSM_ASSERT(path_str, bpathEqual(pv,pv_good_2));
+ }
+ }
+
void testReadFloatingPoint() {
Geom::PathVector pv_good1;
pv_good1.push_back(Geom::Path(Geom::Point(.01,.02)));
@@ -386,6 +421,13 @@ public:
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);
+ // Some curves
+ org_path_str = "M 1,0 C 1,0.5 0.5,1 0,1 S -1,0.5 -1,0 Q -1,-1 0,-1 T 1,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);
}
void testMinexpPrecision() {
@@ -485,6 +527,28 @@ private:
return false;
}
}
+ else if(Geom::QuadraticBezier const *la = dynamic_cast<Geom::QuadraticBezier const*>(ca))
+ {
+ Geom::QuadraticBezier const *lb = dynamic_cast<Geom::QuadraticBezier 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 end of segment: (%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;
+ }
+ }
else if(Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const*>(ca))
{
Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const*>(cb);