diff options
| author | Jasper van de Gronde <jasper.vandegronde@gmail.com> | 2012-05-07 13:03:17 +0000 |
|---|---|---|
| committer | Jasper van de Gronde <th.v.d.gronde@hccnet.nl> | 2012-05-07 13:03:17 +0000 |
| commit | 10474ee5b50e0b0f9e9df8b73e597ec1e6130a8a (patch) | |
| tree | 532d445d71b2beee0be9396f4d31b8168bb34c50 /src | |
| parent | C++ification of SPCtrlLine in preparation of visibility improvements. (diff) | |
| download | inkscape-10474ee5b50e0b0f9e9df8b73e597ec1e6130a8a.tar.gz inkscape-10474ee5b50e0b0f9e9df8b73e597ec1e6130a8a.zip | |
Allow shorthand paths
(bzr r11321.1.1)
Diffstat (limited to 'src')
| -rw-r--r-- | src/svg/path-string.cpp | 5 | ||||
| -rw-r--r-- | src/svg/path-string.h | 45 | ||||
| -rw-r--r-- | src/svg/svg-path-geom-test.h | 64 |
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); |
