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

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