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