Code

Commit LivePathEffect branch to trunk!
[inkscape.git] / src / live_effects / n-art-bpath-2geom.cpp
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