1 /* gEDA - GPL Electronic Design Automation
2 * libgeda - gEDA's library
3 * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
5 * Code originally from librsvg 2.22.2 (LGPL) Copyright (C) 2000 Eazel, Inc.
7 * Author: Raph Levien <raph@artofcode.com>
8 * rsvg-path.c: Parse SVG path element data into bezier path.
9 * rsvg-bpath-util.c: Data structure and convenience functions for
10 * creating bezier paths.
12 * Adapted for gEDA by Peter Clifton <pcjc2@cam.ac.uk>
14 * THIS FILE IS LGPL LICENSED, gEDA AS A WHOLE IS GPL LICENSED
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU Library General Public License as
18 * published by the Free Software Foundation; either version 2 of the
19 * License, or (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * Library General Public License for more details.
26 * You should have received a copy of the GNU Library General Public
27 * License along with this program; if not, write to the
28 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
29 * Boston, MA 02111-1307, USA.
40 #include <glib/gmem.h>
41 #include <glib/gmessages.h>
42 #include <glib/gtypes.h>
44 #include "libgeda_priv.h"
46 #define NUM_BEZIER_SEGMENTS 100
49 PATH
*s_path_new (void)
53 path
= g_new (PATH
, 1);
54 path
->num_sections
= 0;
55 path
->num_sections_max
= 16;
56 path
->sections
= g_new (PATH_SECTION
, path
->num_sections_max
);
62 PATH
*s_path_new_from (PATH_SECTION
*sections
)
67 g_return_val_if_fail (sections
!= NULL
, NULL
);
69 for (i
= 0; sections
[i
].code
!= PATH_END
; i
++);
73 path
= g_new (PATH
, 1);
75 path
->num_sections
= i
;
76 path
->num_sections_max
= i
;
77 path
->sections
= g_new (PATH_SECTION
, i
);
79 memcpy (path
->sections
, sections
, i
* sizeof (PATH_SECTION
));
84 void s_path_free (PATH
* path
)
86 g_return_if_fail (path
!= NULL
);
88 g_free (path
->sections
);
93 void s_path_moveto (PATH
*path
, double x
, double y
)
95 PATH_SECTION
*sections
;
98 g_return_if_fail (path
!= NULL
);
100 /* if the last command was a moveto then change that last moveto instead of
101 creating a new one */
102 sections
= path
->sections
;
103 num_sections
= path
->num_sections
;
105 if (num_sections
> 0)
106 if (sections
[num_sections
- 1].code
== PATH_MOVETO_OPEN
) {
107 sections
[num_sections
- 1].x3
= x
;
108 sections
[num_sections
- 1].y3
= y
;
112 num_sections
= path
->num_sections
++;
114 if (num_sections
== path
->num_sections_max
)
115 path
->sections
= g_realloc (path
->sections
, (path
->num_sections_max
<<= 1) * sizeof (PATH_SECTION
));
116 sections
= path
->sections
;
117 sections
[num_sections
].code
= PATH_MOVETO_OPEN
;
118 sections
[num_sections
].x3
= x
;
119 sections
[num_sections
].y3
= y
;
123 void s_path_lineto (PATH
*path
, double x
, double y
)
125 PATH_SECTION
*sections
;
128 g_return_if_fail (path
!= NULL
);
130 num_sections
= path
->num_sections
++;
132 if (num_sections
== path
->num_sections_max
)
133 path
->sections
= g_realloc (path
->sections
, (path
->num_sections_max
<<= 1) * sizeof (PATH_SECTION
));
134 sections
= path
->sections
;
135 sections
[num_sections
].code
= PATH_LINETO
;
136 sections
[num_sections
].x3
= x
;
137 sections
[num_sections
].y3
= y
;
141 void s_path_curveto (PATH
*path
, double x1
, double y1
,
142 double x2
, double y2
, double x3
, double y3
)
144 PATH_SECTION
*sections
;
147 g_return_if_fail (path
!= NULL
);
149 num_sections
= path
->num_sections
++;
151 if (num_sections
== path
->num_sections_max
)
152 path
->sections
= g_realloc (path
->sections
, (path
->num_sections_max
<<= 1) * sizeof (PATH_SECTION
));
153 sections
= path
->sections
;
154 sections
[num_sections
].code
= PATH_CURVETO
;
155 sections
[num_sections
].x1
= x1
;
156 sections
[num_sections
].y1
= y1
;
157 sections
[num_sections
].x2
= x2
;
158 sections
[num_sections
].y2
= y2
;
159 sections
[num_sections
].x3
= x3
;
160 sections
[num_sections
].y3
= y3
;
164 void s_path_art_finish (PATH
* path
)
168 g_return_if_fail (path
!= NULL
);
170 num_sections
= path
->num_sections
++;
172 if (num_sections
== path
->num_sections_max
)
173 path
->sections
= g_realloc (path
->sections
, (path
->num_sections_max
<<= 1) * sizeof (PATH_SECTION
));
174 path
->sections
[num_sections
].code
= PATH_END
;
178 /* This module parses an SVG style path element into a PATH.
180 At present, there is no support for <marker> or any other contextual
181 information from the SVG file. The API will need to change rather
182 significantly to support these.
184 Reference: SVG working draft 3 March 2000, section 8.
187 typedef struct _RSVGParsePathCtx RSVGParsePathCtx
;
189 struct _RSVGParsePathCtx
{
191 double cpx
, cpy
; /* current point */
192 double rpx
, rpy
; /* reflection point (for 's' and 't' commands) */
193 double mpx
, mpy
; /* Last moved to point (for path closures) */
194 char cmd
; /* current command (lowercase) */
195 int param
; /* parameter number */
196 gboolean rel
; /* true if relative coords */
197 double params
[7]; /* parameters that have been parsed */
201 static void s_path_arc_segment (RSVGParsePathCtx
* ctx
,
202 double xc
, double yc
, double th0
, double th1
,
203 double rx
, double ry
, double x_axis_rotation
)
205 double sin_th
, cos_th
;
206 double a00
, a01
, a10
, a11
;
207 double x1
, y1
, x2
, y2
, x3
, y3
;
211 sin_th
= sin (x_axis_rotation
* (M_PI
/ 180.0));
212 cos_th
= cos (x_axis_rotation
* (M_PI
/ 180.0));
213 /* inverse transform compared with s_path_arc */
219 th_half
= 0.5 * (th1
- th0
);
220 t
= (8.0 / 3.0) * sin (th_half
* 0.5) * sin (th_half
* 0.5) / sin (th_half
);
221 x1
= xc
+ cos (th0
) - t
* sin (th0
);
222 y1
= yc
+ sin (th0
) + t
* cos (th0
);
225 x2
= x3
+ t
* sin (th1
);
226 y2
= y3
- t
* cos (th1
);
227 s_path_curveto (ctx
->path
,
228 a00
* x1
+ a01
* y1
, a10
* x1
+ a11
* y1
,
229 a00
* x2
+ a01
* y2
, a10
* x2
+ a11
* y2
,
230 a00
* x3
+ a01
* y3
, a10
* x3
+ a11
* y3
);
235 * s_path_arc: Add an arc to the path context.
236 * @ctx: Path context.
237 * @rx: Radius in x direction (before rotation).
238 * @ry: Radius in y direction (before rotation).
239 * @x_axis_rotation: Rotation angle for axes.
240 * @large_arc_flag: 0 for arc length <= 180, 1 for arc >= 180.
241 * @sweep: 0 for "negative angle", 1 for "positive angle".
242 * @x: New x coordinate.
243 * @y: New y coordinate.
246 static void s_path_arc (RSVGParsePathCtx
* ctx
,
247 double rx
, double ry
, double x_axis_rotation
,
248 int large_arc_flag
, int sweep_flag
, double x
, double y
)
250 double sin_th
, cos_th
;
251 double a00
, a01
, a10
, a11
;
252 double x0
, y0
, x1
, y1
, xc
, yc
;
253 double d
, sfactor
, sfactor_sq
;
254 double th0
, th1
, th_arc
;
257 /* Check that neither radius is zero, since its isn't either
258 geometrically or mathematically meaningful and will
259 cause divide by zero and subsequent NaNs. We should
260 really do some ranged check ie -0.001 < x < 000.1 rather
261 can just a straight check again zero.
263 if ((rx
== 0.0) || (ry
== 0.0))
266 sin_th
= sin (x_axis_rotation
* (M_PI
/ 180.0));
267 cos_th
= cos (x_axis_rotation
* (M_PI
/ 180.0));
272 x0
= a00
* ctx
->cpx
+ a01
* ctx
->cpy
;
273 y0
= a10
* ctx
->cpx
+ a11
* ctx
->cpy
;
274 x1
= a00
* x
+ a01
* y
;
275 y1
= a10
* x
+ a11
* y
;
276 /* (x0, y0) is current point in transformed coordinate space.
277 (x1, y1) is new point in transformed coordinate space.
279 The arc fits a unit-radius circle in this space.
281 d
= (x1
- x0
) * (x1
- x0
) + (y1
- y0
) * (y1
- y0
);
282 sfactor_sq
= 1.0 / d
- 0.25;
285 sfactor
= sqrt (sfactor_sq
);
286 if (sweep_flag
== large_arc_flag
)
288 xc
= 0.5 * (x0
+ x1
) - sfactor
* (y1
- y0
);
289 yc
= 0.5 * (y0
+ y1
) + sfactor
* (x1
- x0
);
290 /* (xc, yc) is center of the circle. */
292 th0
= atan2 (y0
- yc
, x0
- xc
);
293 th1
= atan2 (y1
- yc
, x1
- xc
);
296 if (th_arc
< 0 && sweep_flag
)
298 else if (th_arc
> 0 && !sweep_flag
)
301 n_segs
= ceil (fabs (th_arc
/ (M_PI
* 0.5 + 0.001)));
303 for (i
= 0; i
< n_segs
; i
++)
304 s_path_arc_segment (ctx
, xc
, yc
,
305 th0
+ i
* th_arc
/ n_segs
,
306 th0
+ (i
+ 1) * th_arc
/ n_segs
, rx
, ry
, x_axis_rotation
);
313 /* supply defaults for missing parameters, assuming relative coordinates
314 are to be interpreted as x,y */
315 static void s_path_parse_default_xy (RSVGParsePathCtx
* ctx
, int n_params
)
320 for (i
= ctx
->param
; i
< n_params
; i
++) {
322 ctx
->params
[i
] = ctx
->params
[i
- 2];
324 ctx
->params
[i
] = ctx
->cpy
;
326 /* we shouldn't get here (usually ctx->param > 0 as
328 ctx
->params
[i
] = ctx
->cpx
;
331 for (i
= ctx
->param
; i
< n_params
; i
++)
332 ctx
->params
[i
] = 0.0;
337 static void s_path_parse_do_cmd (RSVGParsePathCtx
* ctx
, gboolean final
)
339 double x1
, y1
, x2
, y2
, x3
, y3
;
344 if (ctx
->param
== 2 || final
) {
345 s_path_parse_default_xy (ctx
, 2);
346 s_path_moveto (ctx
->path
, ctx
->params
[0], ctx
->params
[1]);
347 ctx
->mpx
= ctx
->cpx
= ctx
->rpx
= ctx
->params
[0];
348 ctx
->mpy
= ctx
->cpy
= ctx
->rpy
= ctx
->params
[1];
350 ctx
->cmd
= 'l'; /* implicit linetos after a moveto */
355 if (ctx
->param
== 2 || final
) {
356 s_path_parse_default_xy (ctx
, 2);
357 s_path_lineto (ctx
->path
, ctx
->params
[0], ctx
->params
[1]);
358 ctx
->cpx
= ctx
->rpx
= ctx
->params
[0];
359 ctx
->cpy
= ctx
->rpy
= ctx
->params
[1];
365 if (ctx
->param
== 6 || final
) {
366 s_path_parse_default_xy (ctx
, 6);
373 s_path_curveto (ctx
->path
, x1
, y1
, x2
, y2
, x3
, y3
);
383 if (ctx
->param
== 4 || final
) {
384 s_path_parse_default_xy (ctx
, 4);
385 x1
= 2 * ctx
->cpx
- ctx
->rpx
;
386 y1
= 2 * ctx
->cpy
- ctx
->rpy
;
391 s_path_curveto (ctx
->path
, x1
, y1
, x2
, y2
, x3
, y3
);
400 /* horizontal lineto */
401 if (ctx
->param
== 1) {
402 s_path_lineto (ctx
->path
, ctx
->params
[0], ctx
->cpy
);
403 ctx
->cpx
= ctx
->rpx
= ctx
->params
[0];
408 /* vertical lineto */
409 if (ctx
->param
== 1) {
410 s_path_lineto (ctx
->path
, ctx
->cpx
, ctx
->params
[0]);
411 ctx
->cpy
= ctx
->rpy
= ctx
->params
[0];
416 /* quadratic bezier curveto */
418 /* non-normative reference:
419 http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
421 if (ctx
->param
== 4 || final
) {
422 s_path_parse_default_xy (ctx
, 4);
423 /* raise quadratic bezier to cubic */
424 x1
= (ctx
->cpx
+ 2 * ctx
->params
[0]) * (1.0 / 3.0);
425 y1
= (ctx
->cpy
+ 2 * ctx
->params
[1]) * (1.0 / 3.0);
428 x2
= (x3
+ 2 * ctx
->params
[0]) * (1.0 / 3.0);
429 y2
= (y3
+ 2 * ctx
->params
[1]) * (1.0 / 3.0);
430 s_path_curveto (ctx
->path
, x1
, y1
, x2
, y2
, x3
, y3
);
431 ctx
->rpx
= ctx
->params
[0];
432 ctx
->rpy
= ctx
->params
[1];
439 /* Truetype quadratic bezier curveto */
440 if (ctx
->param
== 2 || final
) {
441 double xc
, yc
; /* quadratic control point */
443 xc
= 2 * ctx
->cpx
- ctx
->rpx
;
444 yc
= 2 * ctx
->cpy
- ctx
->rpy
;
445 /* generate a quadratic bezier with control point = xc, yc */
446 x1
= (ctx
->cpx
+ 2 * xc
) * (1.0 / 3.0);
447 y1
= (ctx
->cpy
+ 2 * yc
) * (1.0 / 3.0);
450 x2
= (x3
+ 2 * xc
) * (1.0 / 3.0);
451 y2
= (y3
+ 2 * yc
) * (1.0 / 3.0);
452 s_path_curveto (ctx
->path
, x1
, y1
, x2
, y2
, x3
, y3
);
459 if (ctx
->param
> 2) {
460 s_path_parse_default_xy (ctx
, 4);
461 /* raise quadratic bezier to cubic */
462 x1
= (ctx
->cpx
+ 2 * ctx
->params
[0]) * (1.0 / 3.0);
463 y1
= (ctx
->cpy
+ 2 * ctx
->params
[1]) * (1.0 / 3.0);
466 x2
= (x3
+ 2 * ctx
->params
[0]) * (1.0 / 3.0);
467 y2
= (y3
+ 2 * ctx
->params
[1]) * (1.0 / 3.0);
468 s_path_curveto (ctx
->path
, x1
, y1
, x2
, y2
, x3
, y3
);
469 ctx
->rpx
= ctx
->params
[0];
470 ctx
->rpy
= ctx
->params
[1];
474 s_path_parse_default_xy (ctx
, 2);
475 s_path_lineto (ctx
->path
, ctx
->params
[0], ctx
->params
[1]);
476 ctx
->cpx
= ctx
->rpx
= ctx
->params
[0];
477 ctx
->cpy
= ctx
->rpy
= ctx
->params
[1];
483 if (ctx
->param
== 7 || final
) {
485 ctx
->params
[0], ctx
->params
[1], ctx
->params
[2],
486 ctx
->params
[3], ctx
->params
[4], ctx
->params
[5], ctx
->params
[6]);
496 static void s_path_parse_data (RSVGParsePathCtx
* ctx
, const char *data
)
501 gboolean in_num
= FALSE
;
502 gboolean in_frac
= FALSE
;
503 gboolean in_exp
= FALSE
;
504 gboolean exp_wait_sign
= FALSE
;
513 if (c
>= '0' && c
<= '9') {
517 exp
= (exp
* 10) + c
- '0';
518 exp_wait_sign
= FALSE
;
520 val
+= (frac
*= 0.1) * (c
- '0');
522 val
= (val
* 10) + c
- '0';
529 exp_wait_sign
= FALSE
;
533 } else if (c
== '.') {
540 } else if ((c
== 'E' || c
== 'e') && in_num
) {
542 exp_wait_sign
= TRUE
;
545 } else if ((c
== '+' || c
== '-') && in_exp
) {
546 exp_sign
= c
== '+' ? 1 : -1;
550 val
*= sign
* pow (10, exp_sign
* exp
);
552 /* Handle relative coordinates. This switch statement attempts
553 to determine _what_ the coords are relative to. This is
554 underspecified in the 12 Apr working draft. */
562 #ifndef RSVGV_RELATIVE
563 /* rule: even-numbered params are x-relative, odd-numbered
565 if ((ctx
->param
& 1) == 0)
567 else if ((ctx
->param
& 1) == 1)
571 /* rule: even-numbered params are x-relative, odd-numbered
573 if (ctx
->param
== 0 || (ctx
->param
% 2 == 0))
580 /* rule: sixth and seventh are x and y, rest are not
584 else if (ctx
->param
== 6)
588 /* rule: x-relative */
592 /* rule: y-relative */
597 ctx
->params
[ctx
->param
++] = val
;
598 s_path_parse_do_cmd (ctx
, FALSE
);
605 else if ((c
== '+' || c
== '-') && !exp_wait_sign
) {
606 sign
= c
== '+' ? 1 : -1;
613 exp_wait_sign
= FALSE
;
614 } else if (c
== 'z' || c
== 'Z') {
616 s_path_parse_do_cmd (ctx
, TRUE
);
617 /* s_path_closepath (ctx->path); */
618 /* s_path_lineto (ctx->path, ctx->mpx, ctx->mpy); */
619 s_path_art_finish (ctx
->path
);
621 ctx
->cpx
= ctx
->rpx
= ctx
->path
->sections
[ctx
->path
->num_sections
- 1].x3
;
622 ctx
->cpy
= ctx
->rpy
= ctx
->path
->sections
[ctx
->path
->num_sections
- 1].y3
;
623 } else if (c
>= 'A' && c
<= 'Z' && c
!= 'E') {
625 s_path_parse_do_cmd (ctx
, TRUE
);
626 ctx
->cmd
= c
+ 'a' - 'A';
628 } else if (c
>= 'a' && c
<= 'z' && c
!= 'e') {
630 s_path_parse_do_cmd (ctx
, TRUE
);
634 /* else c _should_ be whitespace or , */
639 PATH
*s_path_parse (const char *path_str
)
641 RSVGParsePathCtx ctx
;
643 ctx
.path
= s_path_new ();
651 s_path_parse_data (&ctx
, path_str
);
654 s_path_parse_do_cmd (&ctx
, TRUE
);
660 char *s_path_string_from_path (const PATH
*path
)
662 PATH_SECTION
*section
;
663 GString
*path_string
;
666 path_string
= g_string_new ("");
668 for (i
= 0; i
< path
->num_sections
; i
++) {
669 section
= &path
->sections
[i
];
672 g_string_append_c (path_string
, '\n');
674 switch (section
->code
) {
676 g_string_append_printf (path_string
, "M %i,%i",
677 section
->x3
, section
->y3
);
679 case PATH_MOVETO_OPEN
:
680 g_string_append_printf (path_string
, "M %i,%i",
681 section
->x3
, section
->y3
);
684 g_string_append_printf (path_string
, "C %i,%i %i,%i %i,%i",
685 section
->x1
, section
->y1
,
686 section
->x2
, section
->y2
,
687 section
->x3
, section
->y3
);
690 g_string_append_printf (path_string
, "L %i,%i",
691 section
->x3
, section
->y3
);
694 g_string_append_printf (path_string
, "z");
699 return g_string_free (path_string
, FALSE
);
702 /*! \brief Converts a path to a polygon
704 * \param path [in] The path to convert to a polygon. This parameter must not
706 * \param points [out] An array of the polygon's vertices. This parameter
708 * \return TRUE if the path is closed, FALSE if it is open.
710 int s_path_to_polygon (PATH
*path
, GArray
*points
)
714 sPOINT point
= { 0, 0 };
716 if (points
->len
> 0) {
717 g_array_remove_range (points
, 0, points
->len
- 1);
720 for (i
= 0; i
< path
->num_sections
; i
++) {
722 PATH_SECTION
*section
= &path
->sections
[i
];
724 switch (section
->code
) {
726 bezier
.x
[0] = point
.x
;
727 bezier
.y
[0] = point
.y
;
728 bezier
.x
[1] = section
->x1
;
729 bezier
.y
[1] = section
->y1
;
730 bezier
.x
[2] = section
->x2
;
731 bezier
.y
[2] = section
->y2
;
732 point
.x
= bezier
.x
[3] = section
->x3
;
733 point
.y
= bezier
.y
[3] = section
->y3
;
734 m_polygon_append_bezier (points
, &bezier
, NUM_BEZIER_SEGMENTS
);
737 case PATH_MOVETO_OPEN
:
738 /* Unsupported, just fall through and draw a line */
743 point
.x
= section
->x3
;
744 point
.y
= section
->y3
;
745 m_polygon_append_point (points
, point
.x
, point
.y
);
758 /*! \brief Calculates the distance between the given point and the closest
759 * point on the given path segment.
761 * \param [in] path The path.
762 * \param [in] x The x coordinate of the given point.
763 * \param [in] y The y coordinate of the given point.
764 * \param [in] solid TRUE if the path should be treated as solid, FALSE if
765 * the path should be treated as hollow.
766 * \return The shortest distance from the path to the point. With a solid
767 * shape, this function returns a distance of zero for interior points. With
768 * an invalid parameter, this function returns G_MAXDOUBLE.
770 double s_path_shortest_distance (PATH
*path
, int x
, int y
, int solid
)
772 double shortest_distance
= G_MAXDOUBLE
;
776 points
= g_array_new (FALSE
, FALSE
, sizeof (sPOINT
));
778 closed
= s_path_to_polygon (path
, points
);
781 shortest_distance
= m_polygon_shortest_distance (points
, x
, y
, closed
);
783 } else if (m_polygon_interior_point (points
, x
, y
)) {
784 shortest_distance
= 0;
787 shortest_distance
= m_polygon_shortest_distance (points
, x
, y
, TRUE
);
790 g_array_free (points
, TRUE
);
792 return shortest_distance
;