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