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

Revision 18609, 13.4 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18608, 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  switch (ctx->cmd)
198    {
199    case 'm':
200      /* moveto */
201      if (ctx->param == 2 || final)
202        {
203          rsvg_parse_path_default_xy (ctx, 2);
204          rsvg_bpath_def_moveto (ctx->bpath,
205                                 ctx->params[0], ctx->params[1]);
206          ctx->cpx = ctx->rpx = ctx->params[0];
207          ctx->cpy = ctx->rpy = ctx->params[1];
208          ctx->param = 0;
209        }
210      break;
211    case 'l':
212      /* lineto */
213      if (ctx->param == 2 || final)
214        {
215          rsvg_parse_path_default_xy (ctx, 2);
216          rsvg_bpath_def_lineto (ctx->bpath,
217                                 ctx->params[0], ctx->params[1]);
218          ctx->cpx = ctx->rpx = ctx->params[0];
219          ctx->cpy = ctx->rpy = ctx->params[1];
220          ctx->param = 0;
221        }
222      break;
223    case 'c':
224      /* curveto */
225      if (ctx->param == 6 || final)
226        {
227          rsvg_parse_path_default_xy (ctx, 6);
228          x1 = ctx->params[0];
229          y1 = ctx->params[1];
230          x2 = ctx->params[2];
231          y2 = ctx->params[3];
232          x3 = ctx->params[4];
233          y3 = ctx->params[5];
234          rsvg_bpath_def_curveto (ctx->bpath,
235                                  x1, y1, x2, y2, x3, y3);
236          ctx->rpx = x2;
237          ctx->rpy = y2;
238          ctx->cpx = x3;
239          ctx->cpy = y3;
240          ctx->param = 0;
241        }
242      break;
243    case 's':
244      /* smooth curveto */
245      if (ctx->param == 4 || final)
246        {
247          rsvg_parse_path_default_xy (ctx, 4);
248          x1 = 2 * ctx->cpx - ctx->rpx;
249          y1 = 2 * ctx->cpy - ctx->rpy;
250          x2 = ctx->params[0];
251          y2 = ctx->params[1];
252          x3 = ctx->params[2];
253          y3 = ctx->params[3];
254          rsvg_bpath_def_curveto (ctx->bpath,
255                                  x1, y1, x2, y2, x3, y3);
256          ctx->rpx = x2;
257          ctx->rpy = y2;
258          ctx->cpx = x3;
259          ctx->cpy = y3;
260          ctx->param = 0;
261        }
262      break;
263    case 'h':
264      /* horizontal lineto */
265      if (ctx->param == 1)
266        {
267          rsvg_bpath_def_lineto (ctx->bpath,
268                                 ctx->params[0], ctx->cpy);
269          ctx->cpx = ctx->rpx = ctx->params[0];
270          ctx->param = 0;
271        }
272      break;
273    case 'v':
274      /* vertical lineto */
275      if (ctx->param == 1)
276        {
277          rsvg_bpath_def_lineto (ctx->bpath,
278                                 ctx->cpx, ctx->params[0]);
279          ctx->cpy = ctx->rpy = ctx->params[0];
280          ctx->param = 0;
281        }
282      break;
283    case 'q':
284      /* quadratic bezier curveto */
285
286      /* non-normative reference:
287         http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
288      */
289      if (ctx->param == 4 || final)
290        {
291          rsvg_parse_path_default_xy (ctx, 4);
292          /* raise quadratic bezier to cubic */
293          x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
294          y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
295          x3 = ctx->params[2];
296          y3 = ctx->params[3];
297          x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
298          y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
299          rsvg_bpath_def_curveto (ctx->bpath,
300                                  x1, y1, x2, y2, x3, y3);
301          ctx->rpx = x2;
302          ctx->rpy = y2;
303          ctx->cpx = x3;
304          ctx->cpy = y3;
305          ctx->param = 0;
306        }
307      break;
308    case 't':
309      /* Truetype quadratic bezier curveto */
310      if (ctx->param == 2 || final)
311        {
312          double xc, yc; /* quadratic control point */
313
314          xc = 2 * ctx->cpx - ctx->rpx;
315          yc = 2 * ctx->cpy - ctx->rpy;
316          /* generate a quadratic bezier with control point = xc, yc */
317          x1 = (ctx->cpx + 2 * xc) * (1.0 / 3.0);
318          y1 = (ctx->cpy + 2 * yc) * (1.0 / 3.0);
319          x3 = ctx->params[0];
320          y3 = ctx->params[1];
321          x2 = (x3 + 2 * xc) * (1.0 / 3.0);
322          y2 = (y3 + 2 * yc) * (1.0 / 3.0);
323          rsvg_bpath_def_curveto (ctx->bpath,
324                                  x1, y1, x2, y2, x3, y3);
325          ctx->rpx = xc;
326          ctx->rpy = yc;
327          ctx->cpx = x3;
328          ctx->cpy = y3;
329          ctx->param = 0;
330        }
331      else if (final)
332        {
333          if (ctx->param > 2)
334            {
335              rsvg_parse_path_default_xy (ctx, 4);
336              /* raise quadratic bezier to cubic */
337              x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
338              y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
339              x3 = ctx->params[2];
340              y3 = ctx->params[3];
341              x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
342              y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
343              rsvg_bpath_def_curveto (ctx->bpath,
344                                      x1, y1, x2, y2, x3, y3);
345              ctx->rpx = x2;
346              ctx->rpy = y2;
347              ctx->cpx = x3;
348              ctx->cpy = y3;
349            }
350          else
351            {
352              rsvg_parse_path_default_xy (ctx, 2);
353              rsvg_bpath_def_lineto (ctx->bpath,
354                                     ctx->params[0], ctx->params[1]);
355              ctx->cpx = ctx->rpx = ctx->params[0];
356              ctx->cpy = ctx->rpy = ctx->params[1];
357            }
358          ctx->param = 0;
359        }
360      break;
361    case 'a':
362      if (ctx->param == 7 || final)
363        {
364          rsvg_path_arc (ctx,
365                        ctx->params[0], ctx->params[1], ctx->params[2],
366                        ctx->params[3], ctx->params[4],
367                        ctx->params[5], ctx->params[6]);
368          ctx->param = 0;
369        }
370      break;
371    default:
372      ctx->param = 0;
373    }
374}
375
376static void
377rsvg_parse_path_data (RSVGParsePathCtx *ctx, const char *data)
378{
379  int i = 0;
380  double val = 0;
381  char c = 0;
382  gboolean in_num = FALSE;
383  gboolean in_frac = FALSE;
384  gboolean in_exp = FALSE;
385  gboolean exp_wait_sign = FALSE;
386  int sign = 0;
387  int exp = 0;
388  int exp_sign = 0;
389  double frac = 0.0;
390
391  in_num = FALSE;
392  for (i = 0; ; i++)
393    {
394      c = data[i];
395      if (c >= '0' && c <= '9')
396        {
397          /* digit */
398          if (in_num)
399            {
400              if (in_exp)
401                {
402                  exp = (exp * 10) + c - '0';
403                  exp_wait_sign = FALSE;
404                }
405              else if (in_frac)
406                val += (frac *= 0.1) * (c - '0');
407              else
408                val = (val * 10) + c - '0';
409            }
410          else
411            {
412              in_num = TRUE;
413              in_frac = FALSE;
414              in_exp = FALSE;
415              exp = 0;
416              exp_sign = 1;
417              exp_wait_sign = FALSE;
418              val = c - '0';
419              sign = 1;
420            }
421        }
422      else if (c == '.')
423        {
424          if (!in_num)
425            {
426              in_num = TRUE;
427              val = 0;
428            }
429          in_frac = TRUE;
430          frac = 1;
431        }
432      else if ((c == 'E' || c == 'e') && in_num)
433        {
434          in_exp = TRUE;
435          exp_wait_sign = TRUE;
436          exp = 0;
437          exp_sign = 1;
438        }
439      else if ((c == '+' || c == '-') && in_exp)
440        {
441          exp_sign = c == '+' ? 1 : -1;
442        }
443      else if (in_num)
444        {
445          /* end of number */
446
447          val *= sign * pow (10, exp_sign * exp);
448          if (ctx->rel)
449            {
450              /* Handle relative coordinates. This switch statement attempts
451                 to determine _what_ the coords are relative to. This is
452                 underspecified in the 12 Apr working draft. */
453              switch (ctx->cmd)
454                {
455                case 'l':
456                case 'm':
457                case 'c':
458                case 's':
459                case 'q':
460                case 't':
461#ifndef RSVGV_RELATIVE
462                  /* rule: even-numbered params are x-relative, odd-numbered
463                     are y-relative */
464                  if ((ctx->param & 1) == 0)
465                    val += ctx->cpx;
466                  else if ((ctx->param & 1) == 1)
467                    val += ctx->cpy;
468                  break;
469#else
470                  /* rule: even-numbered params are x-relative, odd-numbered
471                     are y-relative */
472                  if (ctx->param == 0 || (ctx->param % 2 == 0))
473                    val += ctx->cpx;
474                  else
475                    val += ctx->cpy;
476                  break;
477#endif
478                case 'a':
479                  /* rule: sixth and seventh are x and y, rest are not
480                     relative */
481                  if (ctx->param == 5)
482                    val += ctx->cpx;
483                  else if (ctx->param == 6)
484                    val += ctx->cpy;
485                  break;
486                case 'h':
487                  /* rule: x-relative */
488                  val += ctx->cpx;
489                  break;
490                case 'v':
491                  /* rule: y-relative */
492                  val += ctx->cpy;
493                  break;
494                }
495            }
496          ctx->params[ctx->param++] = val;
497          rsvg_parse_path_do_cmd (ctx, FALSE);
498          in_num = FALSE;
499        }
500
501      if (c == '\0')
502        break;
503      else if ((c == '+' || c == '-') && !exp_wait_sign)
504        {
505          sign = c == '+' ? 1 : -1;;
506          val = 0;
507          in_num = TRUE;
508          in_frac = FALSE;
509          in_exp = FALSE;
510          exp = 0;
511          exp_sign = 1;
512          exp_wait_sign = FALSE;
513        }
514      else if (c == 'z' || c == 'Z')
515        {
516          if (ctx->param)
517            rsvg_parse_path_do_cmd (ctx, TRUE);
518          rsvg_bpath_def_closepath (ctx->bpath);
519        }
520      else if (c >= 'A' && c <= 'Z' && c != 'E')
521        {
522          if (ctx->param)
523            rsvg_parse_path_do_cmd (ctx, TRUE);
524          ctx->cmd = c + 'a' - 'A';
525          ctx->rel = FALSE;
526        }
527      else if (c >= 'a' && c <= 'z' && c != 'e')
528        {
529          if (ctx->param)
530            rsvg_parse_path_do_cmd (ctx, TRUE);
531          ctx->cmd = c;
532          ctx->rel = TRUE;
533        }
534      /* else c _should_ be whitespace or , */
535    }
536}
537
538RsvgBpathDef *
539rsvg_parse_path (const char *path_str)
540{
541  RSVGParsePathCtx ctx;
542
543  ctx.bpath = rsvg_bpath_def_new ();
544  ctx.cpx = 0.0;
545  ctx.cpy = 0.0;
546  ctx.cmd = 0;
547  ctx.param = 0;
548
549  rsvg_parse_path_data (&ctx, path_str);
550
551  if (ctx.param)
552    rsvg_parse_path_do_cmd (&ctx, TRUE);
553
554  return ctx.bpath;
555}
556
Note: See TracBrowser for help on using the repository browser.