Code

2Geom version of the path tests + some additional small changes.
authorjaspervdg <jaspervdg@users.sourceforge.net>
Wed, 6 Aug 2008 14:31:46 +0000 (14:31 +0000)
committerjaspervdg <jaspervdg@users.sourceforge.net>
Wed, 6 Aug 2008 14:31:46 +0000 (14:31 +0000)
build.xml
src/svg/Makefile_insert
src/svg/svg-length-test.h
src/svg/svg-path-geom-test.h [new file with mode: 0644]
src/svg/svg-path-nr-test.h [new file with mode: 0644]
src/svg/svg-path-test.h [deleted file]

index c8a65cab3625a2f1219c56a374e026754187cf41..4a8bb093363df78a19646f20f7bc26c182533b99 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -52,7 +52,7 @@
   <!-- -->
   <property name="arch"          value="mingw32-"/>
   <property name="archutil"      value=""/>
-  <property name="devlibs"       location="c:/devlibs"/>
+  <property name="devlibs"       location="${env.DEVLIBS_PATH}"/>
   <property name="cxxtest"       location="cxxtest"/>
   <!-- -->
 
             <include name="svg-affine-test.h"/>
             <include name="svg-color-test.h"/>
             <include name="svg-length-test.h"/>
-            <include name="svg-path-test.h"/>
+            <include name="svg-path-geom-test.h"/>
+            <include name="svg-path-nr-test.h"/>
         </fileset>
     </cxxtestpart>
     <cxxtestpart command="python ${cxxtest}/cxxtestgen.py --have-eh"
index e8f31b3c82d96056ff0375c8d3ce7f8287f59314..f7d2fcc5e5729240bb167572ab53915c1ad82ca4 100644 (file)
@@ -50,7 +50,8 @@ svg_test_svg_includes = \
        $(srcdir)/svg/svg-affine-test.h \
        $(srcdir)/svg/svg-color-test.h \
        $(srcdir)/svg/svg-length-test.h \
-       $(srcdir)/svg/svg-path-test.h
+       $(srcdir)/svg/svg-path-geom-test.h \
+       $(srcdir)/svg/svg-path-nr-test.h
 
 svg_libtest_svg_a_SOURCES = \
        svg/test-svg.cpp        \
index 0f1ca5c959908545b54bf27080347450330c8bf4..ce709b51d29b07199d63d02c46fe96b0ac1b79a9 100644 (file)
@@ -131,7 +131,7 @@ public:
                 validStrings.erase(iter);
             }
         }
-        TSM_ASSERT_EQUALS(validStrings, validStrings.size(), 0);
+        TSM_ASSERT_EQUALS(validStrings, validStrings.size(), 0u);
     }
 
     // TODO: More tests
diff --git a/src/svg/svg-path-geom-test.h b/src/svg/svg-path-geom-test.h
new file mode 100644 (file)
index 0000000..1bcc5fb
--- /dev/null
@@ -0,0 +1,563 @@
+#include <cxxtest/TestSuite.h>\r
+#include "2geom/coord.h"\r
+#include "2geom/curves.h"\r
+#include "2geom/pathvector.h"\r
+#include "svg/svg.h"\r
+#include "prefs-utils.h"\r
+#include "streq.h"\r
+#include <stdio.h>\r
+#include <string>\r
+#include <vector>\r
+#include <glib/gmem.h>\r
+\r
+class SvgPathGeomTest : public CxxTest::TestSuite\r
+{\r
+private:\r
+    std::vector<std::string> rectanglesAbsoluteClosed;\r
+    std::vector<std::string> rectanglesRelativeClosed;\r
+    std::vector<std::string> rectanglesAbsoluteOpen;\r
+    std::vector<std::string> rectanglesRelativeOpen;\r
+    Geom::PathVector rectanglepv;\r
+public:\r
+    SvgPathGeomTest() {\r
+        // Lots of ways to define the same rectangle\r
+        rectanglesAbsoluteClosed.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2 Z");\r
+        rectanglesAbsoluteClosed.push_back("M 1,2 L 4,2 L 4,8 L 1,8 z");\r
+        rectanglesAbsoluteClosed.push_back("M 1,2 4,2 4,8 1,8 z");\r
+        rectanglesAbsoluteClosed.push_back("M 1,2 H 4 V 8 H 1 z");\r
+        rectanglesRelativeClosed.push_back("m 1,2 l 3,0 l 0,6 l -3,0 z");\r
+        rectanglesRelativeClosed.push_back("m 1,2 3,0 0,6 -3,0 z");\r
+        rectanglesRelativeClosed.push_back("m 1,2 h 3 v 6 h -3 z");\r
+        rectanglesAbsoluteOpen.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2");\r
+        rectanglesAbsoluteOpen.push_back("M 1,2 4,2 4,8 1,8 1,2");\r
+        rectanglesAbsoluteOpen.push_back("M 1,2 H 4 V 8 H 1 V 2");\r
+        rectanglesRelativeOpen.push_back("m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6");\r
+        rectanglesRelativeOpen.push_back("m 1,2 3,0 0,6 -3,0 0,-6");\r
+        rectanglesRelativeOpen.push_back("m 1,2 h 3 v 6 h -3 v -6");\r
+        rectanglepv.push_back(Geom::Path(Geom::Point(1,2)));\r
+        rectanglepv.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));\r
+        rectanglepv.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));\r
+        rectanglepv.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));\r
+        rectanglepv.back().append(Geom::LineSegment(Geom::Point(1,8),Geom::Point(1,2)));\r
+        // TODO: Also test some (smooth) cubic/quadratic beziers and elliptical arcs\r
+    }\r
+\r
+// createSuite and destroySuite get us per-suite setup and teardown\r
+// without us having to worry about static initialization order, etc.\r
+    static SvgPathGeomTest *createSuite() { return new SvgPathGeomTest(); }\r
+    static void destroySuite( SvgPathGeomTest *suite ) { delete suite; }\r
+\r
+    void testReadRectanglesAbsoluteClosed()\r
+    {\r
+        rectanglepv.back().close();\r
+        for(size_t i=0; i<rectanglesAbsoluteClosed.size(); i++) {\r
+            Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed[i].c_str());\r
+            TSM_ASSERT(rectanglesAbsoluteClosed[i].c_str(), bpathEqual(pv,rectanglepv));\r
+        }\r
+    }\r
+\r
+    void testReadRectanglesRelativeClosed()\r
+    {\r
+        rectanglepv.back().close();\r
+        for(size_t i=0; i<rectanglesRelativeClosed.size(); i++) {\r
+            Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed[i].c_str());\r
+            TSM_ASSERT(rectanglesRelativeClosed[i].c_str(), bpathEqual(pv,rectanglepv));\r
+        }\r
+    }\r
+\r
+    void testReadRectanglesAbsoluteOpen()\r
+    {\r
+        rectanglepv.back().close(false);\r
+        for(size_t i=0; i<rectanglesAbsoluteOpen.size(); i++) {\r
+            Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteOpen[i].c_str());\r
+            TSM_ASSERT(rectanglesAbsoluteOpen[i].c_str(), bpathEqual(pv,rectanglepv));\r
+        }\r
+    }\r
+\r
+    void testReadRectanglesRelativeOpen()\r
+    {\r
+        rectanglepv.back().close(false);\r
+        for(size_t i=0; i<rectanglesRelativeOpen.size(); i++) {\r
+            Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeOpen[i].c_str());\r
+            TSM_ASSERT(rectanglesRelativeOpen[i].c_str(), bpathEqual(pv,rectanglepv));\r
+        }\r
+    }\r
+\r
+    void testReadConcatenatedPaths()\r
+    {\r
+        Geom::PathVector pv_good;\r
+        pv_good.push_back(rectanglepv.back());\r
+        pv_good.push_back(rectanglepv.back()*Geom::Translate(pv_good[0].finalPoint()));\r
+        pv_good.push_back(rectanglepv.back()*Geom::Translate(pv_good[1].finalPoint()));\r
+        pv_good.push_back(rectanglepv.back());\r
+        pv_good[0].close();\r
+        pv_good[1].close(false);\r
+        pv_good[2].close();\r
+        pv_good[3].close(false);\r
+        std::string path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];\r
+        Geom::PathVector pv = sp_svg_read_pathv(path_str.c_str());\r
+        TS_ASSERT(bpathEqual(pv,pv_good));\r
+    }\r
+\r
+    void testReadZeroLengthSubpaths() {\r
+        // Per the SVG 1.1 specification (section F5) zero-length subpaths are relevant\r
+        Geom::PathVector pv_good;\r
+        pv_good.push_back(Geom::Path(Geom::Point(0,0)));\r
+        pv_good.push_back(Geom::Path(Geom::Point(1,1)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2)));\r
+        pv_good.push_back(Geom::Path(Geom::Point(3,3)));\r
+        pv_good.back().close();\r
+        pv_good.push_back(Geom::Path(Geom::Point(4,4)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(4,4),Geom::Point(5,5)));\r
+        pv_good.back().close();\r
+        pv_good.push_back(Geom::Path(Geom::Point(6,6)));\r
+        {   // Test absolute version\r
+            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";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+        {   // Test relative version\r
+            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";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+    }\r
+\r
+    void testReadImplicitMoveto() {\r
+        Geom::PathVector pv_good;\r
+        pv_good.push_back(Geom::Path(Geom::Point(1,1)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2)));\r
+        pv_good.back().close();\r
+        pv_good.push_back(Geom::Path(Geom::Point(1,1)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(3,3)));\r
+        pv_good.back().close();\r
+        {   // Test absolute version\r
+            char const * path_str = "M 1,1 L 2,2 z L 3,3 z";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+        {   // Test relative version\r
+            char const * path_str = "M 1,1 L 2,2 z L 3,3 z";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+    }\r
+\r
+    void testReadFloatingPoint() {\r
+        Geom::PathVector pv_good;\r
+        pv_good.push_back(Geom::Path(Geom::Point(.01,.02)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(.01,.02),Geom::Point(.04,.02)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(.04,.02),Geom::Point(1.5,1.6)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(1.5,1.6),Geom::Point(.01,.08)));\r
+        pv_good.back().close();\r
+        {   // Test decimals\r
+            char const * path_str = "M .01,.02 L.04.02 L1.5,1.6L0.01,0.08 .01.02 z";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+        {   // Test exponent\r
+            char const * path_str = "M 1e-2,.2e-1 L 0.004e1,0.0002e+2 L0150E-2,1.6e0L1.0e-2,80e-3 z";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+    }\r
+\r
+    void testReadImplicitSeparation() {\r
+        // Coordinates need not be separated by whitespace if they can still be read unambiguously\r
+        Geom::PathVector pv_good;\r
+        pv_good.push_back(Geom::Path(Geom::Point(.1,.2)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(.1,.2),Geom::Point(.4,.2)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(.4,.2),Geom::Point(.4,.8)));\r
+        pv_good.back().append(Geom::LineSegment(Geom::Point(.4,.8),Geom::Point(.1,.8)));\r
+        pv_good.back().close();\r
+        {   // Test absolute\r
+            char const * path_str = "M .1.2+0.4.2e0.4e0+8e-1.1.8 z";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+        {   // Test relative\r
+            char const * path_str = "m .1.2+0.3.0e0.0e0+6e-1-.3.0 z";\r
+            Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
+            TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
+        }\r
+    }\r
+\r
+    void testReadErrorMisplacedCharacter() {\r
+        char const * path_str;\r
+        Geom::PathVector pv;\r
+        rectanglepv.back().close();\r
+        // Comma in the wrong place (commas may only appear between parameters)\r
+        path_str = "M 1,2 4,2 4,8 1,8 z , m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Comma in the wrong place (commas may only appear between parameters)\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m,13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Period in the wrong place (no numbers after a 'z')\r
+        path_str = "M 1,2 4,2 4,8 1,8 z . m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Sign in the wrong place (no numbers after a 'z')\r
+        path_str = "M 1,2 4,2 4,8 1,8 z + - m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Digit in the wrong place (no numbers after a 'z')\r
+        path_str = "M 1,2 4,2 4,8 1,8 z 9809 m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Digit in the wrong place (no numbers after a 'z')\r
+        path_str = "M 1,2 4,2 4,8 1,8 z 9809 876 m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+    }\r
+\r
+    void testReadErrorUnrecognizedCharacter() {\r
+        char const * path_str;\r
+        Geom::PathVector pv;\r
+        rectanglepv.back().close();\r
+        // Unrecognized character\r
+        path_str = "M 1,2 4,2 4,8 1,8 z&m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Unrecognized character\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m &13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+    }\r
+\r
+    void testReadErrorTypo() {\r
+        char const * path_str;\r
+        Geom::PathVector pv;\r
+        rectanglepv.back().close();\r
+        // Typo\r
+        path_str = "M 1,2 4,2 4,8 1,8 z j 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+\r
+        rectanglepv.back().close(false);\r
+        // Typo\r
+        path_str = "M 1,2 4,2 4,8 1,8 L 1,2 x m 13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+    }\r
+\r
+    void testReadErrorIllformedNumbers() {\r
+        char const * path_str;\r
+        Geom::PathVector pv;\r
+        rectanglepv.back().close();\r
+        // Double exponent\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m 13e4e5,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Double sign\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m +-13,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Double sign\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m 13e+-12,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // No digit\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m .e12,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // No digit\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m .,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // No digit\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m +,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // No digit\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m +.e+,15";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+    }\r
+\r
+    void testReadErrorJunk() {\r
+        char const * path_str;\r
+        Geom::PathVector pv;\r
+        rectanglepv.back().close();\r
+        // Junk\r
+        path_str = "M 1,2 4,2 4,8 1,8 z j 357 hkjh.,34e34 90ih6kj4 h5k6vlh4N.,6,45wikuyi3yere..3487 m 13,23";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+    }\r
+\r
+    void testReadErrorStopReading() {\r
+        char const * path_str;\r
+        Geom::PathVector pv;\r
+        rectanglepv.back().close();\r
+        // Unrecognized parameter\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Invalid parameter\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+        // Illformed parameter\r
+        path_str = "M 1,2 4,2 4,8 1,8 z m +-12,23,34";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+\r
+        rectanglepv.back().close(false);\r
+        // "Third" parameter\r
+        path_str = "M 1,2 4,2 4,8 1,8 1,2,3 M 12,23";\r
+        pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(path_str, bpathEqual(pv,rectanglepv));\r
+    }\r
+\r
+    void testRoundTrip() {\r
+        // This is the easiest way to (also) test writing path data, as a path can be written in more than one way.\r
+        Geom::PathVector pv;\r
+        Geom::PathVector new_pv;\r
+        std::string org_path_str;\r
+        char * path_str;\r
+        // Rectangle (closed)\r
+        org_path_str = rectanglesAbsoluteClosed[0];\r
+        pv = sp_svg_read_pathv(org_path_str.c_str());\r
+        path_str = sp_svg_write_path(pv);\r
+        new_pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
+        g_free(path_str);\r
+        // Rectangle (open)\r
+        org_path_str = rectanglesAbsoluteOpen[0];\r
+        pv = sp_svg_read_pathv(org_path_str.c_str());\r
+        path_str = sp_svg_write_path(pv);\r
+        new_pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
+        g_free(path_str);\r
+        // Concatenated rectangles\r
+        org_path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];\r
+        pv = sp_svg_read_pathv(org_path_str.c_str());\r
+        path_str = sp_svg_write_path(pv);\r
+        new_pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
+        g_free(path_str);\r
+        // Zero-length subpaths\r
+        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";\r
+        pv = sp_svg_read_pathv(org_path_str.c_str());\r
+        path_str = sp_svg_write_path(pv);\r
+        new_pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
+        g_free(path_str);\r
+        // Floating-point\r
+        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";\r
+        pv = sp_svg_read_pathv(org_path_str.c_str());\r
+        path_str = sp_svg_write_path(pv);\r
+        new_pv = sp_svg_read_pathv(path_str);\r
+        TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv, new_pv, 1e-17));\r
+        g_free(path_str);\r
+    }\r
+\r
+    void testMinexpPrecision() {\r
+        Geom::PathVector pv;\r
+        char * path_str;\r
+        // Default values\r
+        prefs_set_int_attribute("options.svgoutput", "allowrelativecoordinates", 1);\r
+        prefs_set_int_attribute("options.svgoutput", "forcerepeatcommands", 0);\r
+        prefs_set_int_attribute("options.svgoutput", "numericprecision", 8);\r
+        prefs_set_int_attribute("options.svgoutput", "minimumexponent", -8);\r
+        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");\r
+        path_str = sp_svg_write_path(pv);\r
+        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 );\r
+        g_free(path_str);\r
+    }\r
+\r
+private:\r
+    bool bpathEqual(Geom::PathVector const &a, Geom::PathVector const &b, double eps = 1e-16) {\r
+        if (a.size() != b.size()) {\r
+            char temp[100];\r
+            sprintf(temp, "PathVectors not the same size. (%u != %u)", a.size(), b.size());\r
+            TS_FAIL(temp);\r
+            return false;\r
+        }\r
+        for(size_t i=0; i<a.size(); i++) {\r
+            Geom::Path const &pa = a[i];\r
+            Geom::Path const &pb = b[i];\r
+            if (pa.closed() != pb.closed()) {\r
+                TS_FAIL("One path is closed, the other open.");\r
+                return false;\r
+            }\r
+            if (pa.size() != pb.size()) {\r
+                char temp[100];\r
+                sprintf(temp, "Not the same number of curves in path. (%u != %u)", pa.size(), pb.size());\r
+                TS_FAIL(temp);\r
+                return false;\r
+            }\r
+            for(size_t j=0; j<pa.size(); j++) {\r
+                Geom::Curve const* ca = &pa[j];\r
+                Geom::Curve const* cb = &pb[j];\r
+                if (typeid(*ca) == typeid(*cb))\r
+                {\r
+                    if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))\r
+                    {\r
+                        Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb);\r
+                        if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la)[0][Geom::X], (*la)[0][Geom::Y], (*lb)[0][Geom::X], (*lb)[0][Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                        if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                    }\r
+                    else if(Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca))\r
+                    {\r
+                        Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb);\r
+                        if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                        if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                    }\r
+                    else if(Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca))\r
+                    {\r
+                        Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb);\r
+                        if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                        if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                    }\r
+                    else if(Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const*>(ca))\r
+                    {\r
+                        Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const*>(cb);\r
+                        if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la)[0][Geom::X], (*la)[0][Geom::Y], (*lb)[0][Geom::X], (*lb)[0][Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                        if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different 1st control point: (%g,%g) != (%g,%g)", (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                        if (!Geom::are_near((*la)[2],(*lb)[2], eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different 2nd control point: (%g,%g) != (%g,%g)", (*la)[2][Geom::X], (*la)[2][Geom::Y], (*lb)[2][Geom::X], (*lb)[2][Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                        if (!Geom::are_near((*la)[3],(*lb)[3], eps)) {\r
+                            char temp[200];\r
+                            sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la)[3][Geom::X], (*la)[3][Geom::Y], (*lb)[3][Geom::X], (*lb)[3][Geom::Y]);\r
+                            TS_FAIL(temp);\r
+                            return false;\r
+                        }\r
+                    }\r
+                    else\r
+                    {\r
+                        TS_FAIL((std::string("Unknown curve type: ") + typeid(*ca).name()).c_str());\r
+                        return false;\r
+                    }\r
+                }\r
+                else // not same type\r
+                {\r
+                    if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))\r
+                    {\r
+                        if (Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb)) {\r
+                            if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                            if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                        } else if (Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb)) {\r
+                            if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                            if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                        } else {\r
+                            TS_FAIL((std::string("Different curve types: ") + typeid(*ca).name() + " != " + typeid(*cb).name()).c_str());\r
+                            return false;\r
+                        }\r
+                    }\r
+                    else if(Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb))\r
+                    {\r
+                        if (Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca)) {\r
+                            if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                            if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                        } else if (Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca)) {\r
+                            if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g)", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                            if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
+                                char temp[200];\r
+                                sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g)", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y]);\r
+                                TS_FAIL(temp);\r
+                                return false;\r
+                            }\r
+                        } else {\r
+                            TS_FAIL((std::string("Different curve types: ") + typeid(*ca).name() + " != " + typeid(*cb).name()).c_str());\r
+                            return false;\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+};\r
+\r
+\r
+/*\r
+  Local Variables:\r
+  mode:c++\r
+  c-file-style:"stroustrup"\r
+  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
+  indent-tabs-mode:nil\r
+  fill-column:99\r
+  End:\r
+*/\r
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
diff --git a/src/svg/svg-path-nr-test.h b/src/svg/svg-path-nr-test.h
new file mode 100644 (file)
index 0000000..df9c460
--- /dev/null
@@ -0,0 +1,493 @@
+#include <cxxtest/TestSuite.h>
+#include "libnr/n-art-bpath.h"
+#include "svg/svg.h"
+#include "2geom/coord.h"
+#include "prefs-utils.h"
+#include "streq.h"
+#include <string>
+#include <vector>
+#include <glib/gmem.h>
+
+class SvgPathNRTest : public CxxTest::TestSuite
+{
+private:
+    std::vector<std::string> rectanglesAbsoluteClosed;
+    std::vector<std::string> rectanglesRelativeClosed;
+    std::vector<std::string> rectanglesAbsoluteOpen;
+    std::vector<std::string> rectanglesRelativeOpen;
+    NArtBpath rectangleBpath[5+1];
+public:
+    SvgPathNRTest() {
+        // Lots of ways to define the same rectangle
+        rectanglesAbsoluteClosed.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2 Z");
+        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");
+        rectangleBpath[0].code = NR_MOVETO;
+        rectangleBpath[0].x3 = 1;
+        rectangleBpath[0].y3 = 2;
+        rectangleBpath[1].code = NR_LINETO;
+        rectangleBpath[1].x3 = 4;
+        rectangleBpath[1].y3 = 2;
+        rectangleBpath[2].code = NR_LINETO;
+        rectangleBpath[2].x3 = 4;
+        rectangleBpath[2].y3 = 8;
+        rectangleBpath[3].code = NR_LINETO;
+        rectangleBpath[3].x3 = 1;
+        rectangleBpath[3].y3 = 8;
+        rectangleBpath[4].code = NR_LINETO;
+        rectangleBpath[4].x3 = 1;
+        rectangleBpath[4].y3 = 2;
+        rectangleBpath[5].code = NR_END;
+        // TODO: Also test some (smooth) cubic/quadratic beziers and elliptical arcs
+    }
+
+// createSuite and destroySuite get us per-suite setup and teardown
+// without us having to worry about static initialization order, etc.
+    static SvgPathNRTest *createSuite() { return new SvgPathNRTest(); }
+    static void destroySuite( SvgPathNRTest *suite ) { delete suite; }
+
+    void testReadRectanglesAbsoluteClosed()
+    {
+        rectangleBpath[0].code = NR_MOVETO;
+        for(size_t i=0; i<rectanglesAbsoluteClosed.size(); i++) {
+            NArtBpath * bpath = sp_svg_read_path(rectanglesAbsoluteClosed[i].c_str());
+            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
+            g_free(bpath);
+        }
+    }
+
+    void testReadRectanglesRelativeClosed()
+    {
+        rectangleBpath[0].code = NR_MOVETO;
+        for(size_t i=0; i<rectanglesRelativeClosed.size(); i++) {
+            NArtBpath * bpath = sp_svg_read_path(rectanglesRelativeClosed[i].c_str());
+            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
+            g_free(bpath);
+        }
+    }
+
+    void testReadRectanglesAbsoluteOpen()
+    {
+        rectangleBpath[0].code = NR_MOVETO_OPEN;
+        for(size_t i=0; i<rectanglesAbsoluteOpen.size(); i++) {
+            NArtBpath * bpath = sp_svg_read_path(rectanglesAbsoluteOpen[i].c_str());
+            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
+            g_free(bpath);
+        }
+    }
+
+    void testReadRectanglesRelativeOpen()
+    {
+        rectangleBpath[0].code = NR_MOVETO_OPEN;
+        for(size_t i=0; i<rectanglesRelativeOpen.size(); i++) {
+            NArtBpath * bpath = sp_svg_read_path(rectanglesRelativeOpen[i].c_str());
+            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
+            g_free(bpath);
+        }
+    }
+
+    void testReadConcatenatedPaths()
+    {
+        NArtBpath bpath_good[4*5+1];
+        for(size_t i=0; i<4; i++) {
+            memcpy(bpath_good+i*5,rectangleBpath,sizeof(rectangleBpath[0])*5);
+        }
+        bpath_good[0*5].code = NR_MOVETO;
+        bpath_good[1*5].code = NR_MOVETO_OPEN;
+        bpath_good[2*5].code = NR_MOVETO;
+        bpath_good[3*5].code = NR_MOVETO_OPEN;
+        bpath_good[4*5].code = NR_END;
+        for(size_t i=0; i<5; i++) {
+            bpath_good[1*5+i].x3 += bpath_good[0*5+4].x3;
+            bpath_good[1*5+i].y3 += bpath_good[0*5+4].y3;
+        }
+        for(size_t i=0; i<5; i++) {
+            bpath_good[2*5+i].x3 += bpath_good[1*5+4].x3;
+            bpath_good[2*5+i].y3 += bpath_good[1*5+4].y3;
+        }
+        std::string path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];
+        NArtBpath * bpath = sp_svg_read_path(path_str.c_str());
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testReadZeroLengthSubpaths() {
+        // Per the SVG 1.1 specification (section F5) zero-length subpaths are relevant
+        NArtBpath bpath_good[8+1];
+        bpath_good[0].code = NR_MOVETO_OPEN;
+        bpath_good[0].x3 = bpath_good[0].y3 = 0;
+        bpath_good[1].code = NR_MOVETO_OPEN;
+        bpath_good[1].x3 = bpath_good[1].y3 = 1;
+        bpath_good[2].code = NR_LINETO;
+        bpath_good[2].x3 = bpath_good[2].y3 = 2;
+        bpath_good[3].code = NR_MOVETO;
+        bpath_good[3].x3 = bpath_good[3].y3 = 3;
+        bpath_good[4].code = NR_MOVETO;
+        bpath_good[4].x3 = bpath_good[4].y3 = 4;
+        bpath_good[5].code = NR_LINETO;
+        bpath_good[5].x3 = bpath_good[5].y3 = 5;
+        bpath_good[6].code = NR_LINETO;
+        bpath_good[6].x3 = bpath_good[6].y3 = 4;
+        bpath_good[7].code = NR_MOVETO_OPEN;
+        bpath_good[7].x3 = bpath_good[7].y3 = 6;
+        bpath_good[8].code = NR_END;
+        {   // 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";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+        {   // 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";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+    }
+
+    void testReadImplicitMoveto() {
+        NArtBpath bpath_good[6+1];
+        bpath_good[0].code = NR_MOVETO;
+        bpath_good[0].x3 = bpath_good[0].y3 = 1;
+        bpath_good[1].code = NR_LINETO;
+        bpath_good[1].x3 = bpath_good[1].y3 = 2;
+        bpath_good[2].code = NR_LINETO;
+        bpath_good[2].x3 = bpath_good[2].y3 = 1;
+        bpath_good[3].code = NR_MOVETO;
+        bpath_good[3].x3 = bpath_good[3].y3 = 1;
+        bpath_good[4].code = NR_LINETO;
+        bpath_good[4].x3 = bpath_good[4].y3 = 3;
+        bpath_good[5].code = NR_LINETO;
+        bpath_good[5].x3 = bpath_good[5].y3 = 1;
+        bpath_good[6].code = NR_END;
+        {   // Test absolute version
+            char const * path_str = "M 1,1 L 2,2 z L 3,3 z";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+        {   // Test relative version
+            char const * path_str = "M 1,1 L 2,2 z L 3,3 z";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+    }
+
+    void testReadFloatingPoint() {
+        NArtBpath bpath_good[5+1];
+        bpath_good[0].code = NR_MOVETO;
+        bpath_good[0].x3 = .01;
+        bpath_good[0].y3 = .02;
+        bpath_good[1].code = NR_LINETO;
+        bpath_good[1].x3 = .04;
+        bpath_good[1].y3 = .02;
+        bpath_good[2].code = NR_LINETO;
+        bpath_good[2].x3 = 1.5;
+        bpath_good[2].y3 = 1.6;
+        bpath_good[3].code = NR_LINETO;
+        bpath_good[3].x3 = .01;
+        bpath_good[3].y3 = .08;
+        bpath_good[4].code = NR_LINETO;
+        bpath_good[4].x3 = .01;
+        bpath_good[4].y3 = .02;
+        bpath_good[5].code = NR_END;
+        {   // Test decimals
+            char const * path_str = "M .01,.02 L.04.02 L1.5,1.6L0.01,0.08 .01.02 z";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+        {   // 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";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+    }
+
+    void testReadImplicitSeparation() {
+        // Coordinates need not be separated by whitespace if they can still be read unambiguously
+        NArtBpath bpath_good[5+1];
+        bpath_good[0].code = NR_MOVETO;
+        bpath_good[0].x3 = .1;
+        bpath_good[0].y3 = .2;
+        bpath_good[1].code = NR_LINETO;
+        bpath_good[1].x3 = .4;
+        bpath_good[1].y3 = .2;
+        bpath_good[2].code = NR_LINETO;
+        bpath_good[2].x3 = .4;
+        bpath_good[2].y3 = .8;
+        bpath_good[3].code = NR_LINETO;
+        bpath_good[3].x3 = .1;
+        bpath_good[3].y3 = .8;
+        bpath_good[4].code = NR_LINETO;
+        bpath_good[4].x3 = .1;
+        bpath_good[4].y3 = .2;
+        bpath_good[5].code = NR_END;
+        {   // Test absolute
+            char const * path_str = "M .1.2+0.4.2e0.4e0+8e-1.1.8 z";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+        {   // Test relative
+            char const * path_str = "m .1.2+0.3.0e0.0e0+6e-1-.3.0 z";
+            NArtBpath * bpath = sp_svg_read_path(path_str);
+            TS_ASSERT(bpathEqual(bpath,bpath_good));
+            g_free(bpath);
+        }
+    }
+
+    void testReadErrorMisplacedCharacter() {
+        char const * path_str;
+        NArtBpath * bpath;
+        NArtBpath * bpath_good = rectangleBpath;
+        bpath_good[0].code = NR_MOVETO;
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testReadErrorUnrecognizedCharacter() {
+        char const * path_str;
+        NArtBpath * bpath;
+        NArtBpath * bpath_good = rectangleBpath;
+        bpath_good[0].code = NR_MOVETO;
+        // Unrecognized character
+        path_str = "M 1,2 4,2 4,8 1,8 z&m 13,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // Unrecognized character
+        path_str = "M 1,2 4,2 4,8 1,8 z m &13,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testReadErrorTypo() {
+        char const * path_str;
+        NArtBpath * bpath;
+        NArtBpath * bpath_good = rectangleBpath;
+        bpath_good[0].code = NR_MOVETO;
+        // Typo
+        path_str = "M 1,2 4,2 4,8 1,8 z j 13,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+
+        bpath_good[0].code = NR_MOVETO_OPEN;
+        // Typo
+        path_str = "M 1,2 4,2 4,8 1,8 L 1,2 x m 13,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testReadErrorIllformedNumbers() {
+        char const * path_str;
+        NArtBpath * bpath;
+        NArtBpath * bpath_good = rectangleBpath;
+        bpath_good[0].code = NR_MOVETO;
+        // Double exponent
+        path_str = "M 1,2 4,2 4,8 1,8 z m 13e4e5,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // Double sign
+        path_str = "M 1,2 4,2 4,8 1,8 z m +-13,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // Double sign
+        path_str = "M 1,2 4,2 4,8 1,8 z m 13e+-12,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // No digit
+        path_str = "M 1,2 4,2 4,8 1,8 z m .e12,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // No digit
+        path_str = "M 1,2 4,2 4,8 1,8 z m .,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // No digit
+        path_str = "M 1,2 4,2 4,8 1,8 z m +,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // No digit
+        path_str = "M 1,2 4,2 4,8 1,8 z m +.e+,15";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testReadErrorJunk() {
+        char const * path_str;
+        NArtBpath * bpath;
+        NArtBpath * bpath_good = rectangleBpath;
+        bpath_good[0].code = NR_MOVETO;
+        // 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";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testReadErrorStopReading() {
+        char const * path_str;
+        NArtBpath * bpath;
+        NArtBpath * bpath_good = rectangleBpath;
+        bpath_good[0].code = NR_MOVETO;
+        // Unrecognized parameter
+        path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // Invalid parameter
+        path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+        // Illformed parameter
+        path_str = "M 1,2 4,2 4,8 1,8 z m +-12,23,34";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+
+        bpath_good[0].code = NR_MOVETO_OPEN;
+        // "Third" parameter
+        path_str = "M 1,2 4,2 4,8 1,8 1,2,3 M 12,23";
+        bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,bpath_good));
+        g_free(bpath);
+    }
+
+    void testRoundTrip() {
+        // This is the easiest way to (also) test writing path data, as a path can be written in more than one way.
+        NArtBpath * bpath;
+        NArtBpath * new_bpath;
+        char * path_str;
+        // Rectangle (closed)
+        bpath = sp_svg_read_path(rectanglesAbsoluteClosed[0].c_str());
+        path_str = sp_svg_write_path(bpath);
+        new_bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,new_bpath));
+        g_free(bpath); g_free(path_str); g_free(new_bpath);
+        // Rectangle (open)
+        bpath = sp_svg_read_path(rectanglesAbsoluteOpen[0].c_str());
+        path_str = sp_svg_write_path(bpath);
+        new_bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,new_bpath));
+        g_free(bpath); g_free(path_str); g_free(new_bpath);
+        // Concatenated rectangles
+        bpath = sp_svg_read_path((rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0]).c_str());
+        path_str = sp_svg_write_path(bpath);
+        new_bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,new_bpath));
+        g_free(bpath); g_free(path_str); g_free(new_bpath);
+        // Zero-length subpaths
+        bpath = sp_svg_read_path("M 0,0 M 1,1 L 2,2 M 3,3 z M 4,4 L 5,5 z M 6,6");
+        path_str = sp_svg_write_path(bpath);
+        new_bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath,new_bpath));
+        g_free(bpath); g_free(path_str); g_free(new_bpath);
+        // Floating-point
+        bpath = sp_svg_read_path("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");
+        path_str = sp_svg_write_path(bpath);
+        new_bpath = sp_svg_read_path(path_str);
+        TS_ASSERT(bpathEqual(bpath, new_bpath, 1e-17));
+        g_free(bpath); g_free(path_str); g_free(new_bpath);
+    }
+
+    void testMinexpPrecision() {
+        NArtBpath * bpath;
+        char * path_str;
+        // Default values
+        prefs_set_int_attribute("options.svgoutput", "allowrelativecoordinates", 1);
+        prefs_set_int_attribute("options.svgoutput", "forcerepeatcommands", 0);
+        prefs_set_int_attribute("options.svgoutput", "numericprecision", 8);
+        prefs_set_int_attribute("options.svgoutput", "minimumexponent", -8);
+        bpath = sp_svg_read_path("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(bpath);
+        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(bpath); g_free(path_str);
+    }
+
+private:
+    bool bpathEqual(NArtBpath const * a, NArtBpath const * b, double eps = 1e-16) {
+        while(a->code != NR_END && b->code == a->code) {
+            switch(a->code) {
+            case NR_MOVETO:
+            case NR_MOVETO_OPEN:
+            case NR_LINETO:
+                if (!Geom::are_near(a->x3,b->x3, eps) || !Geom::are_near(a->y3,b->y3, eps)) return false;
+                break;
+            case NR_CURVETO:
+                if (!Geom::are_near(a->x1,b->x1, eps) || !Geom::are_near(a->y1,b->y1, eps)) return false;
+                if (!Geom::are_near(a->x2,b->x2, eps) || !Geom::are_near(a->y2,b->y2, eps)) return false;
+                if (!Geom::are_near(a->x3,b->x3, eps) || !Geom::are_near(a->y3,b->y3, eps)) return false;
+                break;
+            default:
+                TS_FAIL("Unknown path code!");
+            }
+            a++;
+            b++;
+        }
+        return a->code == b->code;
+    }
+};
+
+
+/*
+  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:encoding=utf-8:textwidth=99 :
diff --git a/src/svg/svg-path-test.h b/src/svg/svg-path-test.h
deleted file mode 100644 (file)
index 28173cb..0000000
+++ /dev/null
@@ -1,493 +0,0 @@
-#include <cxxtest/TestSuite.h>
-#include "libnr/n-art-bpath.h"
-#include "svg/svg.h"
-#include "2geom/coord.h"
-#include "prefs-utils.h"
-#include "streq.h"
-#include <string>
-#include <vector>
-#include <glib/gmem.h>
-
-class SvgPathTest : public CxxTest::TestSuite
-{
-private:
-    std::vector<std::string> rectanglesAbsoluteClosed;
-    std::vector<std::string> rectanglesRelativeClosed;
-    std::vector<std::string> rectanglesAbsoluteOpen;
-    std::vector<std::string> rectanglesRelativeOpen;
-    NArtBpath rectangleBpath[5+1];
-public:
-    SvgPathTest() {
-        // Lots of ways to define the same rectangle
-        rectanglesAbsoluteClosed.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2 Z");
-        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");
-        rectangleBpath[0].code = NR_MOVETO;
-        rectangleBpath[0].x3 = 1;
-        rectangleBpath[0].y3 = 2;
-        rectangleBpath[1].code = NR_LINETO;
-        rectangleBpath[1].x3 = 4;
-        rectangleBpath[1].y3 = 2;
-        rectangleBpath[2].code = NR_LINETO;
-        rectangleBpath[2].x3 = 4;
-        rectangleBpath[2].y3 = 8;
-        rectangleBpath[3].code = NR_LINETO;
-        rectangleBpath[3].x3 = 1;
-        rectangleBpath[3].y3 = 8;
-        rectangleBpath[4].code = NR_LINETO;
-        rectangleBpath[4].x3 = 1;
-        rectangleBpath[4].y3 = 2;
-        rectangleBpath[5].code = NR_END;
-        // TODO: Also test some (smooth) cubic/quadratic beziers and elliptical arcs
-    }
-
-// createSuite and destroySuite get us per-suite setup and teardown
-// without us having to worry about static initialization order, etc.
-    static SvgPathTest *createSuite() { return new SvgPathTest(); }
-    static void destroySuite( SvgPathTest *suite ) { delete suite; }
-
-    void testReadRectanglesAbsoluteClosed()
-    {
-        rectangleBpath[0].code = NR_MOVETO;
-        for(size_t i=0; i<rectanglesAbsoluteClosed.size(); i++) {
-            NArtBpath * bpath = sp_svg_read_path(rectanglesAbsoluteClosed[i].c_str());
-            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
-            g_free(bpath);
-        }
-    }
-
-    void testReadRectanglesRelativeClosed()
-    {
-        rectangleBpath[0].code = NR_MOVETO;
-        for(size_t i=0; i<rectanglesRelativeClosed.size(); i++) {
-            NArtBpath * bpath = sp_svg_read_path(rectanglesRelativeClosed[i].c_str());
-            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
-            g_free(bpath);
-        }
-    }
-
-    void testReadRectanglesAbsoluteOpen()
-    {
-        rectangleBpath[0].code = NR_MOVETO_OPEN;
-        for(size_t i=0; i<rectanglesAbsoluteOpen.size(); i++) {
-            NArtBpath * bpath = sp_svg_read_path(rectanglesAbsoluteOpen[i].c_str());
-            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
-            g_free(bpath);
-        }
-    }
-
-    void testReadRectanglesRelativeOpen()
-    {
-        rectangleBpath[0].code = NR_MOVETO_OPEN;
-        for(size_t i=0; i<rectanglesRelativeOpen.size(); i++) {
-            NArtBpath * bpath = sp_svg_read_path(rectanglesRelativeOpen[i].c_str());
-            TS_ASSERT(bpathEqual(bpath,rectangleBpath));
-            g_free(bpath);
-        }
-    }
-
-    void testReadConcatenatedPaths()
-    {
-        NArtBpath bpath_good[4*5+1];
-        for(size_t i=0; i<4; i++) {
-            memcpy(bpath_good+i*5,rectangleBpath,sizeof(rectangleBpath[0])*5);
-        }
-        bpath_good[0*5].code = NR_MOVETO;
-        bpath_good[1*5].code = NR_MOVETO_OPEN;
-        bpath_good[2*5].code = NR_MOVETO;
-        bpath_good[3*5].code = NR_MOVETO_OPEN;
-        bpath_good[4*5].code = NR_END;
-        for(size_t i=0; i<5; i++) {
-            bpath_good[1*5+i].x3 += bpath_good[0*5+4].x3;
-            bpath_good[1*5+i].y3 += bpath_good[0*5+4].y3;
-        }
-        for(size_t i=0; i<5; i++) {
-            bpath_good[2*5+i].x3 += bpath_good[1*5+4].x3;
-            bpath_good[2*5+i].y3 += bpath_good[1*5+4].y3;
-        }
-        std::string path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];
-        NArtBpath * bpath = sp_svg_read_path(path_str.c_str());
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testReadZeroLengthSubpaths() {
-        // Per the SVG 1.1 specification (section F5) zero-length subpaths are relevant
-        NArtBpath bpath_good[8+1];
-        bpath_good[0].code = NR_MOVETO_OPEN;
-        bpath_good[0].x3 = bpath_good[0].y3 = 0;
-        bpath_good[1].code = NR_MOVETO_OPEN;
-        bpath_good[1].x3 = bpath_good[1].y3 = 1;
-        bpath_good[2].code = NR_LINETO;
-        bpath_good[2].x3 = bpath_good[2].y3 = 2;
-        bpath_good[3].code = NR_MOVETO;
-        bpath_good[3].x3 = bpath_good[3].y3 = 3;
-        bpath_good[4].code = NR_MOVETO;
-        bpath_good[4].x3 = bpath_good[4].y3 = 4;
-        bpath_good[5].code = NR_LINETO;
-        bpath_good[5].x3 = bpath_good[5].y3 = 5;
-        bpath_good[6].code = NR_LINETO;
-        bpath_good[6].x3 = bpath_good[6].y3 = 4;
-        bpath_good[7].code = NR_MOVETO_OPEN;
-        bpath_good[7].x3 = bpath_good[7].y3 = 6;
-        bpath_good[8].code = NR_END;
-        {   // 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";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-        {   // 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";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-    }
-
-    void testReadImplicitMoveto() {
-        NArtBpath bpath_good[6+1];
-        bpath_good[0].code = NR_MOVETO;
-        bpath_good[0].x3 = bpath_good[0].y3 = 1;
-        bpath_good[1].code = NR_LINETO;
-        bpath_good[1].x3 = bpath_good[1].y3 = 2;
-        bpath_good[2].code = NR_LINETO;
-        bpath_good[2].x3 = bpath_good[2].y3 = 1;
-        bpath_good[3].code = NR_MOVETO;
-        bpath_good[3].x3 = bpath_good[3].y3 = 1;
-        bpath_good[4].code = NR_LINETO;
-        bpath_good[4].x3 = bpath_good[4].y3 = 3;
-        bpath_good[5].code = NR_LINETO;
-        bpath_good[5].x3 = bpath_good[5].y3 = 1;
-        bpath_good[6].code = NR_END;
-        {   // Test absolute version
-            char const * path_str = "M 1,1 L 2,2 z L 3,3 z";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-        {   // Test relative version
-            char const * path_str = "M 1,1 L 2,2 z L 3,3 z";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-    }
-
-    void testReadFloatingPoint() {
-        NArtBpath bpath_good[5+1];
-        bpath_good[0].code = NR_MOVETO;
-        bpath_good[0].x3 = .01;
-        bpath_good[0].y3 = .02;
-        bpath_good[1].code = NR_LINETO;
-        bpath_good[1].x3 = .04;
-        bpath_good[1].y3 = .02;
-        bpath_good[2].code = NR_LINETO;
-        bpath_good[2].x3 = 1.5;
-        bpath_good[2].y3 = 1.6;
-        bpath_good[3].code = NR_LINETO;
-        bpath_good[3].x3 = .01;
-        bpath_good[3].y3 = .08;
-        bpath_good[4].code = NR_LINETO;
-        bpath_good[4].x3 = .01;
-        bpath_good[4].y3 = .02;
-        bpath_good[5].code = NR_END;
-        {   // Test decimals
-            char const * path_str = "M .01,.02 L.04.02 L1.5,1.6L0.01,0.08 .01.02 z";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-        {   // 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";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-    }
-
-    void testReadImplicitSeparation() {
-        // Coordinates need not be separated by whitespace if they can still be read unambiguously
-        NArtBpath bpath_good[5+1];
-        bpath_good[0].code = NR_MOVETO;
-        bpath_good[0].x3 = .1;
-        bpath_good[0].y3 = .2;
-        bpath_good[1].code = NR_LINETO;
-        bpath_good[1].x3 = .4;
-        bpath_good[1].y3 = .2;
-        bpath_good[2].code = NR_LINETO;
-        bpath_good[2].x3 = .4;
-        bpath_good[2].y3 = .8;
-        bpath_good[3].code = NR_LINETO;
-        bpath_good[3].x3 = .1;
-        bpath_good[3].y3 = .8;
-        bpath_good[4].code = NR_LINETO;
-        bpath_good[4].x3 = .1;
-        bpath_good[4].y3 = .2;
-        bpath_good[5].code = NR_END;
-        {   // Test absolute
-            char const * path_str = "M .1.2+0.4.2e0.4e0+8e-1.1.8 z";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-        {   // Test relative
-            char const * path_str = "m .1.2+0.3.0e0.0e0+6e-1-.3.0 z";
-            NArtBpath * bpath = sp_svg_read_path(path_str);
-            TS_ASSERT(bpathEqual(bpath,bpath_good));
-            g_free(bpath);
-        }
-    }
-
-    void testReadErrorMisplacedCharacter() {
-        char const * path_str;
-        NArtBpath * bpath;
-        NArtBpath * bpath_good = rectangleBpath;
-        bpath_good[0].code = NR_MOVETO;
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testReadErrorUnrecognizedCharacter() {
-        char const * path_str;
-        NArtBpath * bpath;
-        NArtBpath * bpath_good = rectangleBpath;
-        bpath_good[0].code = NR_MOVETO;
-        // Unrecognized character
-        path_str = "M 1,2 4,2 4,8 1,8 z&m 13,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // Unrecognized character
-        path_str = "M 1,2 4,2 4,8 1,8 z m &13,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testReadErrorTypo() {
-        char const * path_str;
-        NArtBpath * bpath;
-        NArtBpath * bpath_good = rectangleBpath;
-        bpath_good[0].code = NR_MOVETO;
-        // Typo
-        path_str = "M 1,2 4,2 4,8 1,8 z j 13,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-
-        bpath_good[0].code = NR_MOVETO_OPEN;
-        // Typo
-        path_str = "M 1,2 4,2 4,8 1,8 L 1,2 x m 13,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testReadErrorIllformedNumbers() {
-        char const * path_str;
-        NArtBpath * bpath;
-        NArtBpath * bpath_good = rectangleBpath;
-        bpath_good[0].code = NR_MOVETO;
-        // Double exponent
-        path_str = "M 1,2 4,2 4,8 1,8 z m 13e4e5,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // Double sign
-        path_str = "M 1,2 4,2 4,8 1,8 z m +-13,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // Double sign
-        path_str = "M 1,2 4,2 4,8 1,8 z m 13e+-12,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // No digit
-        path_str = "M 1,2 4,2 4,8 1,8 z m .e12,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // No digit
-        path_str = "M 1,2 4,2 4,8 1,8 z m .,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // No digit
-        path_str = "M 1,2 4,2 4,8 1,8 z m +,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // No digit
-        path_str = "M 1,2 4,2 4,8 1,8 z m +.e+,15";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testReadErrorJunk() {
-        char const * path_str;
-        NArtBpath * bpath;
-        NArtBpath * bpath_good = rectangleBpath;
-        bpath_good[0].code = NR_MOVETO;
-        // 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";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testReadErrorStopReading() {
-        char const * path_str;
-        NArtBpath * bpath;
-        NArtBpath * bpath_good = rectangleBpath;
-        bpath_good[0].code = NR_MOVETO;
-        // Unrecognized parameter
-        path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // Invalid parameter
-        path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-        // Illformed parameter
-        path_str = "M 1,2 4,2 4,8 1,8 z m +-12,23,34";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-
-        bpath_good[0].code = NR_MOVETO_OPEN;
-        // "Third" parameter
-        path_str = "M 1,2 4,2 4,8 1,8 1,2,3 M 12,23";
-        bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,bpath_good));
-        g_free(bpath);
-    }
-
-    void testRoundTrip() {
-        // This is the easiest way to (also) test writing path data, as a path can be written in more than one way.
-        NArtBpath * bpath;
-        NArtBpath * new_bpath;
-        char * path_str;
-        // Rectangle (closed)
-        bpath = sp_svg_read_path(rectanglesAbsoluteClosed[0].c_str());
-        path_str = sp_svg_write_path(bpath);
-        new_bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,new_bpath));
-        g_free(bpath); g_free(path_str); g_free(new_bpath);
-        // Rectangle (open)
-        bpath = sp_svg_read_path(rectanglesAbsoluteOpen[0].c_str());
-        path_str = sp_svg_write_path(bpath);
-        new_bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,new_bpath));
-        g_free(bpath); g_free(path_str); g_free(new_bpath);
-        // Concatenated rectangles
-        bpath = sp_svg_read_path((rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0]).c_str());
-        path_str = sp_svg_write_path(bpath);
-        new_bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,new_bpath));
-        g_free(bpath); g_free(path_str); g_free(new_bpath);
-        // Zero-length subpaths
-        bpath = sp_svg_read_path("M 0,0 M 1,1 L 2,2 M 3,3 z M 4,4 L 5,5 z M 6,6");
-        path_str = sp_svg_write_path(bpath);
-        new_bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath,new_bpath));
-        g_free(bpath); g_free(path_str); g_free(new_bpath);
-        // Floating-point
-        bpath = sp_svg_read_path("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");
-        path_str = sp_svg_write_path(bpath);
-        new_bpath = sp_svg_read_path(path_str);
-        TS_ASSERT(bpathEqual(bpath, new_bpath, 1e-17));
-        g_free(bpath); g_free(path_str); g_free(new_bpath);
-    }
-
-    void testMinexpPrecision() {
-        NArtBpath * bpath;
-        char * path_str;
-        // Default values
-        prefs_set_int_attribute("options.svgoutput", "allowrelativecoordinates", 1);
-        prefs_set_int_attribute("options.svgoutput", "forcerepeatcommands", 0);
-        prefs_set_int_attribute("options.svgoutput", "numericprecision", 8);
-        prefs_set_int_attribute("options.svgoutput", "minimumexponent", -8);
-        bpath = sp_svg_read_path("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(bpath);
-        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(bpath); g_free(path_str);
-    }
-
-private:
-    bool bpathEqual(NArtBpath const * a, NArtBpath const * b, double eps = 1e-16) {
-        while(a->code != NR_END && b->code == a->code) {
-            switch(a->code) {
-            case NR_MOVETO:
-            case NR_MOVETO_OPEN:
-            case NR_LINETO:
-                if (!Geom::are_near(a->x3,b->x3, eps) || !Geom::are_near(a->y3,b->y3, eps)) return false;
-                break;
-            case NR_CURVETO:
-                if (!Geom::are_near(a->x1,b->x1, eps) || !Geom::are_near(a->y1,b->y1, eps)) return false;
-                if (!Geom::are_near(a->x2,b->x2, eps) || !Geom::are_near(a->y2,b->y2, eps)) return false;
-                if (!Geom::are_near(a->x3,b->x3, eps) || !Geom::are_near(a->y3,b->y3, eps)) return false;
-                break;
-            default:
-                TS_FAIL("Unknown path code!");
-            }
-            a++;
-            b++;
-        }
-        return a->code == b->code;
-    }
-};
-
-
-/*
-  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:encoding=utf-8:textwidth=99 :