Code

32a2ed2319d6162262da7246fbaf85ca0095b091
[inkscape.git] / src / svg / svg-path-geom-test.h
1 #include <cxxtest/TestSuite.h>\r
2 #include "2geom/coord.h"\r
3 #include "2geom/curves.h"\r
4 #include "2geom/pathvector.h"\r
5 #include "svg/svg.h"\r
6 #include "prefs-utils.h"\r
7 #include "streq.h"\r
8 #include <stdio.h>\r
9 #include <string>\r
10 #include <vector>\r
11 #include <glib/gmem.h>\r
12 \r
13 class SvgPathGeomTest : public CxxTest::TestSuite\r
14 {\r
15 private:\r
16     std::vector<std::string> rectanglesAbsoluteClosed;\r
17     std::vector<std::string> rectanglesRelativeClosed;\r
18     std::vector<std::string> rectanglesAbsoluteOpen;\r
19     std::vector<std::string> rectanglesRelativeOpen;\r
20     std::vector<std::string> rectanglesAbsoluteClosed2;\r
21     std::vector<std::string> rectanglesRelativeClosed2;\r
22     Geom::PathVector rectanglepvopen;\r
23     Geom::PathVector rectanglepvclosed;\r
24     Geom::PathVector rectanglepvclosed2;\r
25 public:\r
26     SvgPathGeomTest() {\r
27         // Lots of ways to define the same rectangle\r
28         rectanglesAbsoluteClosed.push_back("M 1,2 L 4,2 L 4,8 L 1,8 z");\r
29         rectanglesAbsoluteClosed.push_back("M 1,2 4,2 4,8 1,8 z");\r
30         rectanglesAbsoluteClosed.push_back("M 1,2 H 4 V 8 H 1 z");\r
31         rectanglesRelativeClosed.push_back("m 1,2 l 3,0 l 0,6 l -3,0 z");\r
32         rectanglesRelativeClosed.push_back("m 1,2 3,0 0,6 -3,0 z");\r
33         rectanglesRelativeClosed.push_back("m 1,2 h 3 v 6 h -3 z");\r
34         rectanglesAbsoluteOpen.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2");\r
35         rectanglesAbsoluteOpen.push_back("M 1,2 4,2 4,8 1,8 1,2");\r
36         rectanglesAbsoluteOpen.push_back("M 1,2 H 4 V 8 H 1 V 2");\r
37         rectanglesRelativeOpen.push_back("m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6");\r
38         rectanglesRelativeOpen.push_back("m 1,2 3,0 0,6 -3,0 0,-6");\r
39         rectanglesRelativeOpen.push_back("m 1,2 h 3 v 6 h -3 v -6");\r
40         rectanglesAbsoluteClosed2.push_back("M 1,2 L 4,2 L 4,8 L 1,8 L 1,2 z");\r
41         rectanglesAbsoluteClosed2.push_back("M 1,2 4,2 4,8 1,8 1,2 z");\r
42         rectanglesAbsoluteClosed2.push_back("M 1,2 H 4 V 8 H 1 V 2 z");\r
43         rectanglesRelativeClosed2.push_back("m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6 z");\r
44         rectanglesRelativeClosed2.push_back("m 1,2 3,0 0,6 -3,0 0,-6 z");\r
45         rectanglesRelativeClosed2.push_back("m 1,2 h 3 v 6 h -3 v -6 z");\r
46         rectanglepvopen.push_back(Geom::Path(Geom::Point(1,2)));\r
47         rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));\r
48         rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));\r
49         rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));\r
50         rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(1,8),Geom::Point(1,2)));\r
51         rectanglepvclosed.push_back(Geom::Path(Geom::Point(1,2)));\r
52         rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));\r
53         rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));\r
54         rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));\r
55         rectanglepvclosed.back().close();\r
56         rectanglepvclosed2.push_back(Geom::Path(Geom::Point(1,2)));\r
57         rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(1,2),Geom::Point(4,2)));\r
58         rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(4,2),Geom::Point(4,8)));\r
59         rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(4,8),Geom::Point(1,8)));\r
60         rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(1,8),Geom::Point(1,2)));\r
61         rectanglepvclosed2.back().close();\r
62         // TODO: Also test some (smooth) cubic/quadratic beziers and elliptical arcs\r
63         // TODO: Should we make it mandatory that h/v in the path data results in a H/VLineSegment?\r
64         //       If so, the tests should be modified to reflect this.\r
65     }\r
66 \r
67 // createSuite and destroySuite get us per-suite setup and teardown\r
68 // without us having to worry about static initialization order, etc.\r
69     static SvgPathGeomTest *createSuite() { return new SvgPathGeomTest(); }\r
70     static void destroySuite( SvgPathGeomTest *suite ) { delete suite; }\r
71 \r
72     void testReadRectanglesAbsoluteClosed()\r
73     {\r
74         for(size_t i=0; i<rectanglesAbsoluteClosed.size(); i++) {\r
75             Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed[i].c_str());\r
76             TSM_ASSERT(rectanglesAbsoluteClosed[i].c_str(), bpathEqual(pv,rectanglepvclosed));\r
77         }\r
78     }\r
79 \r
80     void testReadRectanglesRelativeClosed()\r
81     {\r
82         for(size_t i=0; i<rectanglesRelativeClosed.size(); i++) {\r
83             Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed[i].c_str());\r
84             TSM_ASSERT(rectanglesRelativeClosed[i].c_str(), bpathEqual(pv,rectanglepvclosed));\r
85         }\r
86     }\r
87 \r
88     void testReadRectanglesAbsoluteOpen()\r
89     {\r
90         for(size_t i=0; i<rectanglesAbsoluteOpen.size(); i++) {\r
91             Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteOpen[i].c_str());\r
92             TSM_ASSERT(rectanglesAbsoluteOpen[i].c_str(), bpathEqual(pv,rectanglepvopen));\r
93         }\r
94     }\r
95 \r
96     void testReadRectanglesRelativeOpen()\r
97     {\r
98         for(size_t i=0; i<rectanglesRelativeOpen.size(); i++) {\r
99             Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeOpen[i].c_str());\r
100             TSM_ASSERT(rectanglesRelativeOpen[i].c_str(), bpathEqual(pv,rectanglepvopen));\r
101         }\r
102     }\r
103 \r
104     void testReadRectanglesAbsoluteClosed2()\r
105     {\r
106         for(size_t i=0; i<rectanglesAbsoluteClosed2.size(); i++) {\r
107             Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed2[i].c_str());\r
108             TSM_ASSERT(rectanglesAbsoluteClosed2[i].c_str(), bpathEqual(pv,rectanglepvclosed2));\r
109         }\r
110     }\r
111 \r
112     void testReadRectanglesRelativeClosed2()\r
113     {\r
114         for(size_t i=0; i<rectanglesRelativeClosed2.size(); i++) {\r
115             Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed2[i].c_str());\r
116             TSM_ASSERT(rectanglesRelativeClosed2[i].c_str(), bpathEqual(pv,rectanglepvclosed2));\r
117         }\r
118     }\r
119 \r
120     void testReadConcatenatedPaths()\r
121     {\r
122         // Note that finalPoint doesn't actually return the final point of the path, just the last given point... (but since this might be intentional and we're not testing lib2geom here, we just specify the final point explicitly\r
123         Geom::PathVector pv_good;\r
124         pv_good.push_back(rectanglepvclosed.back());\r
125         pv_good.push_back(rectanglepvopen.back() * Geom::Translate(1,2)/* * Geom::Translate(pv_good[0].finalPoint())*/);\r
126         pv_good.push_back(rectanglepvclosed.back() * Geom::Translate(2,4)/* *Geom::Translate(pv_good[1].finalPoint())*/);\r
127         pv_good.push_back(rectanglepvopen.back());\r
128         pv_good[0].close();\r
129         pv_good[1].close(false);\r
130         pv_good[2].close();\r
131         pv_good[3].close(false);\r
132         std::string path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];\r
133         Geom::PathVector pv = sp_svg_read_pathv(path_str.c_str());\r
134         TS_ASSERT(bpathEqual(pv,pv_good));\r
135     }\r
136 \r
137     void testReadZeroLengthSubpaths() {\r
138         // Per the SVG 1.1 specification (section F5) zero-length subpaths are relevant\r
139         Geom::PathVector pv_good;\r
140         pv_good.push_back(Geom::Path(Geom::Point(0,0)));\r
141         pv_good.push_back(Geom::Path(Geom::Point(1,1)));\r
142         pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2)));\r
143         pv_good.push_back(Geom::Path(Geom::Point(3,3)));\r
144         pv_good.back().close();\r
145         pv_good.push_back(Geom::Path(Geom::Point(4,4)));\r
146         pv_good.back().append(Geom::LineSegment(Geom::Point(4,4),Geom::Point(5,5)));\r
147         pv_good.back().close();\r
148         pv_good.push_back(Geom::Path(Geom::Point(6,6)));\r
149         {   // Test absolute version\r
150             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
151             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
152             TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
153         }\r
154         {   // Test relative version\r
155             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
156             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
157             TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
158         }\r
159     }\r
160 \r
161     void testReadImplicitMoveto() {\r
162         TS_WARN("Currently lib2geom (/libnr) has no way of specifying the difference between '... z M 0,0 L 1,0' and '... z L 1,0', the SVG specification does state that these should be handled differently with respect to markers however, see the description of the 'orient' attribute of the 'marker' element.");\r
163         Geom::PathVector pv_good;\r
164         pv_good.push_back(Geom::Path(Geom::Point(1,1)));\r
165         pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(2,2)));\r
166         pv_good.back().close();\r
167         pv_good.push_back(Geom::Path(Geom::Point(1,1)));\r
168         pv_good.back().append(Geom::LineSegment(Geom::Point(1,1),Geom::Point(3,3)));\r
169         pv_good.back().close();\r
170         {   // Test absolute version\r
171             char const * path_str = "M 1,1 L 2,2 z L 3,3 z";\r
172             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
173             TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
174         }\r
175         {   // Test relative version\r
176             char const * path_str = "M 1,1 L 2,2 z L 3,3 z";\r
177             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
178             TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
179         }\r
180     }\r
181 \r
182     void testReadFloatingPoint() {\r
183         Geom::PathVector pv_good1;\r
184         pv_good1.push_back(Geom::Path(Geom::Point(.01,.02)));\r
185         pv_good1.back().append(Geom::LineSegment(Geom::Point(.01,.02),Geom::Point(.04,.02)));\r
186         pv_good1.back().append(Geom::LineSegment(Geom::Point(.04,.02),Geom::Point(1.5,1.6)));\r
187         pv_good1.back().append(Geom::LineSegment(Geom::Point(1.5,1.6),Geom::Point(.01,.08)));\r
188         pv_good1.back().append(Geom::LineSegment(Geom::Point(.01,.08),Geom::Point(.01,.02)));\r
189         pv_good1.back().close();\r
190         {   // Test decimals\r
191             char const * path_str = "M .01,.02 L.04.02 L1.5,1.6L0.01,0.08 .01.02 z";\r
192             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
193             TSM_ASSERT(path_str, bpathEqual(pv,pv_good1));\r
194         }\r
195         Geom::PathVector pv_good2;\r
196         pv_good2.push_back(Geom::Path(Geom::Point(.01,.02)));\r
197         pv_good2.back().append(Geom::LineSegment(Geom::Point(.01,.02),Geom::Point(.04,.02)));\r
198         pv_good2.back().append(Geom::LineSegment(Geom::Point(.04,.02),Geom::Point(1.5,1.6)));\r
199         pv_good2.back().append(Geom::LineSegment(Geom::Point(1.5,1.6),Geom::Point(.01,.08)));\r
200         pv_good2.back().close();\r
201         {   // Test exponent\r
202             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
203             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
204             TSM_ASSERT(path_str, bpathEqual(pv,pv_good2));\r
205         }\r
206     }\r
207 \r
208     void testReadImplicitSeparation() {\r
209         // Coordinates need not be separated by whitespace if they can still be read unambiguously\r
210         Geom::PathVector pv_good;\r
211         pv_good.push_back(Geom::Path(Geom::Point(.1,.2)));\r
212         pv_good.back().append(Geom::LineSegment(Geom::Point(.1,.2),Geom::Point(.4,.2)));\r
213         pv_good.back().append(Geom::LineSegment(Geom::Point(.4,.2),Geom::Point(.4,.8)));\r
214         pv_good.back().append(Geom::LineSegment(Geom::Point(.4,.8),Geom::Point(.1,.8)));\r
215         pv_good.back().close();\r
216         {   // Test absolute\r
217             char const * path_str = "M .1.2+0.4.2e0.4e0+8e-1.1.8 z";\r
218             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
219             TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
220         }\r
221         {   // Test relative\r
222             char const * path_str = "m .1.2+0.3.0e0.0e0+6e-1-.3.0 z";\r
223             Geom::PathVector pv = sp_svg_read_pathv(path_str);\r
224             TSM_ASSERT(path_str, bpathEqual(pv,pv_good));\r
225         }\r
226     }\r
227 \r
228     void testReadErrorMisplacedCharacter() {\r
229         char const * path_str;\r
230         Geom::PathVector pv;\r
231         // Comma in the wrong place (commas may only appear between parameters)\r
232         path_str = "M 1,2 4,2 4,8 1,8 z , m 13,15";\r
233         pv = sp_svg_read_pathv(path_str);\r
234         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
235         // Comma in the wrong place (commas may only appear between parameters)\r
236         path_str = "M 1,2 4,2 4,8 1,8 z m,13,15";\r
237         pv = sp_svg_read_pathv(path_str);\r
238         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
239         // Period in the wrong place (no numbers after a 'z')\r
240         path_str = "M 1,2 4,2 4,8 1,8 z . m 13,15";\r
241         pv = sp_svg_read_pathv(path_str);\r
242         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
243         // Sign in the wrong place (no numbers after a 'z')\r
244         path_str = "M 1,2 4,2 4,8 1,8 z + - m 13,15";\r
245         pv = sp_svg_read_pathv(path_str);\r
246         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
247         // Digit in the wrong place (no numbers after a 'z')\r
248         path_str = "M 1,2 4,2 4,8 1,8 z 9809 m 13,15";\r
249         pv = sp_svg_read_pathv(path_str);\r
250         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
251         // Digit in the wrong place (no numbers after a 'z')\r
252         path_str = "M 1,2 4,2 4,8 1,8 z 9809 876 m 13,15";\r
253         pv = sp_svg_read_pathv(path_str);\r
254         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
255     }\r
256 \r
257     void testReadErrorUnrecognizedCharacter() {\r
258         char const * path_str;\r
259         Geom::PathVector pv;\r
260         // Unrecognized character\r
261         path_str = "M 1,2 4,2 4,8 1,8 z&m 13,15";\r
262         pv = sp_svg_read_pathv(path_str);\r
263         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
264         // Unrecognized character\r
265         path_str = "M 1,2 4,2 4,8 1,8 z m &13,15";\r
266         pv = sp_svg_read_pathv(path_str);\r
267         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
268     }\r
269 \r
270     void testReadErrorTypo() {\r
271         char const * path_str;\r
272         Geom::PathVector pv;\r
273         // Typo\r
274         path_str = "M 1,2 4,2 4,8 1,8 z j 13,15";\r
275         pv = sp_svg_read_pathv(path_str);\r
276         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
277 \r
278         // Typo\r
279         path_str = "M 1,2 4,2 4,8 1,8 L 1,2 x m 13,15";\r
280         pv = sp_svg_read_pathv(path_str);\r
281         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvopen));\r
282     }\r
283 \r
284     void testReadErrorIllformedNumbers() {\r
285         char const * path_str;\r
286         Geom::PathVector pv;\r
287         // Double exponent\r
288         path_str = "M 1,2 4,2 4,8 1,8 z m 13e4e5,15";\r
289         pv = sp_svg_read_pathv(path_str);\r
290         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
291         // Double sign\r
292         path_str = "M 1,2 4,2 4,8 1,8 z m +-13,15";\r
293         pv = sp_svg_read_pathv(path_str);\r
294         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
295         // Double sign\r
296         path_str = "M 1,2 4,2 4,8 1,8 z m 13e+-12,15";\r
297         pv = sp_svg_read_pathv(path_str);\r
298         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
299         // No digit\r
300         path_str = "M 1,2 4,2 4,8 1,8 z m .e12,15";\r
301         pv = sp_svg_read_pathv(path_str);\r
302         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
303         // No digit\r
304         path_str = "M 1,2 4,2 4,8 1,8 z m .,15";\r
305         pv = sp_svg_read_pathv(path_str);\r
306         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
307         // No digit\r
308         path_str = "M 1,2 4,2 4,8 1,8 z m +,15";\r
309         pv = sp_svg_read_pathv(path_str);\r
310         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
311         // No digit\r
312         path_str = "M 1,2 4,2 4,8 1,8 z m +.e+,15";\r
313         pv = sp_svg_read_pathv(path_str);\r
314         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
315     }\r
316 \r
317     void testReadErrorJunk() {\r
318         char const * path_str;\r
319         Geom::PathVector pv;\r
320         // Junk\r
321         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
322         pv = sp_svg_read_pathv(path_str);\r
323         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
324     }\r
325 \r
326     void testReadErrorStopReading() {\r
327         char const * path_str;\r
328         Geom::PathVector pv;\r
329         // Unrecognized parameter\r
330         path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";\r
331         pv = sp_svg_read_pathv(path_str);\r
332         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
333         // Invalid parameter\r
334         path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";\r
335         pv = sp_svg_read_pathv(path_str);\r
336         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
337         // Illformed parameter\r
338         path_str = "M 1,2 4,2 4,8 1,8 z m +-12,23,34";\r
339         pv = sp_svg_read_pathv(path_str);\r
340         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvclosed));\r
341 \r
342         // "Third" parameter\r
343         path_str = "M 1,2 4,2 4,8 1,8 1,2,3 M 12,23";\r
344         pv = sp_svg_read_pathv(path_str);\r
345         TSM_ASSERT(path_str, bpathEqual(pv,rectanglepvopen));\r
346     }\r
347 \r
348     void testRoundTrip() {\r
349         // This is the easiest way to (also) test writing path data, as a path can be written in more than one way.\r
350         Geom::PathVector pv;\r
351         Geom::PathVector new_pv;\r
352         std::string org_path_str;\r
353         char * path_str;\r
354         // Rectangle (closed)\r
355         org_path_str = rectanglesAbsoluteClosed[0];\r
356         pv = sp_svg_read_pathv(org_path_str.c_str());\r
357         path_str = sp_svg_write_path(pv);\r
358         new_pv = sp_svg_read_pathv(path_str);\r
359         TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
360         g_free(path_str);\r
361         // Rectangle (open)\r
362         org_path_str = rectanglesAbsoluteOpen[0];\r
363         pv = sp_svg_read_pathv(org_path_str.c_str());\r
364         path_str = sp_svg_write_path(pv);\r
365         new_pv = sp_svg_read_pathv(path_str);\r
366         TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
367         g_free(path_str);\r
368         // Concatenated rectangles\r
369         org_path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] + rectanglesAbsoluteOpen[0];\r
370         pv = sp_svg_read_pathv(org_path_str.c_str());\r
371         path_str = sp_svg_write_path(pv);\r
372         new_pv = sp_svg_read_pathv(path_str);\r
373         TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
374         g_free(path_str);\r
375         // Zero-length subpaths\r
376         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
377         pv = sp_svg_read_pathv(org_path_str.c_str());\r
378         path_str = sp_svg_write_path(pv);\r
379         new_pv = sp_svg_read_pathv(path_str);\r
380         TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv,new_pv));\r
381         g_free(path_str);\r
382         // Floating-point\r
383         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
384         pv = sp_svg_read_pathv(org_path_str.c_str());\r
385         path_str = sp_svg_write_path(pv);\r
386         new_pv = sp_svg_read_pathv(path_str);\r
387         TSM_ASSERT(org_path_str.c_str(), bpathEqual(pv, new_pv, 1e-17));\r
388         g_free(path_str);\r
389     }\r
390 \r
391     void testMinexpPrecision() {\r
392         Geom::PathVector pv;\r
393         char * path_str;\r
394         // Default values\r
395         prefs_set_int_attribute("options.svgoutput", "allowrelativecoordinates", 1);\r
396         prefs_set_int_attribute("options.svgoutput", "forcerepeatcommands", 0);\r
397         prefs_set_int_attribute("options.svgoutput", "numericprecision", 8);\r
398         prefs_set_int_attribute("options.svgoutput", "minimumexponent", -8);\r
399         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
400         path_str = sp_svg_write_path(pv);\r
401         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
402         g_free(path_str);\r
403     }\r
404 \r
405 private:\r
406     bool bpathEqual(Geom::PathVector const &a, Geom::PathVector const &b, double eps = 1e-16) {\r
407         if (a.size() != b.size()) {\r
408             char temp[100];\r
409             sprintf(temp, "PathVectors not the same size: %u != %u", a.size(), b.size());\r
410             TS_FAIL(temp);\r
411             return false;\r
412         }\r
413         for(size_t i=0; i<a.size(); i++) {\r
414             Geom::Path const &pa = a[i];\r
415             Geom::Path const &pb = b[i];\r
416             if (pa.closed() && !pb.closed()) {\r
417                 char temp[100];\r
418                 sprintf(temp, "Left subpath is closed, right subpath is open. Subpath: %u", i);\r
419                 TS_FAIL(temp);\r
420                 return false;\r
421             }\r
422             if (!pa.closed() && pb.closed()) {\r
423                 char temp[100];\r
424                 sprintf(temp, "Right subpath is closed, left subpath is open. Subpath: %u", i);\r
425                 TS_FAIL(temp);\r
426                 return false;\r
427             }\r
428             if (pa.size() != pb.size()) {\r
429                 char temp[100];\r
430                 sprintf(temp, "Not the same number of segments: %u != %u, subpath: %u", pa.size(), pb.size(), i);\r
431                 TS_FAIL(temp);\r
432                 return false;\r
433             }\r
434             for(size_t j=0; j<pa.size(); j++) {\r
435                 Geom::Curve const* ca = &pa[j];\r
436                 Geom::Curve const* cb = &pb[j];\r
437                 if (typeid(*ca) == typeid(*cb))\r
438                 {\r
439                     if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))\r
440                     {\r
441                         Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb);\r
442                         if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {\r
443                             char temp[200];\r
444                             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], i, j);\r
445                             TS_FAIL(temp);\r
446                             return false;\r
447                         }\r
448                         if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {\r
449                             char temp[200];\r
450                             sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y], i, j);\r
451                             TS_FAIL(temp);\r
452                             return false;\r
453                         }\r
454                     }\r
455                     else if(Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca))\r
456                     {\r
457                         Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb);\r
458                         if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
459                             char temp[200];\r
460                             sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], i, j);\r
461                             TS_FAIL(temp);\r
462                             return false;\r
463                         }\r
464                         if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
465                             char temp[200];\r
466                             sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], i, j);\r
467                             TS_FAIL(temp);\r
468                             return false;\r
469                         }\r
470                     }\r
471                     else if(Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca))\r
472                     {\r
473                         Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb);\r
474                         if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
475                             char temp[200];\r
476                             sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], i, j);\r
477                             TS_FAIL(temp);\r
478                             return false;\r
479                         }\r
480                         if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
481                             char temp[200];\r
482                             sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], i, j);\r
483                             TS_FAIL(temp);\r
484                             return false;\r
485                         }\r
486                     }\r
487                     else if(Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const*>(ca))\r
488                     {\r
489                         Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const*>(cb);\r
490                         if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {\r
491                             char temp[200];\r
492                             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], i, j);\r
493                             TS_FAIL(temp);\r
494                             return false;\r
495                         }\r
496                         if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {\r
497                             char temp[200];\r
498                             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], i, j);\r
499                             TS_FAIL(temp);\r
500                             return false;\r
501                         }\r
502                         if (!Geom::are_near((*la)[2],(*lb)[2], eps)) {\r
503                             char temp[200];\r
504                             sprintf(temp, "Different 2nd control point: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[2][Geom::X], (*la)[2][Geom::Y], (*lb)[2][Geom::X], (*lb)[2][Geom::Y], i, j);\r
505                             TS_FAIL(temp);\r
506                             return false;\r
507                         }\r
508                         if (!Geom::are_near((*la)[3],(*lb)[3], eps)) {\r
509                             char temp[200];\r
510                             sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la)[3][Geom::X], (*la)[3][Geom::Y], (*lb)[3][Geom::X], (*lb)[3][Geom::Y], i, j);\r
511                             TS_FAIL(temp);\r
512                             return false;\r
513                         }\r
514                     }\r
515                     else\r
516                     {\r
517                         char temp[200];\r
518                         sprintf(temp, "Unknown curve type: %s, subpath: %u, segment: %u", typeid(*ca).name(), i, j);\r
519                         TS_FAIL(temp);\r
520                     }\r
521                 }\r
522                 else // not same type\r
523                 {\r
524                     if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))\r
525                     {\r
526                         if (Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb)) {\r
527                             if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
528                                 char temp[200];\r
529                                 sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], i, j);\r
530                                 TS_FAIL(temp);\r
531                                 return false;\r
532                             }\r
533                             if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
534                                 char temp[200];\r
535                                 sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], i, j);\r
536                                 TS_FAIL(temp);\r
537                                 return false;\r
538                             }\r
539                             char temp[200];\r
540                             sprintf(temp, "A LineSegment and an HLineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
541                             TS_TRACE(temp);\r
542                         } else if (Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb)) {\r
543                             if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
544                                 char temp[200];\r
545                                 sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], i, j);\r
546                                 TS_FAIL(temp);\r
547                                 return false;\r
548                             }\r
549                             if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
550                                 char temp[200];\r
551                                 sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], i, j);\r
552                                 TS_FAIL(temp);\r
553                                 return false;\r
554                             }\r
555                             char temp[200];\r
556                             sprintf(temp, "A LineSegment and a VLineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
557                             TS_TRACE(temp);\r
558                         } else {\r
559                             char temp[200];\r
560                             sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), i, j);\r
561                             TS_FAIL(temp);\r
562                         }\r
563                     }\r
564                     else if(Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb))\r
565                     {\r
566                         if (Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca)) {\r
567                             if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
568                                 char temp[200];\r
569                                 sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], i, j);\r
570                                 TS_FAIL(temp);\r
571                                 return false;\r
572                             }\r
573                             if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
574                                 char temp[200];\r
575                                 sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], i, j);\r
576                                 TS_FAIL(temp);\r
577                                 return false;\r
578                             }\r
579                             char temp[200];\r
580                             sprintf(temp, "An HLineSegment and a LineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
581                             TS_TRACE(temp);\r
582                         } else if (Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca)) {\r
583                             if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
584                                 char temp[200];\r
585                                 sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], i, j);\r
586                                 TS_FAIL(temp);\r
587                                 return false;\r
588                             }\r
589                             if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
590                                 char temp[200];\r
591                                 sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], i, j);\r
592                                 TS_FAIL(temp);\r
593                                 return false;\r
594                             }\r
595                             char temp[200];\r
596                             sprintf(temp, "A VLineSegment and a LineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
597                             TS_TRACE(temp);\r
598                         } else {\r
599                             char temp[200];\r
600                             sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), i, j);\r
601                             TS_FAIL(temp);\r
602                             return false;\r
603                         }\r
604                     } else {\r
605                         char temp[200];\r
606                         sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), i, j);\r
607                         TS_FAIL(temp);\r
608                     }\r
609                 }\r
610             }\r
611         }\r
612         return true;\r
613     }\r
614 };\r
615 \r
616 \r
617 /*\r
618   Local Variables:\r
619   mode:c++\r
620   c-file-style:"stroustrup"\r
621   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
622   indent-tabs-mode:nil\r
623   fill-column:99\r
624   End:\r
625 */\r
626 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r