1 #define SEEN_LIBNR_N_ART_BPATH_2GEOM_CPP\r
2 \r
3 /** \file\r
4 * Contains functions to convert from NArtBpath to 2geom's Path\r
5 *\r
6 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>\r
7 *\r
8 * Released under GNU GPL, read the file 'COPYING' for more information\r
9 */\r
10 \r
11 \r
12 #include "live_effects/n-art-bpath-2geom.h"\r
13 #include "svg/svg.h"\r
14 #include <glib.h>\r
15 #include <2geom/path.h>\r
16 #include <2geom/svg-path.h>\r
17 #include <2geom/svg-path-parser.h>\r
18 #include <2geom/sbasis-to-bezier.h>\r
19 \r
20 #define LPE_USE_2GEOM_CONVERSION\r
21 \r
22 //##########################################################\r
23 \r
24 #include <iostream>\r
25 #include <sstream>\r
26 #include <string>\r
27 #include <boost/format.hpp>\r
28 \r
29 static void curve_to_svgd(std::ostream & f, Geom::Curve const* c) {\r
30 if(Geom::LineSegment const *line_segment = dynamic_cast<Geom::LineSegment const *>(c)) {\r
31 f << boost::format("L %g,%g ") % (*line_segment)[1][0] % (*line_segment)[1][1];\r
32 }\r
33 else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast<Geom::QuadraticBezier const *>(c)) {\r
34 f << boost::format("Q %g,%g %g,%g ") % (*quadratic_bezier)[1][0] % (*quadratic_bezier)[1][0] \r
35 % (*quadratic_bezier)[2][0] % (*quadratic_bezier)[2][1];\r
36 }\r
37 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(c)) {\r
38 f << boost::format("C %g,%g %g,%g %g,%g ") \r
39 % (*cubic_bezier)[1][0] % (*cubic_bezier)[1][1] \r
40 % (*cubic_bezier)[2][0] % (*cubic_bezier)[2][1] \r
41 % (*cubic_bezier)[3][0] % (*cubic_bezier)[3][1];\r
42 }\r
43 // else if(Geom::SVGEllipticalArc const *svg_elliptical_arc = dynamic_cast<Geom::SVGEllipticalArc *>(c)) {\r
44 // //get at the innards and spit them out as svgd\r
45 // }\r
46 else { \r
47 //this case handles sbasis as well as all other curve types\r
48 Geom::Path sbasis_path;\r
49 path_from_sbasis(sbasis_path, c->sbasis(), 0.1);\r
50 \r
51 //recurse to convert the new path resulting from the sbasis to svgd\r
52 for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {\r
53 curve_to_svgd(f, &(*iter));\r
54 }\r
55 }\r
56 }\r
57 \r
58 static void write_svgd(std::ostream & f, Geom::Path const &p) {\r
59 if(f == NULL) {\r
60 f << "ERRRRRRORRRRR";\r
61 return;\r
62 }\r
63 \r
64 f << boost::format("M %g,%g ") % p.initialPoint()[0] % p.initialPoint()[1];\r
65 \r
66 for(Geom::Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) {\r
67 curve_to_svgd(f, &(*iter));\r
68 }\r
69 if(p.closed())\r
70 f << "Z ";\r
71 }\r
72 \r
73 static void write_svgd(std::ostream & f, std::vector<Geom::Path> const &p) {\r
74 std::vector<Geom::Path>::const_iterator it(p.begin());\r
75 for(; it != p.end(); it++) {\r
76 write_svgd(f, *it);\r
77 }\r
78 }\r
79 \r
80 //##########################################################\r
81 #ifndef LPE_USE_2GEOM_CONVERSION\r
82 \r
83 static\r
84 Geom::Point point(double *nums, int ix) {\r
85 return Geom::Point(nums[ix], nums[ix + 1]);\r
86 }\r
87 \r
88 using namespace Geom;\r
89 \r
90 class OldPathBuilder {\r
91 public:\r
92 OldPathBuilder(double const &c = Geom_EPSILON) : _current_path(NULL) {\r
93 _continuity_tollerance = c;\r
94 }\r
95 \r
96 void startPathRel(Point const &p0) { startPath(p0 + _current_point); }\r
97 void startPath(Point const &p0) {\r
98 _pathset.push_back(Geom::Path());\r
99 _current_path = &_pathset.back();\r
100 _initial_point = _current_point = p0;\r
101 }\r
102 \r
103 void pushLineRel(Point const &p0) { pushLine(p0 + _current_point); }\r
104 void pushLine(Point const &p1) {\r
105 if (!_current_path) startPath(_current_point);\r
106 _current_path->appendNew<LineSegment>(p1);\r
107 _current_point = p1;\r
108 }\r
109 \r
110 void pushLineRel(Point const &p0, Point const &p1) { pushLine(p0 + _current_point, p1 + _current_point); }\r
111 void pushLine(Point const &p0, Point const &p1) {\r
112 if(p0 != _current_point) startPath(p0);\r
113 pushLine(p1);\r
114 }\r
115 \r
116 void pushHorizontalRel(Coord y) { pushHorizontal(y + _current_point[1]); }\r
117 void pushHorizontal(Coord y) {\r
118 if (!_current_path) startPath(_current_point);\r
119 pushLine(Point(_current_point[0], y));\r
120 }\r
121 \r
122 void pushVerticalRel(Coord x) { pushVertical(x + _current_point[0]); }\r
123 void pushVertical(Coord x) {\r
124 if (!_current_path) startPath(_current_point);\r
125 pushLine(Point(x, _current_point[1]));\r
126 }\r
127 \r
128 void pushQuadraticRel(Point const &p1, Point const &p2) { pushQuadratic(p1 + _current_point, p2 + _current_point); }\r
129 void pushQuadratic(Point const &p1, Point const &p2) {\r
130 if (!_current_path) startPath(_current_point);\r
131 _current_path->appendNew<QuadraticBezier>(p1, p2);\r
132 _current_point = p2;\r
133 }\r
134 \r
135 void pushQuadraticRel(Point const &p0, Point const &p1, Point const &p2) {\r
136 pushQuadratic(p0 + _current_point, p1 + _current_point, p2 + _current_point);\r
137 }\r
138 void pushQuadratic(Point const &p0, Point const &p1, Point const &p2) {\r
139 if(p0 != _current_point) startPath(p0);\r
140 pushQuadratic(p1, p2);\r
141 }\r
142 \r
143 void pushCubicRel(Point const &p1, Point const &p2, Point const &p3) {\r
144 pushCubic(p1 + _current_point, p2 + _current_point, p3 + _current_point);\r
145 }\r
146 void pushCubic(Point const &p1, Point const &p2, Point const &p3) {\r
147 if (!_current_path) startPath(_current_point);\r
148 _current_path->appendNew<CubicBezier>(p1, p2, p3);\r
149 _current_point = p3;\r
150 }\r
151 \r
152 void pushCubicRel(Point const &p0, Point const &p1, Point const &p2, Point const &p3) {\r
153 pushCubic(p0 + _current_point, p1 + _current_point, p2 + _current_point, p3 + _current_point);\r
154 }\r
155 void pushCubic(Point const &p0, Point const &p1, Point const &p2, Point const &p3) {\r
156 if(p0 != _current_point) startPath(p0);\r
157 pushCubic(p1, p2, p3);\r
158 }\r
159 /*\r
160 void pushEllipseRel(Point const &radii, double rotation, bool large, bool sweep, Point const &end) {\r
161 pushEllipse(radii, rotation, large, sweep, end + _current_point);\r
162 }\r
163 void pushEllipse(Point const &radii, double rotation, bool large, bool sweep, Point const &end) {\r
164 if (!_current_path) startPath(_current_point);\r
165 _current_path->append(SVGEllipticalArc(_current_point, radii[0], radii[1], rotation, large, sweep, end));\r
166 _current_point = end;\r
167 }\r
168 \r
169 void pushEllipseRel(Point const &initial, Point const &radii, double rotation, bool large, bool sweep, Point const &end) {\r
170 pushEllipse(initial + _current_point, radii, rotation, large, sweep, end + _current_point);\r
171 }\r
172 void pushEllipse(Point const &initial, Point const &radii, double rotation, bool large, bool sweep, Point const &end) {\r
173 if(initial != _current_point) startPath(initial);\r
174 pushEllipse(radii, rotation, large, sweep, end);\r
175 }*/\r
176 \r
177 void pushSBasis(SBasisCurve &sb) {\r
178 pushSBasis(sb.sbasis());\r
179 }\r
180 void pushSBasis(D2<SBasis> sb) {\r
181 Point initial = Point(sb[X][0][0], sb[Y][0][0]);\r
182 if (!_current_path) startPath(_current_point);\r
183 if (distance(initial, _current_point) > _continuity_tollerance) {\r
184 startPath(initial);\r
185 } else if (_current_point != initial) {\r
186 /* in this case there are three possible options\r
187 1. connect the points with tiny line segments\r
188 this may well translate into bug reports from\r
189 users claiming "duplicate or extraneous nodes"\r
190 2. fudge the initial point of the multidimsb\r
191 we've chosen to do this here but question the \r
192 numerical stability of this decision\r
193 3. translate the whole sbasis so that initial is coincident\r
194 with _current_point. this could very well lead\r
195 to an accumulation of error for paths that expect \r
196 to meet in the end.\r
197 perhaps someday an option could be made to allow \r
198 the user to choose between these alternatives\r
199 if the need arises\r
200 */\r
201 sb[X][0][0] = _current_point[X];\r
202 sb[Y][0][0] = _current_point[Y]; \r
203 }\r
204 _current_path->append(sb);\r
205 }\r
206 \r
207 void closePath() {\r
208 if (_current_path) {\r
209 _current_path->close(true);\r
210 _current_path = NULL;\r
211 }\r
212 _current_point = _initial_point = Point();\r
213 }\r
214 \r
215 std::vector<Path> const &peek() const { return _pathset; }\r
216 \r
217 private:\r
218 std::vector<Path> _pathset;\r
219 Path *_current_path;\r
220 Point _current_point;\r
221 Point _initial_point;\r
222 double _continuity_tollerance;\r
223 };\r
224 \r
225 static\r
226 std::vector<Geom::Path>\r
227 read_svgd(std::istringstream & s) {\r
228 assert(s);\r
229 \r
230 OldPathBuilder builder;\r
231 \r
232 char mode = 0;\r
233 \r
234 double nums[7];\r
235 int cur = 0;\r
236 while(!s.eof()) {\r
237 char ch;\r
238 s >> ch;\r
239 if((ch >= 'A' and ch <= 'Z') or (ch >= 'a' and ch <= 'z')) {\r
240 mode = ch;\r
241 cur = 0;\r
242 } else if (ch == ' ' or ch == '\t' or ch == '\n' or ch == '\r' or ch == ',')\r
243 continue;\r
244 else if ((ch >= '0' and ch <= '9') or ch == '-' or ch == '.' or ch == '+') {\r
245 s.unget();\r
246 //TODO: use something else, perhaps. Unless the svg path number spec matches scan.\r
247 s >> nums[cur];\r
248 cur++;\r
249 }\r
250 \r
251 switch(mode) {\r
252 //FIXME: "If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands."\r
253 case 'm':\r
254 if(cur >= 2) {\r
255 builder.startPathRel(point(nums, 0));\r
256 cur = 0;\r
257 }\r
258 break;\r
259 case 'M':\r
260 if(cur >= 2) {\r
261 builder.startPath(point(nums, 0));\r
262 cur = 0;\r
263 }\r
264 break;\r
265 case 'l':\r
266 if(cur >= 2) {\r
267 builder.pushLineRel(point(nums, 0));\r
268 cur = 0;\r
269 }\r
270 break;\r
271 case 'L':\r
272 if(cur >= 2) {\r
273 builder.pushLine(point(nums, 0));\r
274 cur = 0;\r
275 }\r
276 break;\r
277 case 'h':\r
278 if(cur >= 1) {\r
279 builder.pushHorizontalRel(nums[0]);\r
280 cur = 0;\r
281 }\r
282 break;\r
283 case 'H':\r
284 if(cur >= 1) {\r
285 builder.pushHorizontal(nums[0]);\r
286 cur = 0;\r
287 }\r
288 break;\r
289 case 'v':\r
290 if(cur >= 1) {\r
291 builder.pushVerticalRel(nums[0]);\r
292 cur = 0;\r
293 }\r
294 break;\r
295 case 'V':\r
296 if(cur >= 1) {\r
297 builder.pushVertical(nums[0]);\r
298 cur = 0;\r
299 }\r
300 break;\r
301 case 'c':\r
302 if(cur >= 6) {\r
303 builder.pushCubicRel(point(nums, 0), point(nums, 2), point(nums, 4));\r
304 cur = 0;\r
305 }\r
306 break;\r
307 case 'C':\r
308 if(cur >= 6) {\r
309 builder.pushCubic(point(nums, 0), point(nums, 2), point(nums, 4));\r
310 cur = 0;\r
311 }\r
312 break;\r
313 case 'q':\r
314 if(cur >= 4) {\r
315 builder.pushQuadraticRel(point(nums, 0), point(nums, 2));\r
316 cur = 0;\r
317 }\r
318 break;\r
319 case 'Q':\r
320 if(cur >= 4) {\r
321 builder.pushQuadratic(point(nums, 0), point(nums, 2));\r
322 cur = 0;\r
323 }\r
324 break;\r
325 case 'a':\r
326 if(cur >= 7) {\r
327 //builder.pushEllipseRel(point(nums, 0), nums[2], nums[3] > 0, nums[4] > 0, point(nums, 5));\r
328 cur = 0;\r
329 }\r
330 break;\r
331 case 'A':\r
332 if(cur >= 7) {\r
333 //builder.pushEllipse(point(nums, 0), nums[2], nums[3] > 0, nums[4] > 0, point(nums, 5));\r
334 cur = 0;\r
335 }\r
336 break;\r
337 case 'z':\r
338 case 'Z':\r
339 builder.closePath();\r
340 break;\r
341 }\r
342 }\r
343 return builder.peek();\r
344 }\r
345 \r
346 \r
347 #endif \r
348 //##########################################################\r
349 \r
350 std::vector<Geom::Path> \r
351 SVGD_to_2GeomPath (char const *svgd)\r
352 {\r
353 std::vector<Geom::Path> pathv;\r
354 #ifdef LPE_USE_2GEOM_CONVERSION\r
355 try {\r
356 pathv = Geom::parse_svg_path(svgd);\r
357 }\r
358 catch (std::runtime_error e) {\r
359 g_warning("SVGPathParseError: %s", e.what());\r
360 }\r
361 #else\r
362 std::istringstream ss;\r
363 std::string svgd_string = svgd;\r
364 ss.str(svgd_string);\r
365 pathv = read_svgd(ss);\r
366 #endif\r
367 return pathv;\r
368 }\r
369 \r
370 \r
371 std::vector<Geom::Path>\r
372 BPath_to_2GeomPath(NArtBpath const * bpath)\r
373 {\r
374 std::vector<Geom::Path> pathv;\r
375 char *svgpath = sp_svg_write_path(bpath);\r
376 if (!svgpath) {\r
377 g_warning("BPath_to_2GeomPath - empty path returned");\r
378 return pathv;\r
379 }\r
380 pathv = SVGD_to_2GeomPath(svgpath);\r
381 g_free(svgpath);\r
382 return pathv;\r
383 }\r
384 \r
385 char *\r
386 SVGD_from_2GeomPath(std::vector<Geom::Path> const & path)\r
387 {\r
388 std::ostringstream ss;\r
389 write_svgd(ss, path);\r
390 ss.flush();\r
391 std::string str = ss.str();\r
392 char * svgd = g_strdup(str.c_str());\r
393 return svgd;\r
394 }\r
395 \r
396 NArtBpath *\r
397 BPath_from_2GeomPath(std::vector<Geom::Path> const & path)\r
398 {\r
399 char * svgd = SVGD_from_2GeomPath(path);\r
400 NArtBpath *bpath = sp_svg_read_path(svgd);\r
401 g_free(svgd);\r
402 return bpath;\r
403 }\r
404 \r
405 \r
406 \r
407 /*\r
408 Local Variables:\r
409 mode:c++\r
410 c-file-style:"stroustrup"\r
411 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
412 indent-tabs-mode:nil\r
413 fill-column:99\r
414 End:\r
415 */\r
416 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :\r