1 /* Lepton EDA library
2 * Copyright (C) 1998-2016 gEDA Contributors
3 * Copyright (C) 2017-2021 Lepton EDA Contributors
4 *
5 * Code originally from librsvg 2.22.2 (LGPL) Copyright (C) 2000 Eazel, Inc.
6 *
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.
11 *
12 * Adapted for gEDA by Peter Clifton <pcjc2@cam.ac.uk>
13 *
14 * THIS FILE IS LGPL LICENSED, gEDA AS A WHOLE IS GPL LICENSED
15 *
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.
20 *
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.
25 *
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., 51 Franklin Street, Fifth Floor,
29 * Boston, MA 02110-1301 USA
30 *
31 */
32 /*! \file path.c
33 */
34
35 #include "config.h"
36
37 #include <stdio.h>
38 #include <math.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #include <glib.h>
43
44 #include "liblepton_priv.h"
45
46 #define NUM_BEZIER_SEGMENTS 100
47
48
49 /*! \brief Create a new LeptonPath structure.
50 *
51 * \return The newly created LeptonPath structure.
52 */
53 LeptonPath*
lepton_path_new()54 lepton_path_new ()
55 {
56 LeptonPath *path;
57
58 path = g_new (LeptonPath, 1);
59 path->num_sections = 0;
60 path->num_sections_max = 16;
61 path->sections = g_new (LeptonPathSection, path->num_sections_max);
62
63 return path;
64 }
65
66
67 /*! \brief Free memory associated with the LeptonPath object.
68 *
69 * \param [in] LeptonPath object to be freed.
70 */
71 void
lepton_path_free(LeptonPath * path)72 lepton_path_free (LeptonPath * path)
73 {
74 if (path != NULL) {
75 g_free (path->sections);
76 g_free (path);
77 }
78 }
79
80 /*! \brief Get the code type of path section.
81 * \par Function Description
82 * This function returns the PATH_CODE name of a
83 * #LeptonPathSection object.
84 *
85 * \param [in] section The #LeptonPathSection object.
86 * \return PATH_CODE enum value of the path section object.
87 */
88 PATH_CODE
lepton_path_section_get_code(LeptonPathSection * section)89 lepton_path_section_get_code (LeptonPathSection *section)
90 {
91 return section->code;
92 }
93
94 /*! \brief Get X coordinate of the first point of path section.
95 * \par Function Description
96 * Given a #LeptonPathSection variable, this getter returns the X
97 * coordinate of its first point.
98 *
99 * \param [in] section The #LeptonPathSection variable.
100 * \return The X coordinate value of the first point of the path
101 * section.
102 */
103 int
lepton_path_section_get_x1(LeptonPathSection * section)104 lepton_path_section_get_x1 (LeptonPathSection *section)
105 {
106 return section->x1;
107 }
108
109 /*! \brief Get Y coordinate of the first point of path section.
110 * \par Function Description
111 * Given a #LeptonPathSection variable, this getter returns the Y
112 * coordinate of its first point.
113 *
114 * \param [in] section The #LeptonPathSection variable.
115 * \return The Y coordinate value of the first point of the path
116 * section.
117 */
118 int
lepton_path_section_get_y1(LeptonPathSection * section)119 lepton_path_section_get_y1 (LeptonPathSection *section)
120 {
121 return section->y1;
122 }
123
124 /*! \brief Get X coordinate of the second point of path section.
125 * \par Function Description
126 * Given a #LeptonPathSection variable, this getter returns the X
127 * coordinate of its second point.
128 *
129 * \param [in] section The #LeptonPathSection variable.
130 * \return The X coordinate value of the second point of the path
131 * section.
132 */
133 int
lepton_path_section_get_x2(LeptonPathSection * section)134 lepton_path_section_get_x2 (LeptonPathSection *section)
135 {
136 return section->x2;
137 }
138
139 /*! \brief Get Y coordinate of the second point of path section.
140 * \par Function Description
141 * Given a #LeptonPathSection variable, this getter returns the Y
142 * coordinate of its second point.
143 *
144 * \param [in] section The #LeptonPathSection variable.
145 * \return The Y coordinate value of the second point of the path
146 * section.
147 */
148 int
lepton_path_section_get_y2(LeptonPathSection * section)149 lepton_path_section_get_y2 (LeptonPathSection *section)
150 {
151 return section->y2;
152 }
153
154 /*! \brief Get X coordinate of the third point of path section.
155 * \par Function Description
156 * Given a #LeptonPathSection variable, this getter returns the X
157 * coordinate of its third point.
158 *
159 * \param [in] section The #LeptonPathSection variable.
160 * \return The X coordinate value of the third point of the path
161 * section.
162 */
163 int
lepton_path_section_get_x3(LeptonPathSection * section)164 lepton_path_section_get_x3 (LeptonPathSection *section)
165 {
166 return section->x3;
167 }
168
169 /*! \brief Get Y coordinate of the third point of path section.
170 * \par Function Description
171 * Given a #LeptonPathSection variable, this getter returns the Y
172 * coordinate of its third point.
173 *
174 * \param [in] section The #LeptonPathSection variable.
175 * \return The Y coordinate value of the third point of the path
176 * section.
177 */
178 int
lepton_path_section_get_y3(LeptonPathSection * section)179 lepton_path_section_get_y3 (LeptonPathSection *section)
180 {
181 return section->y3;
182 }
183
184 /*! \brief Return a path section code enum value from a string.
185 * \par Function Description
186 * Given a string \a s, returns the #PATH_CODE enum value
187 * corresponding to it. This is mainly intended to be used for
188 * value conversion in Scheme FFI functions.
189 *
190 * \param [in] s The string.
191 */
192 int
lepton_path_section_code_from_string(char * s)193 lepton_path_section_code_from_string (char *s)
194 {
195 int result = PATH_END;
196
197 if (strcmp (s, "moveto") == 0) { result = PATH_MOVETO; }
198 else if (strcmp (s, "lineto") == 0) { result = PATH_LINETO; }
199 else if (strcmp (s, "curveto") == 0) {result = PATH_CURVETO; }
200 else if (strcmp (s, "closepath") == 0) {result = PATH_END; }
201
202 return result;
203 }
204
205 /*! \brief Return a string holding the representation of #PATH_CODE value.
206 * \par Function Description
207 * Given a #PATH_CODE value, returns its external representation
208 * as a string. This is mainly intended to be used in Scheme FFI
209 * functions.
210 *
211 * \param [in] code The #PATH_CODE value.
212 */
213 const char*
lepton_path_section_code_to_string(int code)214 lepton_path_section_code_to_string (int code)
215 {
216 const char *result = NULL;
217
218 switch (code)
219 {
220 case PATH_MOVETO:
221 case PATH_MOVETO_OPEN: result = "moveto"; break;
222 case PATH_LINETO: result = "lineto"; break;
223 case PATH_CURVETO: result = "curveto"; break;
224 case PATH_END: result = "closepath"; break;
225 default: break;
226 }
227
228 return result;
229 }
230
231 /*! \brief Create a new "moveto" section in #LeptonPath object.
232 * \par Function Description
233 * The function modifies #LeptonPath object by adding a new
234 * "moveto" section with given coordinates x and y. If the last
235 * section of the path is already a "moveto" one, amend it
236 * instead of adding a new section.
237 *
238 * \param [in,out] path The #LeptonPath object.
239 * \param [in] x The X coordinate of the "moveto" point.
240 * \param [in] y The Y coordinate of the "moveto" point.
241 */
242 void
lepton_path_moveto(LeptonPath * path,double x,double y)243 lepton_path_moveto (LeptonPath *path,
244 double x,
245 double y)
246 {
247 LeptonPathSection *sections;
248 int num_sections;
249
250 g_return_if_fail (path != NULL);
251
252 /* if the last command was a moveto then change that last moveto instead of
253 creating a new one */
254 sections = path->sections;
255 num_sections = path->num_sections;
256
257 if (num_sections > 0)
258 if (sections[num_sections - 1].code == PATH_MOVETO_OPEN) {
259 sections[num_sections - 1].x3 = x;
260 sections[num_sections - 1].y3 = y;
261 return;
262 }
263
264 num_sections = path->num_sections++;
265
266 if (num_sections == path->num_sections_max)
267 path->sections =
268 (LeptonPathSection*) g_realloc (path->sections,
269 (path->num_sections_max <<= 1) *
270 sizeof (LeptonPathSection));
271 sections = path->sections;
272 sections[num_sections].code = PATH_MOVETO_OPEN;
273 sections[num_sections].x3 = x;
274 sections[num_sections].y3 = y;
275 }
276
277
278 /*! \brief Create a new "lineto" section in #LeptonPath object.
279 * \par Function Description
280 * The function modifies #LeptonPath object by adding a new
281 * "lineto" section with given coordinates x and y.
282 *
283 * \param [in,out] path The #LeptonPath object.
284 * \param [in] x The X coordinate of the "lineto" point.
285 * \param [in] y The Y coordinate of the "lineto" point.
286 */
287 void
lepton_path_lineto(LeptonPath * path,double x,double y)288 lepton_path_lineto (LeptonPath *path,
289 double x,
290 double y)
291 {
292 LeptonPathSection *sections;
293 int num_sections;
294
295 g_return_if_fail (path != NULL);
296
297 num_sections = path->num_sections++;
298
299 if (num_sections == path->num_sections_max)
300 path->sections =
301 (LeptonPathSection*) g_realloc (path->sections,
302 (path->num_sections_max <<= 1) *
303 sizeof (LeptonPathSection));
304 sections = path->sections;
305 sections[num_sections].code = PATH_LINETO;
306 sections[num_sections].x3 = x;
307 sections[num_sections].y3 = y;
308 }
309
310
311 /*! \brief Create a new "curveto" section in #LeptonPath object.
312 * \par Function Description
313 * The function modifies #LeptonPath object by adding a new
314 * "curveto" section with given coordinates of three control
315 * points.
316 *
317 * \param [in,out] path The #LeptonPath object.
318 * \param [in] x1 The X coordinate of the first control point.
319 * \param [in] y1 The Y coordinate of the first control point.
320 * \param [in] x2 The X coordinate of the second control point.
321 * \param [in] y2 The Y coordinate of the second control point.
322 * \param [in] x3 The X coordinate of the third control point.
323 * \param [in] y3 The Y coordinate of the third control point.
324 */
325 void
lepton_path_curveto(LeptonPath * path,double x1,double y1,double x2,double y2,double x3,double y3)326 lepton_path_curveto (LeptonPath *path,
327 double x1,
328 double y1,
329 double x2,
330 double y2,
331 double x3,
332 double y3)
333 {
334 LeptonPathSection *sections;
335 int num_sections;
336
337 g_return_if_fail (path != NULL);
338
339 num_sections = path->num_sections++;
340
341 if (num_sections == path->num_sections_max)
342 path->sections =
343 (LeptonPathSection*) g_realloc (path->sections,
344 (path->num_sections_max <<= 1) *
345 sizeof (LeptonPathSection));
346 sections = path->sections;
347 sections[num_sections].code = PATH_CURVETO;
348 sections[num_sections].x1 = x1;
349 sections[num_sections].y1 = y1;
350 sections[num_sections].x2 = x2;
351 sections[num_sections].y2 = y2;
352 sections[num_sections].x3 = x3;
353 sections[num_sections].y3 = y3;
354 }
355
356
357 /*! \brief Finish #LeptonPath object creation.
358 * \par Function Description
359 * Finishes #LeptonPath object creation by adding a last ending
360 * section.
361 *
362 * \param [in,out] path The #LeptonPath object.
363 */
364 void
lepton_path_art_finish(LeptonPath * path)365 lepton_path_art_finish (LeptonPath * path)
366 {
367 int num_sections;
368
369 g_return_if_fail (path != NULL);
370
371 num_sections = path->num_sections++;
372
373 if (num_sections == path->num_sections_max)
374 path->sections =
375 (LeptonPathSection*) g_realloc (path->sections,
376 (path->num_sections_max <<= 1) *
377 sizeof (LeptonPathSection));
378 path->sections[num_sections].code = PATH_END;
379 }
380
381
382 /* This module parses an SVG style path element into a LeptonPath.
383
384 At present, there is no support for <marker> or any other contextual
385 information from the SVG file. The API will need to change rather
386 significantly to support these.
387
388 Reference: SVG working draft 3 March 2000, section 8.
389 */
390
391 typedef struct _RSVGParsePathCtx RSVGParsePathCtx;
392
393 struct _RSVGParsePathCtx {
394 LeptonPath *path;
395 double cpx, cpy; /* current point */
396 double rpx, rpy; /* reflection point (for 's' and 't' commands) */
397 double mpx, mpy; /* Last moved to point (for path closures) */
398 char cmd; /* current command (lowercase) */
399 int param; /* parameter number */
400 gboolean rel; /* true if relative coords */
401 double params[7]; /* parameters that have been parsed */
402 };
403
404
405 static void
lepton_path_arc_segment(RSVGParsePathCtx * ctx,double xc,double yc,double th0,double th1,double rx,double ry,double x_axis_rotation)406 lepton_path_arc_segment (RSVGParsePathCtx * ctx,
407 double xc,
408 double yc,
409 double th0,
410 double th1,
411 double rx,
412 double ry,
413 double x_axis_rotation)
414 {
415 double sin_th, cos_th;
416 double a00, a01, a10, a11;
417 double x1, y1, x2, y2, x3, y3;
418 double t;
419 double th_half;
420
421 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
422 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
423 /* inverse transform compared with lepton_path_arc */
424 a00 = cos_th * rx;
425 a01 = -sin_th * ry;
426 a10 = sin_th * rx;
427 a11 = cos_th * ry;
428
429 th_half = 0.5 * (th1 - th0);
430 t = (8.0 / 3.0) * sin (th_half * 0.5) * sin (th_half * 0.5) / sin (th_half);
431 x1 = xc + cos (th0) - t * sin (th0);
432 y1 = yc + sin (th0) + t * cos (th0);
433 x3 = xc + cos (th1);
434 y3 = yc + sin (th1);
435 x2 = x3 + t * sin (th1);
436 y2 = y3 - t * cos (th1);
437 lepton_path_curveto (ctx->path,
438 a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
439 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
440 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
441 }
442
443
444 /*
445 * lepton_path_arc: Add an arc to the path context.
446 * @ctx: Path context.
447 * @rx: Radius in x direction (before rotation).
448 * @ry: Radius in y direction (before rotation).
449 * @x_axis_rotation: Rotation angle for axes.
450 * @large_arc_flag: 0 for arc length <= 180, 1 for arc >= 180.
451 * @sweep: 0 for "negative angle", 1 for "positive angle".
452 * @x: New x coordinate.
453 * @y: New y coordinate.
454 *
455 */
456 static void
lepton_path_arc(RSVGParsePathCtx * ctx,double rx,double ry,double x_axis_rotation,int large_arc_flag,int sweep_flag,double x,double y)457 lepton_path_arc (RSVGParsePathCtx * ctx,
458 double rx,
459 double ry,
460 double x_axis_rotation,
461 int large_arc_flag,
462 int sweep_flag,
463 double x,
464 double y)
465 {
466 double sin_th, cos_th;
467 double a00, a01, a10, a11;
468 double x0, y0, x1, y1, xc, yc;
469 double d, sfactor, sfactor_sq;
470 double th0, th1, th_arc;
471 int i, n_segs;
472
473 /* Check that neither radius is zero, since its isn't either
474 geometrically or mathematically meaningful and will
475 cause divide by zero and subsequent NaNs. We should
476 really do some ranged check ie -0.001 < x < 000.1 rather
477 can just a straight check again zero.
478 */
479 if ((rx == 0.0) || (ry == 0.0))
480 return;
481
482 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
483 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
484 a00 = cos_th / rx;
485 a01 = sin_th / rx;
486 a10 = -sin_th / ry;
487 a11 = cos_th / ry;
488 x0 = a00 * ctx->cpx + a01 * ctx->cpy;
489 y0 = a10 * ctx->cpx + a11 * ctx->cpy;
490 x1 = a00 * x + a01 * y;
491 y1 = a10 * x + a11 * y;
492 /* (x0, y0) is current point in transformed coordinate space.
493 (x1, y1) is new point in transformed coordinate space.
494
495 The arc fits a unit-radius circle in this space.
496 */
497 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
498 sfactor_sq = 1.0 / d - 0.25;
499 if (sfactor_sq < 0)
500 sfactor_sq = 0;
501 sfactor = sqrt (sfactor_sq);
502 if (sweep_flag == large_arc_flag)
503 sfactor = -sfactor;
504 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
505 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
506 /* (xc, yc) is center of the circle. */
507
508 th0 = atan2 (y0 - yc, x0 - xc);
509 th1 = atan2 (y1 - yc, x1 - xc);
510
511 th_arc = th1 - th0;
512 if (th_arc < 0 && sweep_flag)
513 th_arc += 2 * M_PI;
514 else if (th_arc > 0 && !sweep_flag)
515 th_arc -= 2 * M_PI;
516
517 n_segs = ceil (fabs (th_arc / (M_PI * 0.5 + 0.001)));
518
519 for (i = 0; i < n_segs; i++)
520 lepton_path_arc_segment (ctx,
521 xc, yc,
522 th0 + i * th_arc / n_segs,
523 th0 + (i + 1) * th_arc / n_segs,
524 rx, ry,
525 x_axis_rotation);
526
527 ctx->cpx = x;
528 ctx->cpy = y;
529 }
530
531
532 /* supply defaults for missing parameters, assuming relative coordinates
533 are to be interpreted as x,y */
534 static void
lepton_path_parse_default_xy(RSVGParsePathCtx * ctx,int n_params)535 lepton_path_parse_default_xy (RSVGParsePathCtx * ctx,
536 int n_params)
537 {
538 int i;
539
540 if (ctx->rel) {
541 for (i = ctx->param; i < n_params; i++) {
542 if (i > 2)
543 ctx->params[i] = ctx->params[i - 2];
544 else if (i == 1)
545 ctx->params[i] = ctx->cpy;
546 else if (i == 0)
547 /* we shouldn't get here (usually ctx->param > 0 as
548 precondition) */
549 ctx->params[i] = ctx->cpx;
550 }
551 } else {
552 for (i = ctx->param; i < n_params; i++)
553 ctx->params[i] = 0.0;
554 }
555 }
556
557
558 static void
lepton_path_parse_do_cmd(RSVGParsePathCtx * ctx,gboolean final)559 lepton_path_parse_do_cmd (RSVGParsePathCtx *ctx,
560 gboolean final)
561 {
562 double x1, y1, x2, y2, x3, y3;
563
564 switch (ctx->cmd) {
565 case 'm':
566 /* moveto */
567 if (ctx->param == 2 || final) {
568 lepton_path_parse_default_xy (ctx, 2);
569 lepton_path_moveto (ctx->path, ctx->params[0], ctx->params[1]);
570 ctx->mpx = ctx->cpx = ctx->rpx = ctx->params[0];
571 ctx->mpy = ctx->cpy = ctx->rpy = ctx->params[1];
572 ctx->param = 0;
573 ctx->cmd = 'l'; /* implicit linetos after a moveto */
574 }
575 break;
576 case 'l':
577 /* lineto */
578 if (ctx->param == 2 || final) {
579 lepton_path_parse_default_xy (ctx, 2);
580 lepton_path_lineto (ctx->path, ctx->params[0], ctx->params[1]);
581 ctx->cpx = ctx->rpx = ctx->params[0];
582 ctx->cpy = ctx->rpy = ctx->params[1];
583 ctx->param = 0;
584 }
585 break;
586 case 'c':
587 /* curveto */
588 if (ctx->param == 6 || final) {
589 lepton_path_parse_default_xy (ctx, 6);
590 x1 = ctx->params[0];
591 y1 = ctx->params[1];
592 x2 = ctx->params[2];
593 y2 = ctx->params[3];
594 x3 = ctx->params[4];
595 y3 = ctx->params[5];
596 lepton_path_curveto (ctx->path, x1, y1, x2, y2, x3, y3);
597 ctx->rpx = x2;
598 ctx->rpy = y2;
599 ctx->cpx = x3;
600 ctx->cpy = y3;
601 ctx->param = 0;
602 }
603 break;
604 case 's':
605 /* smooth curveto */
606 if (ctx->param == 4 || final) {
607 lepton_path_parse_default_xy (ctx, 4);
608 x1 = 2 * ctx->cpx - ctx->rpx;
609 y1 = 2 * ctx->cpy - ctx->rpy;
610 x2 = ctx->params[0];
611 y2 = ctx->params[1];
612 x3 = ctx->params[2];
613 y3 = ctx->params[3];
614 lepton_path_curveto (ctx->path, x1, y1, x2, y2, x3, y3);
615 ctx->rpx = x2;
616 ctx->rpy = y2;
617 ctx->cpx = x3;
618 ctx->cpy = y3;
619 ctx->param = 0;
620 }
621 break;
622 case 'h':
623 /* horizontal lineto */
624 if (ctx->param == 1) {
625 lepton_path_lineto (ctx->path, ctx->params[0], ctx->cpy);
626 ctx->cpx = ctx->rpx = ctx->params[0];
627 ctx->param = 0;
628 }
629 break;
630 case 'v':
631 /* vertical lineto */
632 if (ctx->param == 1) {
633 lepton_path_lineto (ctx->path, ctx->cpx, ctx->params[0]);
634 ctx->cpy = ctx->rpy = ctx->params[0];
635 ctx->param = 0;
636 }
637 break;
638 case 'q':
639 /* quadratic bezier curveto */
640
641 /* non-normative reference:
642 http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
643 */
644 if (ctx->param == 4 || final) {
645 lepton_path_parse_default_xy (ctx, 4);
646 /* raise quadratic bezier to cubic */
647 x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
648 y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
649 x3 = ctx->params[2];
650 y3 = ctx->params[3];
651 x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
652 y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
653 lepton_path_curveto (ctx->path, x1, y1, x2, y2, x3, y3);
654 ctx->rpx = ctx->params[0];
655 ctx->rpy = ctx->params[1];
656 ctx->cpx = x3;
657 ctx->cpy = y3;
658 ctx->param = 0;
659 }
660 break;
661 case 't':
662 /* Truetype quadratic bezier curveto */
663 if (ctx->param == 2 || final) {
664 double xc, yc; /* quadratic control point */
665
666 xc = 2 * ctx->cpx - ctx->rpx;
667 yc = 2 * ctx->cpy - ctx->rpy;
668 /* generate a quadratic bezier with control point = xc, yc */
669 x1 = (ctx->cpx + 2 * xc) * (1.0 / 3.0);
670 y1 = (ctx->cpy + 2 * yc) * (1.0 / 3.0);
671 x3 = ctx->params[0];
672 y3 = ctx->params[1];
673 x2 = (x3 + 2 * xc) * (1.0 / 3.0);
674 y2 = (y3 + 2 * yc) * (1.0 / 3.0);
675 lepton_path_curveto (ctx->path, x1, y1, x2, y2, x3, y3);
676 ctx->rpx = xc;
677 ctx->rpy = yc;
678 ctx->cpx = x3;
679 ctx->cpy = y3;
680 ctx->param = 0;
681 } else if (final) {
682 if (ctx->param > 2) {
683 lepton_path_parse_default_xy (ctx, 4);
684 /* raise quadratic bezier to cubic */
685 x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
686 y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
687 x3 = ctx->params[2];
688 y3 = ctx->params[3];
689 x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
690 y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
691 lepton_path_curveto (ctx->path, x1, y1, x2, y2, x3, y3);
692 ctx->rpx = ctx->params[0];
693 ctx->rpy = ctx->params[1];
694 ctx->cpx = x3;
695 ctx->cpy = y3;
696 } else {
697 lepton_path_parse_default_xy (ctx, 2);
698 lepton_path_lineto (ctx->path, ctx->params[0], ctx->params[1]);
699 ctx->cpx = ctx->rpx = ctx->params[0];
700 ctx->cpy = ctx->rpy = ctx->params[1];
701 }
702 ctx->param = 0;
703 }
704 break;
705 case 'a':
706 if (ctx->param == 7 || final) {
707 lepton_path_arc (ctx,
708 ctx->params[0],
709 ctx->params[1],
710 ctx->params[2],
711 ctx->params[3],
712 ctx->params[4],
713 ctx->params[5],
714 ctx->params[6]);
715 ctx->param = 0;
716 }
717 break;
718 default:
719 ctx->param = 0;
720 }
721 }
722
723
724 static void
lepton_path_parse_data(RSVGParsePathCtx * ctx,const char * data)725 lepton_path_parse_data (RSVGParsePathCtx *ctx,
726 const char *data)
727 {
728 int i = 0;
729 double val = 0;
730 char c = 0;
731 gboolean in_num = FALSE;
732 gboolean in_frac = FALSE;
733 gboolean in_exp = FALSE;
734 gboolean exp_wait_sign = FALSE;
735 int sign = 0;
736 int exp = 0;
737 int exp_sign = 0;
738 double frac = 0.0;
739
740 in_num = FALSE;
741 for (i = 0;; i++) {
742 c = data[i];
743 if (c >= '0' && c <= '9') {
744 /* digit */
745 if (in_num) {
746 if (in_exp) {
747 exp = (exp * 10) + c - '0';
748 exp_wait_sign = FALSE;
749 } else if (in_frac)
750 val += (frac *= 0.1) * (c - '0');
751 else
752 val = (val * 10) + c - '0';
753 } else {
754 in_num = TRUE;
755 in_frac = FALSE;
756 in_exp = FALSE;
757 exp = 0;
758 exp_sign = 1;
759 exp_wait_sign = FALSE;
760 val = c - '0';
761 sign = 1;
762 }
763 } else if (c == '.') {
764 if (!in_num) {
765 in_num = TRUE;
766 val = 0;
767 }
768 in_frac = TRUE;
769 frac = 1;
770 } else if ((c == 'E' || c == 'e') && in_num) {
771 in_exp = TRUE;
772 exp_wait_sign = TRUE;
773 exp = 0;
774 exp_sign = 1;
775 } else if ((c == '+' || c == '-') && in_exp) {
776 exp_sign = c == '+' ? 1 : -1;
777 } else if (in_num) {
778 /* end of number */
779
780 val *= sign * pow (10, exp_sign * exp);
781 if (ctx->rel) {
782 /* Handle relative coordinates. This switch statement attempts
783 to determine _what_ the coords are relative to. This is
784 underspecified in the 12 Apr working draft. */
785 switch (ctx->cmd) {
786 case 'l':
787 case 'm':
788 case 'c':
789 case 's':
790 case 'q':
791 case 't':
792 #ifndef RSVGV_RELATIVE
793 /* rule: even-numbered params are x-relative, odd-numbered
794 are y-relative */
795 if ((ctx->param & 1) == 0)
796 val += ctx->cpx;
797 else if ((ctx->param & 1) == 1)
798 val += ctx->cpy;
799 break;
800 #else
801 /* rule: even-numbered params are x-relative, odd-numbered
802 are y-relative */
803 if (ctx->param == 0 || (ctx->param % 2 == 0))
804 val += ctx->cpx;
805 else
806 val += ctx->cpy;
807 break;
808 #endif
809 case 'a':
810 /* rule: sixth and seventh are x and y, rest are not
811 relative */
812 if (ctx->param == 5)
813 val += ctx->cpx;
814 else if (ctx->param == 6)
815 val += ctx->cpy;
816 break;
817 case 'h':
818 /* rule: x-relative */
819 val += ctx->cpx;
820 break;
821 case 'v':
822 /* rule: y-relative */
823 val += ctx->cpy;
824 break;
825 }
826 }
827 ctx->params[ctx->param++] = val;
828 lepton_path_parse_do_cmd (ctx, FALSE);
829
830 in_num = FALSE;
831 }
832
833 if (c == '\0')
834 break;
835 else if ((c == '+' || c == '-') && !exp_wait_sign) {
836 sign = c == '+' ? 1 : -1;
837 val = 0;
838 in_num = TRUE;
839 in_frac = FALSE;
840 in_exp = FALSE;
841 exp = 0;
842 exp_sign = 1;
843 exp_wait_sign = FALSE;
844 } else if (c == 'z' || c == 'Z') {
845 if (ctx->param)
846 lepton_path_parse_do_cmd (ctx, TRUE);
847 /* s_path_closepath (ctx->path); */
848 /* lepton_path_lineto (ctx->path, ctx->mpx, ctx->mpy); */
849 lepton_path_art_finish (ctx->path);
850
851 ctx->cpx = ctx->rpx = ctx->path->sections[ctx->path->num_sections - 1].x3;
852 ctx->cpy = ctx->rpy = ctx->path->sections[ctx->path->num_sections - 1].y3;
853 } else if (c >= 'A' && c <= 'Z' && c != 'E') {
854 if (ctx->param)
855 lepton_path_parse_do_cmd (ctx, TRUE);
856 ctx->cmd = c + 'a' - 'A';
857 ctx->rel = FALSE;
858 } else if (c >= 'a' && c <= 'z' && c != 'e') {
859 if (ctx->param)
860 lepton_path_parse_do_cmd (ctx, TRUE);
861 ctx->cmd = c;
862 ctx->rel = TRUE;
863 }
864 /* else c _should_ be whitespace or , */
865 }
866 }
867
868
869 /*! \brief Return #LeptonPath pointer object from path string.
870 * \par Function Description
871 * Given a path string representation in gEDA file format creates
872 * a #LeptonPath object corresponding to it and returns the
873 * pointer to the object.
874 *
875 * \param path_str [in] The representation of path as a string.
876 * \return The #LeptonPath object pointer.
877 */
878 LeptonPath*
lepton_path_parse(const char * path_str)879 lepton_path_parse (const char *path_str)
880 {
881 RSVGParsePathCtx ctx;
882
883 ctx.path = lepton_path_new ();
884 ctx.cpx = 0.0;
885 ctx.cpy = 0.0;
886 ctx.mpx = 0.0;
887 ctx.mpy = 0.0;
888 ctx.cmd = 0;
889 ctx.param = 0;
890
891 lepton_path_parse_data (&ctx, path_str);
892
893 if (ctx.param)
894 lepton_path_parse_do_cmd (&ctx, TRUE);
895
896 return ctx.path;
897 }
898
899
900 /*! \brief Return string representation of #LeptonPath object.
901 * \par Function Description
902 * Given a #LeptonPath object pointer, returns its representation
903 * as a string in gEDA file format.
904 *
905 * \param path [in] The #LeptonPath object pointer.
906 * \return The string representing the object.
907 */
908 char*
lepton_path_string_from_path(const LeptonPath * path)909 lepton_path_string_from_path (const LeptonPath *path)
910 {
911 LeptonPathSection *section;
912 GString *path_string;
913 int i;
914
915 path_string = g_string_new ("");
916
917 for (i = 0; i < path->num_sections; i++) {
918 section = &path->sections[i];
919
920 if (i > 0)
921 g_string_append_c (path_string, '\n');
922
923 switch (section->code) {
924 case PATH_MOVETO:
925 g_string_append_printf (path_string, "M %i,%i",
926 section->x3, section->y3);
927 break;
928 case PATH_MOVETO_OPEN:
929 g_string_append_printf (path_string, "M %i,%i",
930 section->x3, section->y3);
931 break;
932 case PATH_CURVETO:
933 g_string_append_printf (path_string, "C %i,%i %i,%i %i,%i",
934 section->x1, section->y1,
935 section->x2, section->y2,
936 section->x3, section->y3);
937 break;
938 case PATH_LINETO:
939 g_string_append_printf (path_string, "L %i,%i",
940 section->x3, section->y3);
941 break;
942 case PATH_END:
943 g_string_append_printf (path_string, "z");
944 break;
945 }
946 }
947
948 return g_string_free (path_string, FALSE);
949 }
950
951 /*! \brief Converts a path to a polygon
952 *
953 * \param path [in] The path to convert to a polygon. This parameter must not
954 * be NULL.
955 * \param points [out] An array of the polygon's vertices. This parameter
956 * must not be NULL.
957 * \return TRUE if the path is closed, FALSE if it is open.
958 */
959 int
lepton_path_to_polygon(LeptonPath * path,GArray * points)960 lepton_path_to_polygon (LeptonPath *path,
961 GArray *points)
962 {
963 int closed = FALSE;
964 int i;
965 LeptonPoint point = { 0, 0 };
966
967 if (points->len > 0) {
968 g_array_remove_range (points, 0, points->len - 1);
969 }
970
971 for (i = 0; i < path->num_sections; i++) {
972 LeptonBezier bezier;
973 LeptonPathSection *section = &path->sections[i];
974
975 switch (section->code) {
976 case PATH_CURVETO:
977 bezier.x[0] = point.x;
978 bezier.y[0] = point.y;
979 bezier.x[1] = section->x1;
980 bezier.y[1] = section->y1;
981 bezier.x[2] = section->x2;
982 bezier.y[2] = section->y2;
983 point.x = bezier.x[3] = section->x3;
984 point.y = bezier.y[3] = section->y3;
985 m_polygon_append_bezier (points, &bezier, NUM_BEZIER_SEGMENTS);
986 break;
987
988 case PATH_MOVETO_OPEN:
989 /* Unsupported, just fall through and draw a line */
990 /* Fall through */
991
992 case PATH_MOVETO:
993 case PATH_LINETO:
994 point.x = section->x3;
995 point.y = section->y3;
996 m_polygon_append_point (points, point.x, point.y);
997 break;
998
999 case PATH_END:
1000 closed = TRUE;
1001 break;
1002 }
1003 }
1004
1005 return closed;
1006 }
1007
1008
1009 /*! \brief Calculates the distance between the given point and the closest
1010 * point on the given path segment.
1011 *
1012 * \param [in] path The path.
1013 * \param [in] x The x coordinate of the given point.
1014 * \param [in] y The y coordinate of the given point.
1015 * \param [in] solid TRUE if the path should be treated as solid, FALSE if
1016 * the path should be treated as hollow.
1017 * \return The shortest distance from the path to the point. With a solid
1018 * shape, this function returns a distance of zero for interior points. With
1019 * an invalid parameter, this function returns G_MAXDOUBLE.
1020 */
1021 double
lepton_path_shortest_distance(LeptonPath * path,int x,int y,int solid)1022 lepton_path_shortest_distance (LeptonPath *path,
1023 int x,
1024 int y,
1025 int solid)
1026 {
1027 double shortest_distance = G_MAXDOUBLE;
1028 int closed;
1029 GArray *points;
1030
1031 points = g_array_new (FALSE, FALSE, sizeof (LeptonPoint));
1032
1033 closed = lepton_path_to_polygon (path, points);
1034
1035 if (!solid) {
1036 shortest_distance = m_polygon_shortest_distance (points, x, y, closed);
1037
1038 } else if (m_polygon_interior_point (points, x, y)) {
1039 shortest_distance = 0;
1040
1041 } else {
1042 shortest_distance = m_polygon_shortest_distance (points, x, y, TRUE);
1043 }
1044
1045 g_array_free (points, TRUE);
1046
1047 return shortest_distance;
1048 }
1049