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