source: trunk/third/librsvg/rsvg-path.c @ 17277

Revision 17277, 14.6 KB checked in by amb, 23 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r17276, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2   rsvg-path.c: Parse SVG path element data into bezier path.
3 
4   Copyright (C) 2000 Eazel, Inc.
5 
6   This program is free software; you can redistribute it and/or
7   modify it under the terms of the GNU General Public License as
8   published by the Free Software Foundation; either version 2 of the
9   License, or (at your option) any later version.
10 
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   General Public License for more details.
15 
16   You should have received a copy of the GNU General Public
17   License along with this program; if not, write to the
18   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   Boston, MA 02111-1307, USA.
20 
21   Author: Raph Levien <raph@artofcode.com>
22*/
23
24/* This is adapted from svg-path in Gill. */
25
26#include <string.h>
27#include <stdlib.h>
28#include <math.h>
29
30#include <glib.h>
31
32#include "rsvg-bpath-util.h"
33#include "rsvg-path.h"
34
35/* This module parses an SVG path element into an RsvgBpathDef.
36
37   At present, there is no support for <marker> or any other contextual
38   information from the SVG file. The API will need to change rather
39   significantly to support these.
40
41   Reference: SVG working draft 3 March 2000, section 8.
42*/
43
44#ifndef M_PI
45#define M_PI 3.14159265358979323846
46#endif  /*  M_PI  */
47
48typedef struct _RSVGParsePathCtx RSVGParsePathCtx;
49
50struct _RSVGParsePathCtx {
51  RsvgBpathDef *bpath;
52  double cpx, cpy;  /* current point */
53  double rpx, rpy;  /* reflection point (for 's' and 't' commands) */
54  char cmd;         /* current command (lowercase) */
55  int param;        /* parameter number */
56  gboolean rel;     /* true if relative coords */
57  double params[7]; /* parameters that have been parsed */
58};
59
60static void
61rsvg_path_arc_segment (RSVGParsePathCtx *ctx,
62                      double xc, double yc,
63                      double th0, double th1,
64                      double rx, double ry, double x_axis_rotation)
65{
66  double sin_th, cos_th;
67  double a00, a01, a10, a11;
68  double x1, y1, x2, y2, x3, y3;
69  double t;
70  double th_half;
71
72  sin_th = sin (x_axis_rotation * (M_PI / 180.0));
73  cos_th = cos (x_axis_rotation * (M_PI / 180.0));
74  /* inverse transform compared with rsvg_path_arc */
75  a00 = cos_th * rx;
76  a01 = -sin_th * ry;
77  a10 = sin_th * rx;
78  a11 = cos_th * ry;
79
80  th_half = 0.5 * (th1 - th0);
81  t = (8.0 / 3.0) * sin (th_half * 0.5) * sin (th_half * 0.5) / sin (th_half);
82  x1 = xc + cos (th0) - t * sin (th0);
83  y1 = yc + sin (th0) + t * cos (th0);
84  x3 = xc + cos (th1);
85  y3 = yc + sin (th1);
86  x2 = x3 + t * sin (th1);
87  y2 = y3 - t * cos (th1);
88  rsvg_bpath_def_curveto (ctx->bpath,
89                                  a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
90                                  a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
91                                  a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
92}
93
94/**
95 * rsvg_path_arc: Add an RSVG arc to the path context.
96 * @ctx: Path context.
97 * @rx: Radius in x direction (before rotation).
98 * @ry: Radius in y direction (before rotation).
99 * @x_axis_rotation: Rotation angle for axes.
100 * @large_arc_flag: 0 for arc length <= 180, 1 for arc >= 180.
101 * @sweep: 0 for "negative angle", 1 for "positive angle".
102 * @x: New x coordinate.
103 * @y: New y coordinate.
104 *
105 **/
106static void
107rsvg_path_arc (RSVGParsePathCtx *ctx,
108              double rx, double ry, double x_axis_rotation,
109              int large_arc_flag, int sweep_flag,
110              double x, double y)
111{
112  double sin_th, cos_th;
113  double a00, a01, a10, a11;
114  double x0, y0, x1, y1, xc, yc;
115  double d, sfactor, sfactor_sq;
116  double th0, th1, th_arc;
117  int i, n_segs;
118
119  sin_th = sin (x_axis_rotation * (M_PI / 180.0));
120  cos_th = cos (x_axis_rotation * (M_PI / 180.0));
121  a00 = cos_th / rx;
122  a01 = sin_th / rx;
123  a10 = -sin_th / ry;
124  a11 = cos_th / ry;
125  x0 = a00 * ctx->cpx + a01 * ctx->cpy;
126  y0 = a10 * ctx->cpx + a11 * ctx->cpy;
127  x1 = a00 * x + a01 * y;
128  y1 = a10 * x + a11 * y;
129  /* (x0, y0) is current point in transformed coordinate space.
130     (x1, y1) is new point in transformed coordinate space.
131
132     The arc fits a unit-radius circle in this space.
133  */
134  d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
135  sfactor_sq = 1.0 / d - 0.25;
136  if (sfactor_sq < 0) sfactor_sq = 0;
137  sfactor = sqrt (sfactor_sq);
138  if (sweep_flag == large_arc_flag) sfactor = -sfactor;
139  xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
140  yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
141  /* (xc, yc) is center of the circle. */
142
143  th0 = atan2 (y0 - yc, x0 - xc);
144  th1 = atan2 (y1 - yc, x1 - xc);
145
146  th_arc = th1 - th0;
147  if (th_arc < 0 && sweep_flag)
148    th_arc += 2 * M_PI;
149  else if (th_arc > 0 && !sweep_flag)
150    th_arc -= 2 * M_PI;
151
152  n_segs = ceil (fabs (th_arc / (M_PI * 0.5 + 0.001)));
153
154  for (i = 0; i < n_segs; i++)
155    rsvg_path_arc_segment (ctx, xc, yc,
156                          th0 + i * th_arc / n_segs,
157                          th0 + (i + 1) * th_arc / n_segs,
158                          rx, ry, x_axis_rotation);
159
160  ctx->cpx = x;
161  ctx->cpy = y;
162}
163
164
165/* supply defaults for missing parameters, assuming relative coordinates
166   are to be interpreted as x,y */
167static void
168rsvg_parse_path_default_xy (RSVGParsePathCtx *ctx, int n_params)
169{
170  int i;
171
172  if (ctx->rel)
173    {
174      for (i = ctx->param; i < n_params; i++)
175        {
176          if (i > 2)
177            ctx->params[i] = ctx->params[i - 2];
178          else if (i == 1)
179            ctx->params[i] = ctx->cpy;
180          else if (i == 0)
181            /* we shouldn't get here (usually ctx->param > 0 as
182               precondition) */
183            ctx->params[i] = ctx->cpx;
184        }
185    }
186  else
187    {
188      for (i = ctx->param; i < n_params; i++)
189        ctx->params[i] = 0.0;
190    }
191}
192
193static void
194rsvg_parse_path_do_cmd (RSVGParsePathCtx *ctx, gboolean final)
195{
196  double x1, y1, x2, y2, x3, y3;
197
198#ifdef VERBOSE
199  int i;
200
201  g_print ("parse_path %c:", ctx->cmd);
202  for (i = 0; i < ctx->param; i++)
203    g_print (" %f", ctx->params[i]);
204  g_print (final ? ".\n" : "\n");
205#endif
206
207  switch (ctx->cmd)
208    {
209    case 'm':
210      /* moveto */
211      if (ctx->param == 2 || final)
212        {
213          rsvg_parse_path_default_xy (ctx, 2);
214#ifdef VERBOSE
215          g_print ("'m' moveto %g,%g\n",
216                   ctx->params[0], ctx->params[1]);
217#endif
218          rsvg_bpath_def_moveto (ctx->bpath,
219                                         ctx->params[0], ctx->params[1]);
220          ctx->cpx = ctx->rpx = ctx->params[0];
221          ctx->cpy = ctx->rpy = ctx->params[1];
222          ctx->param = 0;
223        }
224      break;
225    case 'l':
226      /* lineto */
227      if (ctx->param == 2 || final)
228        {
229          rsvg_parse_path_default_xy (ctx, 2);
230#ifdef VERBOSE
231          g_print ("'l' lineto %g,%g\n",
232                   ctx->params[0], ctx->params[1]);
233#endif
234          rsvg_bpath_def_lineto (ctx->bpath,
235                                         ctx->params[0], ctx->params[1]);
236          ctx->cpx = ctx->rpx = ctx->params[0];
237          ctx->cpy = ctx->rpy = ctx->params[1];
238          ctx->param = 0;
239        }
240      break;
241    case 'c':
242      /* curveto */
243      if (ctx->param == 6 || final)
244        {
245          rsvg_parse_path_default_xy (ctx, 6);
246          x1 = ctx->params[0];
247          y1 = ctx->params[1];
248          x2 = ctx->params[2];
249          y2 = ctx->params[3];
250          x3 = ctx->params[4];
251          y3 = ctx->params[5];
252#ifdef VERBOSE
253          g_print ("'c' curveto %g,%g %g,%g, %g,%g\n",
254                   x1, y1, x2, y2, x3, y3);
255#endif
256          rsvg_bpath_def_curveto (ctx->bpath,
257                                          x1, y1, x2, y2, x3, y3);
258          ctx->rpx = x2;
259          ctx->rpy = y2;
260          ctx->cpx = x3;
261          ctx->cpy = y3;
262          ctx->param = 0;
263        }
264      break;
265    case 's':
266      /* smooth curveto */
267      if (ctx->param == 4 || final)
268        {
269          rsvg_parse_path_default_xy (ctx, 4);
270          x1 = 2 * ctx->cpx - ctx->rpx;
271          y1 = 2 * ctx->cpy - ctx->rpy;
272          x2 = ctx->params[0];
273          y2 = ctx->params[1];
274          x3 = ctx->params[2];
275          y3 = ctx->params[3];
276#ifdef VERBOSE
277          g_print ("'s' curveto %g,%g %g,%g, %g,%g\n",
278                   x1, y1, x2, y2, x3, y3);
279#endif
280          rsvg_bpath_def_curveto (ctx->bpath,
281                                          x1, y1, x2, y2, x3, y3);
282          ctx->rpx = x2;
283          ctx->rpy = y2;
284          ctx->cpx = x3;
285          ctx->cpy = y3;
286          ctx->param = 0;
287        }
288      break;
289    case 'h':
290      /* horizontal lineto */
291      if (ctx->param == 1)
292        {
293#ifdef VERBOSE
294          g_print ("'h' lineto %g,%g\n",
295                   ctx->params[0], ctx->cpy);
296#endif
297          rsvg_bpath_def_lineto (ctx->bpath,
298                                         ctx->params[0], ctx->cpy);
299          ctx->cpx = ctx->rpx = ctx->params[0];
300          ctx->param = 0;
301        }
302      break;
303    case 'v':
304      /* vertical lineto */
305      if (ctx->param == 1)
306        {
307#ifdef VERBOSE
308          g_print ("'v' lineto %g,%g\n",
309                   ctx->cpx, ctx->params[0]);
310#endif
311          rsvg_bpath_def_lineto (ctx->bpath,
312                                         ctx->cpx, ctx->params[0]);
313          ctx->cpy = ctx->rpy = ctx->params[0];
314          ctx->param = 0;
315        }
316      break;
317    case 'q':
318      /* quadratic bezier curveto */
319
320      /* non-normative reference:
321         http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
322      */
323      if (ctx->param == 4 || final)
324        {
325          rsvg_parse_path_default_xy (ctx, 4);
326          /* raise quadratic bezier to cubic */
327          x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
328          y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
329          x3 = ctx->params[2];
330          y3 = ctx->params[3];
331          x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
332          y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
333#ifdef VERBOSE
334          g_print ("'q' curveto %g,%g %g,%g, %g,%g\n",
335                   x1, y1, x2, y2, x3, y3);
336#endif
337          rsvg_bpath_def_curveto (ctx->bpath,
338                                          x1, y1, x2, y2, x3, y3);
339          ctx->rpx = x2;
340          ctx->rpy = y2;
341          ctx->cpx = x3;
342          ctx->cpy = y3;
343          ctx->param = 0;
344        }
345      break;
346    case 't':
347      /* Truetype quadratic bezier curveto */
348      if (ctx->param == 2 || final)
349        {
350          double xc, yc; /* quadratic control point */
351
352          xc = 2 * ctx->cpx - ctx->rpx;
353          yc = 2 * ctx->cpy - ctx->rpy;
354          /* generate a quadratic bezier with control point = xc, yc */
355          x1 = (ctx->cpx + 2 * xc) * (1.0 / 3.0);
356          y1 = (ctx->cpy + 2 * yc) * (1.0 / 3.0);
357          x3 = ctx->params[0];
358          y3 = ctx->params[1];
359          x2 = (x3 + 2 * xc) * (1.0 / 3.0);
360          y2 = (y3 + 2 * yc) * (1.0 / 3.0);
361#ifdef VERBOSE
362          g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
363                   x1, y1, x2, y2, x3, y3);
364#endif
365          rsvg_bpath_def_curveto (ctx->bpath,
366                                          x1, y1, x2, y2, x3, y3);
367          ctx->rpx = xc;
368          ctx->rpy = yc;
369          ctx->cpx = x3;
370          ctx->cpy = y3;
371          ctx->param = 0;
372        }
373      else if (final)
374        {
375          if (ctx->param > 2)
376            {
377              rsvg_parse_path_default_xy (ctx, 4);
378              /* raise quadratic bezier to cubic */
379              x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
380              y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
381              x3 = ctx->params[2];
382              y3 = ctx->params[3];
383              x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
384              y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
385#ifdef VERBOSE
386              g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
387                       x1, y1, x2, y2, x3, y3);
388#endif
389              rsvg_bpath_def_curveto (ctx->bpath,
390                                              x1, y1, x2, y2, x3, y3);
391              ctx->rpx = x2;
392              ctx->rpy = y2;
393              ctx->cpx = x3;
394              ctx->cpy = y3;
395            }
396          else
397            {
398              rsvg_parse_path_default_xy (ctx, 2);
399#ifdef VERBOSE
400              g_print ("'t' lineto %g,%g\n",
401                       ctx->params[0], ctx->params[1]);
402#endif
403              rsvg_bpath_def_lineto (ctx->bpath,
404                                             ctx->params[0], ctx->params[1]);
405              ctx->cpx = ctx->rpx = ctx->params[0];
406              ctx->cpy = ctx->rpy = ctx->params[1];
407            }
408          ctx->param = 0;
409        }
410      break;
411    case 'a':
412      if (ctx->param == 7 || final)
413        {
414          rsvg_path_arc (ctx,
415                        ctx->params[0], ctx->params[1], ctx->params[2],
416                        ctx->params[3], ctx->params[4],
417                        ctx->params[5], ctx->params[6]);
418          ctx->param = 0;
419        }
420      break;
421    default:
422      ctx->param = 0;
423    }
424}
425
426static void
427rsvg_parse_path_data (RSVGParsePathCtx *ctx, const char *data)
428{
429  int i = 0;
430  double val = 0;
431  char c = 0;
432  gboolean in_num = FALSE;
433  gboolean in_frac = FALSE;
434  gboolean in_exp = FALSE;
435  gboolean exp_wait_sign = FALSE;
436  int sign = 0;
437  int exp = 0;
438  int exp_sign = 0;
439  double frac = 0.0;
440
441  in_num = FALSE;
442  for (i = 0; ; i++)
443    {
444      c = data[i];
445      if (c >= '0' && c <= '9')
446        {
447          /* digit */
448          if (in_num)
449            {
450              if (in_exp)
451                {
452                  exp = (exp * 10) + c - '0';
453                  exp_wait_sign = FALSE;
454                }
455              else if (in_frac)
456                val += (frac *= 0.1) * (c - '0');
457              else
458                val = (val * 10) + c - '0';
459            }
460          else
461            {
462              in_num = TRUE;
463              in_frac = FALSE;
464              in_exp = FALSE;
465              exp = 0;
466              exp_sign = 1;
467              exp_wait_sign = FALSE;
468              val = c - '0';
469              sign = 1;
470            }
471        }
472      else if (c == '.')
473        {
474          if (!in_num)
475            {
476              in_num = TRUE;
477              val = 0;
478            }
479          in_frac = TRUE;
480          frac = 1;
481        }
482      else if ((c == 'E' || c == 'e') && in_num)
483        {
484          in_exp = TRUE;
485          exp_wait_sign = TRUE;
486          exp = 0;
487          exp_sign = 1;
488        }
489      else if ((c == '+' || c == '-') && in_exp)
490        {
491          exp_sign = c == '+' ? 1 : -1;
492        }
493      else if (in_num)
494        {
495          /* end of number */
496
497          val *= sign * pow (10, exp_sign * exp);
498          if (ctx->rel)
499            {
500              /* Handle relative coordinates. This switch statement attempts
501                 to determine _what_ the coords are relative to. This is
502                 underspecified in the 12 Apr working draft. */
503              switch (ctx->cmd)
504                {
505                case 'l':
506                case 'm':
507                case 'c':
508                case 's':
509                case 'q':
510                case 't':
511#ifndef RSVGV_RELATIVE
512                  /* rule: even-numbered params are x-relative, odd-numbered
513                     are y-relative */
514                  if ((ctx->param & 1) == 0)
515                    val += ctx->cpx;
516                  else if ((ctx->param & 1) == 1)
517                    val += ctx->cpy;
518                  break;
519#else
520                  /* rule: even-numbered params are x-relative, odd-numbered
521                     are y-relative */
522                  if (ctx->param == 0 || (ctx->param % 2 ==0))
523                    val += ctx->cpx;
524                  else
525                    val += ctx->cpy;
526                  break;
527#endif
528                case 'a':
529                  /* rule: sixth and seventh are x and y, rest are not
530                     relative */
531                  if (ctx->param == 5)
532                    val += ctx->cpx;
533                  else if (ctx->param == 6)
534                    val += ctx->cpy;
535                  break;
536                case 'h':
537                  /* rule: x-relative */
538                  val += ctx->cpx;
539                  break;
540                case 'v':
541                  /* rule: y-relative */
542                  val += ctx->cpy;
543                  break;
544                }
545            }
546          ctx->params[ctx->param++] = val;
547          rsvg_parse_path_do_cmd (ctx, FALSE);
548          in_num = FALSE;
549        }
550
551      if (c == '\0')
552        break;
553      else if ((c == '+' || c == '-') && !exp_wait_sign)
554        {
555          sign = c == '+' ? 1 : -1;;
556          val = 0;
557          in_num = TRUE;
558          in_frac = FALSE;
559          in_exp = FALSE;
560          exp = 0;
561          exp_sign = 1;
562          exp_wait_sign = FALSE;
563        }
564      else if (c == 'z' || c == 'Z')
565        {
566          if (ctx->param)
567            rsvg_parse_path_do_cmd (ctx, TRUE);
568#ifdef VERBOSE
569          g_print ("'z' closepath\n");
570#endif
571          rsvg_bpath_def_closepath (ctx->bpath);
572        }
573      else if (c >= 'A' && c <= 'Z' && c != 'E')
574        {
575          if (ctx->param)
576            rsvg_parse_path_do_cmd (ctx, TRUE);
577          ctx->cmd = c + 'a' - 'A';
578          ctx->rel = FALSE;
579        }
580      else if (c >= 'a' && c <= 'z' && c != 'e')
581        {
582          if (ctx->param)
583            rsvg_parse_path_do_cmd (ctx, TRUE);
584          ctx->cmd = c;
585          ctx->rel = TRUE;
586        }
587      /* else c _should_ be whitespace or , */
588    }
589}
590
591RsvgBpathDef *
592rsvg_parse_path (const char *path_str)
593{
594  RSVGParsePathCtx ctx;
595
596  ctx.bpath = rsvg_bpath_def_new ();
597  ctx.cpx = 0.0;
598  ctx.cpy = 0.0;
599  ctx.cmd = 0;
600  ctx.param = 0;
601
602  rsvg_parse_path_data (&ctx, path_str);
603
604  if (ctx.param)
605    rsvg_parse_path_do_cmd (&ctx, TRUE);
606
607  return ctx.bpath;
608}
609
Note: See TracBrowser for help on using the repository browser.