1 #define __SP_SVG_PARSE_C__
2 /*
3 svg-path.c: Parse SVG path element data into bezier path.
5 Copyright (C) 2000 Eazel, Inc.
6 Copyright (C) 2000 Lauris Kaplinski
7 Copyright (C) 2001 Ximian, Inc.
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public
20 License along with this program; if not, write to the
21 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.
24 Authors:
25 Raph Levien <raph@artofcode.com>
26 Lauris Kaplinski <lauris@ximian.com>
27 */
29 #include <cassert>
30 #include <glib/gmem.h>
31 #include <glib/gmessages.h>
32 #include <glib/gstrfuncs.h>
34 #include "libnr/n-art-bpath.h"
35 #include "gnome-canvas-bpath-util.h"
36 #include "stringstream.h"
39 /* This module parses an SVG path element into an RsvgBpathDef.
41 At present, there is no support for <marker> or any other contextual
42 information from the SVG file. The API will need to change rather
43 significantly to support these.
45 Reference: SVG working draft 3 March 2000, section 8.
46 */
48 #ifndef M_PI
49 #define M_PI 3.14159265358979323846
50 #endif /* M_PI */
52 /* We are lazy ;-) (Lauris) */
53 #define rsvg_bpath_def_new gnome_canvas_bpath_def_new
54 #define rsvg_bpath_def_moveto gnome_canvas_bpath_def_moveto
55 #define rsvg_bpath_def_lineto gnome_canvas_bpath_def_lineto
56 #define rsvg_bpath_def_curveto gnome_canvas_bpath_def_curveto
57 #define rsvg_bpath_def_closepath gnome_canvas_bpath_def_closepath
59 struct RSVGParsePathCtx {
60 GnomeCanvasBpathDef *bpath;
61 double cpx, cpy; /* current point */
62 double rpx, rpy; /* reflection point (for 's' and 't' commands) */
63 double spx, spy; /* beginning of current subpath point */
64 char cmd; /* current command (lowercase) */
65 int param; /* parameter number */
66 gboolean rel; /* true if relative coords */
67 double params[7]; /* parameters that have been parsed */
68 };
70 static void rsvg_path_arc_segment(RSVGParsePathCtx *ctx,
71 double xc, double yc,
72 double th0, double th1,
73 double rx, double ry, double x_axis_rotation)
74 {
75 double sin_th, cos_th;
76 double a00, a01, a10, a11;
77 double x1, y1, x2, y2, x3, y3;
78 double t;
79 double th_half;
81 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
82 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
83 /* inverse transform compared with rsvg_path_arc */
84 a00 = cos_th * rx;
85 a01 = -sin_th * ry;
86 a10 = sin_th * rx;
87 a11 = cos_th * ry;
89 th_half = 0.5 * (th1 - th0);
90 t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
91 x1 = xc + cos (th0) - t * sin (th0);
92 y1 = yc + sin (th0) + t * cos (th0);
93 x3 = xc + cos (th1);
94 y3 = yc + sin (th1);
95 x2 = x3 + t * sin (th1);
96 y2 = y3 - t * cos (th1);
97 rsvg_bpath_def_curveto(ctx->bpath,
98 a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
99 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
100 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
101 }
103 /**
104 * rsvg_path_arc: Add an RSVG arc to the path context.
105 * @ctx: Path context.
106 * @rx: Radius in x direction (before rotation).
107 * @ry: Radius in y direction (before rotation).
108 * @x_axis_rotation: Rotation angle for axes.
109 * @large_arc_flag: 0 for arc length <= 180, 1 for arc >= 180.
110 * @sweep: 0 for "negative angle", 1 for "positive angle".
111 * @x: New x coordinate.
112 * @y: New y coordinate.
113 *
114 **/
115 static void rsvg_path_arc (RSVGParsePathCtx *ctx,
116 double rx, double ry, double x_axis_rotation,
117 int large_arc_flag, int sweep_flag,
118 double x, double y)
119 {
120 double sin_th, cos_th;
121 double a00, a01, a10, a11;
122 double x0, y0, x1, y1, xc, yc;
123 double d, sfactor, sfactor_sq;
124 double th0, th1, th_arc;
125 double px, py, pl;
126 int i, n_segs;
128 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
129 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
131 /*
132 Correction of out-of-range radii as described in Appendix F.6.6:
134 1. Ensure radii are non-zero (Done?).
135 2. Ensure that radii are positive.
136 3. Ensure that radii are large enough.
137 */
139 if(rx < 0.0) rx = -rx;
140 if(ry < 0.0) ry = -ry;
142 px = cos_th * (ctx->cpx - x) * 0.5 + sin_th * (ctx->cpy - y) * 0.5;
143 py = cos_th * (ctx->cpy - y) * 0.5 - sin_th * (ctx->cpx - x) * 0.5;
144 pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);
146 if(pl > 1.0)
147 {
148 pl = sqrt(pl);
149 rx *= pl;
150 ry *= pl;
151 }
153 /* Proceed with computations as described in Appendix F.6.5 */
155 a00 = cos_th / rx;
156 a01 = sin_th / rx;
157 a10 = -sin_th / ry;
158 a11 = cos_th / ry;
159 x0 = a00 * ctx->cpx + a01 * ctx->cpy;
160 y0 = a10 * ctx->cpx + a11 * ctx->cpy;
161 x1 = a00 * x + a01 * y;
162 y1 = a10 * x + a11 * y;
163 /* (x0, y0) is current point in transformed coordinate space.
164 (x1, y1) is new point in transformed coordinate space.
166 The arc fits a unit-radius circle in this space.
167 */
168 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
169 sfactor_sq = 1.0 / d - 0.25;
170 if (sfactor_sq < 0) sfactor_sq = 0;
171 sfactor = sqrt (sfactor_sq);
172 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
173 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
174 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
175 /* (xc, yc) is center of the circle. */
177 th0 = atan2 (y0 - yc, x0 - xc);
178 th1 = atan2 (y1 - yc, x1 - xc);
180 th_arc = th1 - th0;
181 if (th_arc < 0 && sweep_flag)
182 th_arc += 2 * M_PI;
183 else if (th_arc > 0 && !sweep_flag)
184 th_arc -= 2 * M_PI;
186 n_segs = (int) ceil (fabs (th_arc / (M_PI * 0.5 + 0.001)));
188 for (i = 0; i < n_segs; i++) {
189 rsvg_path_arc_segment(ctx, xc, yc,
190 th0 + i * th_arc / n_segs,
191 th0 + (i + 1) * th_arc / n_segs,
192 rx, ry, x_axis_rotation);
193 }
195 ctx->cpx = x;
196 ctx->cpy = y;
197 }
200 /* supply defaults for missing parameters, assuming relative coordinates
201 are to be interpreted as x,y */
202 static void rsvg_parse_path_default_xy(RSVGParsePathCtx *ctx, int n_params)
203 {
204 int i;
206 if (ctx->rel) {
207 for (i = ctx->param; i < n_params; i++) {
208 if (i > 2)
209 ctx->params[i] = ctx->params[i - 2];
210 else if (i == 1)
211 ctx->params[i] = ctx->cpy;
212 else if (i == 0)
213 /* we shouldn't get here (usually ctx->param > 0 as
214 precondition) */
215 ctx->params[i] = ctx->cpx;
216 }
217 } else {
218 for (i = ctx->param; i < n_params; i++) {
219 ctx->params[i] = 0.0;
220 }
221 }
222 }
224 static void rsvg_parse_path_do_cmd(RSVGParsePathCtx *ctx, gboolean final)
225 {
226 double x1, y1, x2, y2, x3, y3;
228 #ifdef VERBOSE
229 int i;
231 g_print ("parse_path %c:", ctx->cmd);
232 for (i = 0; i < ctx->param; i++) {
233 g_print(" %f", ctx->params[i]);
234 }
235 g_print (final ? ".\n" : "\n");
236 #endif
238 switch (ctx->cmd) {
239 case 'm':
240 /* moveto */
241 if (ctx->param == 2
242 || final)
243 {
244 rsvg_parse_path_default_xy (ctx, 2);
245 #ifdef VERBOSE
246 g_print ("'m' moveto %g,%g\n",
247 ctx->params[0], ctx->params[1]);
248 #endif
249 rsvg_bpath_def_moveto (ctx->bpath,
250 ctx->params[0], ctx->params[1]);
251 ctx->cpx = ctx->rpx = ctx->spx = ctx->params[0];
252 ctx->cpy = ctx->rpy = ctx->spy = ctx->params[1];
253 ctx->param = 0;
254 ctx->cmd = 'l';
255 }
256 break;
257 case 'l':
258 /* lineto */
259 if (ctx->param == 2
260 || final)
261 {
262 rsvg_parse_path_default_xy (ctx, 2);
263 #ifdef VERBOSE
264 g_print ("'l' lineto %g,%g\n",
265 ctx->params[0], ctx->params[1]);
266 #endif
267 rsvg_bpath_def_lineto (ctx->bpath,
268 ctx->params[0], ctx->params[1]);
269 ctx->cpx = ctx->rpx = ctx->params[0];
270 ctx->cpy = ctx->rpy = ctx->params[1];
271 ctx->param = 0;
272 }
273 break;
274 case 'c':
275 /* curveto */
276 if ( ( ctx->param == 6 )
277 || final )
278 {
279 rsvg_parse_path_default_xy (ctx, 6);
280 x1 = ctx->params[0];
281 y1 = ctx->params[1];
282 x2 = ctx->params[2];
283 y2 = ctx->params[3];
284 x3 = ctx->params[4];
285 y3 = ctx->params[5];
286 #ifdef VERBOSE
287 g_print ("'c' curveto %g,%g %g,%g, %g,%g\n",
288 x1, y1, x2, y2, x3, y3);
289 #endif
290 rsvg_bpath_def_curveto (ctx->bpath,
291 x1, y1, x2, y2, x3, y3);
292 ctx->rpx = x2;
293 ctx->rpy = y2;
294 ctx->cpx = x3;
295 ctx->cpy = y3;
296 ctx->param = 0;
297 }
298 break;
299 case 's':
300 /* smooth curveto */
301 if ( ( ctx->param == 4 )
302 || final )
303 {
304 rsvg_parse_path_default_xy (ctx, 4);
305 x1 = 2 * ctx->cpx - ctx->rpx;
306 y1 = 2 * ctx->cpy - ctx->rpy;
307 x2 = ctx->params[0];
308 y2 = ctx->params[1];
309 x3 = ctx->params[2];
310 y3 = ctx->params[3];
311 #ifdef VERBOSE
312 g_print ("'s' curveto %g,%g %g,%g, %g,%g\n",
313 x1, y1, x2, y2, x3, y3);
314 #endif
315 rsvg_bpath_def_curveto (ctx->bpath,
316 x1, y1, x2, y2, x3, y3);
317 ctx->rpx = x2;
318 ctx->rpy = y2;
319 ctx->cpx = x3;
320 ctx->cpy = y3;
321 ctx->param = 0;
322 }
323 break;
324 case 'h':
325 /* horizontal lineto */
326 if (ctx->param == 1) {
327 #ifdef VERBOSE
328 g_print ("'h' lineto %g,%g\n",
329 ctx->params[0], ctx->cpy);
330 #endif
331 rsvg_bpath_def_lineto (ctx->bpath,
332 ctx->params[0], ctx->cpy);
333 ctx->cpx = ctx->rpx = ctx->params[0];
334 ctx->param = 0;
335 }
336 break;
337 case 'v':
338 /* vertical lineto */
339 if (ctx->param == 1) {
340 #ifdef VERBOSE
341 g_print ("'v' lineto %g,%g\n",
342 ctx->cpx, ctx->params[0]);
343 #endif
344 rsvg_bpath_def_lineto (ctx->bpath,
345 ctx->cpx, ctx->params[0]);
346 ctx->cpy = ctx->rpy = ctx->params[0];
347 ctx->param = 0;
348 }
349 break;
350 case 'q':
351 /* quadratic bezier curveto */
353 /* non-normative reference:
354 http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
355 */
356 if (ctx->param == 4 || final)
357 {
358 rsvg_parse_path_default_xy (ctx, 4);
359 /* raise quadratic bezier to cubic */
360 x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
361 y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
362 x3 = ctx->params[2];
363 y3 = ctx->params[3];
364 x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
365 y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
366 #ifdef VERBOSE
367 g_print("'q' curveto %g,%g %g,%g, %g,%g\n",
368 x1, y1, x2, y2, x3, y3);
369 #endif
370 rsvg_bpath_def_curveto(ctx->bpath,
371 x1, y1, x2, y2, x3, y3);
372 ctx->rpx = ctx->params[0];
373 ctx->rpy = ctx->params[1];
374 ctx->cpx = x3;
375 ctx->cpy = y3;
376 ctx->param = 0;
377 }
378 break;
379 case 't':
380 /* Truetype quadratic bezier curveto */
381 if (ctx->param == 2 || final) {
382 double xc, yc; /* quadratic control point */
384 xc = 2 * ctx->cpx - ctx->rpx;
385 yc = 2 * ctx->cpy - ctx->rpy;
386 /* generate a quadratic bezier with control point = xc, yc */
387 x1 = (ctx->cpx + 2 * xc) * (1.0 / 3.0);
388 y1 = (ctx->cpy + 2 * yc) * (1.0 / 3.0);
389 x3 = ctx->params[0];
390 y3 = ctx->params[1];
391 x2 = (x3 + 2 * xc) * (1.0 / 3.0);
392 y2 = (y3 + 2 * yc) * (1.0 / 3.0);
393 #ifdef VERBOSE
394 g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
395 x1, y1, x2, y2, x3, y3);
396 #endif
397 rsvg_bpath_def_curveto (ctx->bpath,
398 x1, y1, x2, y2, x3, y3);
399 ctx->rpx = xc;
400 ctx->rpy = yc;
401 ctx->cpx = x3;
402 ctx->cpy = y3;
403 ctx->param = 0;
404 } else if (final) {
405 if (ctx->param > 2) {
406 rsvg_parse_path_default_xy(ctx, 4);
407 /* raise quadratic bezier to cubic */
408 x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
409 y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
410 x3 = ctx->params[2];
411 y3 = ctx->params[3];
412 x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
413 y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
414 #ifdef VERBOSE
415 g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
416 x1, y1, x2, y2, x3, y3);
417 #endif
418 rsvg_bpath_def_curveto (ctx->bpath,
419 x1, y1, x2, y2, x3, y3);
420 ctx->rpx = x2;
421 ctx->rpy = y2;
422 ctx->cpx = x3;
423 ctx->cpy = y3;
424 } else {
425 rsvg_parse_path_default_xy(ctx, 2);
426 #ifdef VERBOSE
427 g_print ("'t' lineto %g,%g\n",
428 ctx->params[0], ctx->params[1]);
429 #endif
430 rsvg_bpath_def_lineto(ctx->bpath,
431 ctx->params[0], ctx->params[1]);
432 ctx->cpx = ctx->rpx = ctx->params[0];
433 ctx->cpy = ctx->rpy = ctx->params[1];
434 }
435 ctx->param = 0;
436 }
437 break;
438 case 'a':
439 if (ctx->param == 7 || final)
440 {
441 rsvg_path_arc(ctx,
442 ctx->params[0], ctx->params[1], ctx->params[2],
443 (int) ctx->params[3], (int) ctx->params[4],
444 ctx->params[5], ctx->params[6]);
445 ctx->param = 0;
446 }
447 break;
448 default:
449 ctx->param = 0;
450 }
451 }
453 static void rsvg_parse_path_data(RSVGParsePathCtx *ctx, const char *data)
454 {
455 int i = 0;
456 double val = 0;
457 char c = 0;
458 gboolean in_num = FALSE;
459 gboolean in_frac = FALSE;
460 gboolean in_exp = FALSE;
461 gboolean exp_wait_sign = FALSE;
462 int sign = 0;
463 int exp = 0;
464 int exp_sign = 0;
465 double frac = 0.0;
467 /* fixme: Do better error processing: e.g. at least stop parsing as soon as we find an error.
468 * At some point we'll need to do all of
469 * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing.
470 */
471 for (i = 0; ; i++)
472 {
473 c = data[i];
474 if (c >= '0' && c <= '9')
475 {
476 /* digit */
477 if (in_num)
478 {
479 if (in_exp)
480 {
481 exp = (exp * 10) + c - '0';
482 exp_wait_sign = FALSE;
483 }
484 else if (in_frac)
485 val += (frac *= 0.1) * (c - '0');
486 else
487 val = (val * 10) + c - '0';
488 }
489 else
490 {
491 in_num = TRUE;
492 assert(!in_frac && !in_exp);
493 exp = 0;
494 exp_sign = 1;
495 exp_wait_sign = FALSE;
496 val = c - '0';
497 sign = 1;
498 }
499 }
500 else if (c == '.' && !(in_frac || in_exp))
501 {
502 if (!in_num)
503 {
504 in_num = TRUE;
505 assert(!in_exp);
506 exp = 0;
507 exp_sign = 1;
508 exp_wait_sign = FALSE;
509 val = 0;
510 sign = 1;
511 }
512 in_frac = TRUE;
513 frac = 1;
514 }
515 else if ((c == 'E' || c == 'e') && in_num)
516 {
517 /* fixme: Should we add `&& !in_exp' to the above condition?
518 * It looks like the current code will parse `1e3e4' (as 1e4). */
519 in_exp = TRUE;
520 exp_wait_sign = TRUE;
521 exp = 0;
522 exp_sign = 1;
523 }
524 else if ((c == '+' || c == '-') && in_exp)
525 {
526 exp_sign = c == '+' ? 1 : -1;
527 }
528 else if (in_num)
529 {
530 /* end of number */
532 val *= sign * pow (10, exp_sign * exp);
533 if (ctx->rel)
534 {
535 /* Handle relative coordinates. This switch statement attempts
536 to determine _what_ the coords are relative to. This is
537 underspecified in the 12 Apr working draft. */
538 switch (ctx->cmd)
539 {
540 case 'l':
541 case 'm':
542 case 'c':
543 case 's':
544 case 'q':
545 case 't':
546 if ( ctx->param & 1 ) {
547 val += ctx->cpy; /* odd param, y */
548 } else {
549 val += ctx->cpx; /* even param, x */
550 }
551 break;
552 case 'a':
553 /* rule: sixth and seventh are x and y, rest are not
554 relative */
555 if (ctx->param == 5)
556 val += ctx->cpx;
557 else if (ctx->param == 6)
558 val += ctx->cpy;
559 break;
560 case 'h':
561 /* rule: x-relative */
562 val += ctx->cpx;
563 break;
564 case 'v':
565 /* rule: y-relative */
566 val += ctx->cpy;
567 break;
568 }
569 }
570 ctx->params[ctx->param++] = val;
571 rsvg_parse_path_do_cmd (ctx, FALSE);
572 if (c=='.') {
573 in_num = TRUE;
574 val = 0;
575 in_frac = TRUE;
576 in_exp = FALSE;
577 frac = 1;
578 }
579 else {
580 in_num = FALSE;
581 in_frac = FALSE;
582 in_exp = FALSE;
583 }
584 }
586 if (c == '\0')
587 break;
588 else if ((c == '+' || c == '-') && !exp_wait_sign)
589 {
590 sign = c == '+' ? 1 : -1;;
591 val = 0;
592 in_num = TRUE;
593 in_frac = FALSE;
594 in_exp = FALSE;
595 exp = 0;
596 exp_sign = 1;
597 exp_wait_sign = FALSE;
598 }
599 else if (c == 'z' || c == 'Z')
600 {
601 if (ctx->param)
602 rsvg_parse_path_do_cmd (ctx, TRUE);
603 rsvg_bpath_def_closepath (ctx->bpath);
605 ctx->cmd = 'm';
606 ctx->params[0] = ctx->cpx = ctx->rpx = ctx->spx;
607 ctx->params[1] = ctx->cpy = ctx->rpy = ctx->spy;
608 ctx->param = 2;
609 }
610 else if (c >= 'A' && c <= 'Z' && c != 'E')
611 {
612 if (ctx->param)
613 rsvg_parse_path_do_cmd (ctx, TRUE);
614 ctx->cmd = c + 'a' - 'A';
615 ctx->rel = FALSE;
616 }
617 else if (c >= 'a' && c <= 'z' && c != 'e')
618 {
619 if (ctx->param)
620 rsvg_parse_path_do_cmd (ctx, TRUE);
621 ctx->cmd = c;
622 ctx->rel = TRUE;
623 }
624 /* else c _should_ be whitespace or , */
625 }
626 }
629 NArtBpath *sp_svg_read_path(gchar const *str)
630 {
631 RSVGParsePathCtx ctx;
632 NArtBpath *bpath;
634 ctx.bpath = gnome_canvas_bpath_def_new ();
635 ctx.cpx = 0.0;
636 ctx.cpy = 0.0;
637 ctx.cmd = 0;
638 ctx.param = 0;
640 rsvg_parse_path_data (&ctx, str);
642 if (ctx.param && ctx.cmd != 'm') {
643 rsvg_parse_path_do_cmd (&ctx, TRUE);
644 }
646 gnome_canvas_bpath_def_art_finish (ctx.bpath);
648 bpath = g_new (NArtBpath, ctx.bpath->n_bpath);
649 memcpy (bpath, ctx.bpath->bpath, ctx.bpath->n_bpath * sizeof (NArtBpath));
650 g_assert ((bpath + ctx.bpath->n_bpath - 1)->code == NR_END);
651 gnome_canvas_bpath_def_unref (ctx.bpath);
653 return bpath;
654 }
656 gchar *sp_svg_write_path(NArtBpath const *bpath)
657 {
658 Inkscape::SVGOStringStream os;
659 bool closed=false;
661 g_return_val_if_fail (bpath != NULL, NULL);
663 for (int i = 0; bpath[i].code != NR_END; i++){
664 if (i) {
665 os << " ";
666 }
667 switch (bpath [i].code){
668 case NR_LINETO:
669 os << "L " << bpath[i].x3 << "," << bpath[i].y3;
670 break;
672 case NR_CURVETO:
673 os << "C " << bpath[i].x1 << "," << bpath[i].y1
674 << " " << bpath[i].x2 << "," << bpath[i].y2
675 << " " << bpath[i].x3 << "," << bpath[i].y3;
676 break;
678 case NR_MOVETO_OPEN:
679 case NR_MOVETO:
680 if (closed) {
681 os << "z ";
682 }
683 closed = ( bpath[i].code == NR_MOVETO );
684 os << "M " << bpath[i].x3 << "," << bpath[i].y3;
685 break;
686 default:
687 g_assert_not_reached ();
688 }
689 }
690 if (closed) {
691 os << " z ";
692 }
694 // std::string s = os.str();
695 // gchar *ret = g_strdup(s.c_str());
696 // delete (s);
697 // return ret;
698 return g_strdup (os.str().c_str());
699 }
701 /*
702 Local Variables:
703 mode:c++
704 c-file-style:"stroustrup"
705 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
706 indent-tabs-mode:nil
707 fill-column:99
708 End:
709 */
710 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :