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 "preferences.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
395 Inkscape::Preferences *prefs = Inkscape::Preferences::get();\r
396 prefs->setBool("/options/svgoutput/allowrelativecoordinates", true);\r
397 prefs->setBool("/options/svgoutput/forcerepeatcommands", false);\r
398 prefs->setInt("/options/svgoutput/numericprecision", 8);\r
399 prefs->setInt("/options/svgoutput/minimumexponent", -8);\r
400 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
401 path_str = sp_svg_write_path(pv);\r
402 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
403 g_free(path_str);\r
404 }\r
405 \r
406 private:\r
407 bool bpathEqual(Geom::PathVector const &a, Geom::PathVector const &b, double eps = 1e-16) {\r
408 if (a.size() != b.size()) {\r
409 char temp[100];\r
410 sprintf(temp, "PathVectors not the same size: %u != %u", a.size(), b.size());\r
411 TS_FAIL(temp);\r
412 return false;\r
413 }\r
414 for(size_t i=0; i<a.size(); i++) {\r
415 Geom::Path const &pa = a[i];\r
416 Geom::Path const &pb = b[i];\r
417 if (pa.closed() && !pb.closed()) {\r
418 char temp[100];\r
419 sprintf(temp, "Left subpath is closed, right subpath is open. Subpath: %u", i);\r
420 TS_FAIL(temp);\r
421 return false;\r
422 }\r
423 if (!pa.closed() && pb.closed()) {\r
424 char temp[100];\r
425 sprintf(temp, "Right subpath is closed, left subpath is open. Subpath: %u", i);\r
426 TS_FAIL(temp);\r
427 return false;\r
428 }\r
429 if (pa.size() != pb.size()) {\r
430 char temp[100];\r
431 sprintf(temp, "Not the same number of segments: %u != %u, subpath: %u", pa.size(), pb.size(), i);\r
432 TS_FAIL(temp);\r
433 return false;\r
434 }\r
435 for(size_t j=0; j<pa.size(); j++) {\r
436 Geom::Curve const* ca = &pa[j];\r
437 Geom::Curve const* cb = &pb[j];\r
438 if (typeid(*ca) == typeid(*cb))\r
439 {\r
440 if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))\r
441 {\r
442 Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb);\r
443 if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {\r
444 char temp[200];\r
445 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
446 TS_FAIL(temp);\r
447 return false;\r
448 }\r
449 if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {\r
450 char temp[200];\r
451 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
452 TS_FAIL(temp);\r
453 return false;\r
454 }\r
455 }\r
456 else if(Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca))\r
457 {\r
458 Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb);\r
459 if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
460 char temp[200];\r
461 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
462 TS_FAIL(temp);\r
463 return false;\r
464 }\r
465 if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
466 char temp[200];\r
467 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
468 TS_FAIL(temp);\r
469 return false;\r
470 }\r
471 }\r
472 else if(Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca))\r
473 {\r
474 Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb);\r
475 if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
476 char temp[200];\r
477 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
478 TS_FAIL(temp);\r
479 return false;\r
480 }\r
481 if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
482 char temp[200];\r
483 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
484 TS_FAIL(temp);\r
485 return false;\r
486 }\r
487 }\r
488 else if(Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const*>(ca))\r
489 {\r
490 Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const*>(cb);\r
491 if (!Geom::are_near((*la)[0],(*lb)[0], eps)) {\r
492 char temp[200];\r
493 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
494 TS_FAIL(temp);\r
495 return false;\r
496 }\r
497 if (!Geom::are_near((*la)[1],(*lb)[1], eps)) {\r
498 char temp[200];\r
499 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
500 TS_FAIL(temp);\r
501 return false;\r
502 }\r
503 if (!Geom::are_near((*la)[2],(*lb)[2], eps)) {\r
504 char temp[200];\r
505 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
506 TS_FAIL(temp);\r
507 return false;\r
508 }\r
509 if (!Geom::are_near((*la)[3],(*lb)[3], eps)) {\r
510 char temp[200];\r
511 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
512 TS_FAIL(temp);\r
513 return false;\r
514 }\r
515 }\r
516 else\r
517 {\r
518 char temp[200];\r
519 sprintf(temp, "Unknown curve type: %s, subpath: %u, segment: %u", typeid(*ca).name(), i, j);\r
520 TS_FAIL(temp);\r
521 }\r
522 }\r
523 else // not same type\r
524 {\r
525 if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca))\r
526 {\r
527 if (Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb)) {\r
528 if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
529 char temp[200];\r
530 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
531 TS_FAIL(temp);\r
532 return false;\r
533 }\r
534 if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
535 char temp[200];\r
536 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
537 TS_FAIL(temp);\r
538 return false;\r
539 }\r
540 char temp[200];\r
541 sprintf(temp, "A LineSegment and an HLineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
542 TS_TRACE(temp);\r
543 } else if (Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb)) {\r
544 if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
545 char temp[200];\r
546 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
547 TS_FAIL(temp);\r
548 return false;\r
549 }\r
550 if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
551 char temp[200];\r
552 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
553 TS_FAIL(temp);\r
554 return false;\r
555 }\r
556 char temp[200];\r
557 sprintf(temp, "A LineSegment and a VLineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
558 TS_TRACE(temp);\r
559 } else {\r
560 char temp[200];\r
561 sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), i, j);\r
562 TS_FAIL(temp);\r
563 }\r
564 }\r
565 else if(Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb))\r
566 {\r
567 if (Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca)) {\r
568 if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
569 char temp[200];\r
570 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
571 TS_FAIL(temp);\r
572 return false;\r
573 }\r
574 if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
575 char temp[200];\r
576 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
577 TS_FAIL(temp);\r
578 return false;\r
579 }\r
580 char temp[200];\r
581 sprintf(temp, "An HLineSegment and a LineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
582 TS_TRACE(temp);\r
583 } else if (Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca)) {\r
584 if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) {\r
585 char temp[200];\r
586 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
587 TS_FAIL(temp);\r
588 return false;\r
589 }\r
590 if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) {\r
591 char temp[200];\r
592 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
593 TS_FAIL(temp);\r
594 return false;\r
595 }\r
596 char temp[200];\r
597 sprintf(temp, "A VLineSegment and a LineSegment have been considered equal. Subpath: %u, segment: %u", i, j);\r
598 TS_TRACE(temp);\r
599 } else {\r
600 char temp[200];\r
601 sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), i, j);\r
602 TS_FAIL(temp);\r
603 return false;\r
604 }\r
605 } else {\r
606 char temp[200];\r
607 sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), i, j);\r
608 TS_FAIL(temp);\r
609 }\r
610 }\r
611 }\r
612 }\r
613 return true;\r
614 }\r
615 };\r
616 \r
617 \r
618 /*\r
619 Local Variables:\r
620 mode:c++\r
621 c-file-style:"stroustrup"\r
622 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
623 indent-tabs-mode:nil\r
624 fill-column:99\r
625 End:\r
626 */\r
627 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r