[AdgToyText] Refactored using maps
[adg.git] / adg / adg-path.c
blob5fc63ba843c534c43308642e4ce235cf6239d430
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version. *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
20 /**
21 * SECTION:adg-path
22 * @short_description: The basic model representing a generic path
24 * The #AdgPath model is a virtual path: in a few words, it is a
25 * simple conceptual #cairo_path_t struct. This class implements
26 * methods to manipulate the underlying cairo path.
28 * Although some of the provided methods are clearly based on the
29 * original cairo path manipulation API, their behavior could be
30 * sligthly different. This is intentional, because the ADG provides
31 * additional path manipulation algorithms, sometime quite complex,
32 * and a more restrictive filter on the path quality is required.
33 * Also, the ADG is designed to be used by technicians while cairo
34 * targets a broader range of developers.
36 * As an example, following the rule of the less surprise, some
37 * cairo functions guess the current point when it is not defined,
38 * while the #AdgPath methods trigger a warning without other effect.
39 * Furthermore, after a cairo_path_close_path() call a MOVE_TO
40 * primitive to the starting point of the segment is automatically
41 * added by cairo while in the ADG, after an adg_path_close(), the
42 * current point is simply unset.
43 **/
45 /**
46 * AdgPath:
48 * All fields are private and should not be used directly.
49 * Use its public methods instead.
50 **/
53 #include "adg-path.h"
54 #include "adg-path-private.h"
55 #include "adg-primitive.h"
56 #include "adg-intl.h"
58 #include <math.h>
61 static void finalize (GObject *object);
62 static void changed (AdgModel *model);
63 static void clear_cairo_path (AdgPath *path);
64 static cairo_path_t * get_cairo_path (AdgPath *path);
65 static cairo_path_t * get_cpml_path (AdgPath *path);
66 static GArray * arc_to_curves (GArray *array,
67 const cairo_path_data_t
68 *src);
69 static void append_primitive (AdgPath *path,
70 AdgPrimitive *primitive);
71 static gint needed_pairs (CpmlPrimitiveType type,
72 gboolean cp_is_valid);
73 static void clear_operation (AdgPath *path);
74 static gboolean append_operation (AdgPath *path,
75 AdgOperator operator,
76 ...);
77 static void do_operation (AdgPath *path,
78 cairo_path_data_t
79 *path_data);
80 static void do_chamfer (AdgPath *path,
81 CpmlPrimitive *current);
82 static void do_fillet (AdgPath *path,
83 CpmlPrimitive *current);
84 static gboolean is_convex (const CpmlPrimitive
85 *primitive1,
86 const CpmlPrimitive
87 *primitive2);
90 G_DEFINE_TYPE(AdgPath, adg_path, ADG_TYPE_MODEL);
93 static void
94 adg_path_class_init(AdgPathClass *klass)
96 GObjectClass *gobject_class;
97 AdgModelClass *model_class;
99 gobject_class = (GObjectClass *) klass;
100 model_class = (AdgModelClass *) klass;
102 g_type_class_add_private(klass, sizeof(AdgPathPrivate));
104 gobject_class->finalize = finalize;
106 model_class->changed = changed;
109 static void
110 adg_path_init(AdgPath *path)
112 AdgPathPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(path, ADG_TYPE_PATH,
113 AdgPathPrivate);
115 data->cp_is_valid = FALSE;
116 data->path = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
117 data->cairo_path.status = CAIRO_STATUS_INVALID_PATH_DATA;
118 data->cairo_path.data = NULL;
119 data->cairo_path.num_data = 0;
120 data->operation.operator = ADG_OPERATOR_NONE;
122 path->data = data;
125 static void
126 finalize(GObject *object)
128 AdgPath *path;
129 AdgPathPrivate *data;
130 GObjectClass *object_class;
132 path = (AdgPath *) object;
133 data = path->data;
134 object_class = (GObjectClass *) adg_path_parent_class;
136 g_array_free(data->path, TRUE);
137 clear_cairo_path(path);
138 clear_operation(path);
140 if (object_class->finalize != NULL)
141 object_class->finalize(object);
146 * adg_path_new:
148 * Creates a new path model. The path must be constructed in the @callback
149 * function: AdgPath will cache and reuse the cairo_copy_path() returned by
150 * the cairo context after the @callback call.
152 * Return value: the new model
154 AdgModel *
155 adg_path_new(void)
157 return (AdgModel *) g_object_new(ADG_TYPE_PATH, NULL);
162 * adg_path_get_cairo_path:
163 * @path: an #AdgPath
165 * Gets a pointer to the cairo path structure of @path. The return value
166 * is owned by @path and must be considered read-only.
168 * This function also converts %CAIRO_PATH_ARC_TO primitives, not
169 * recognized by cairo, into approximated Bézier curves. The conversion
170 * is cached so any furter request is O(1). This cache is cleared
171 * whenever @path is modified (by adding a new primitive or by calling
172 * adg_path_clear()).
174 * <important>
175 * <title>TODO</title>
176 * <itemizedlist>
177 * <listitem>Actually, the arcs are approximated to Bézier using the
178 * hardcoded max angle of PI/2. This should be customizable
179 * by adding, for instance, a property to the #AdgPath class
180 * with a default value of PI/2.</listitem>
181 * </itemizedlist>
182 * </important>
184 * Return value: a pointer to the internal cairo path or %NULL on errors
186 const cairo_path_t *
187 adg_path_get_cairo_path(AdgPath *path)
189 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
191 return get_cairo_path(path);
195 * adg_path_get_cpml_path:
196 * @path: an #AdgPath
198 * Gets a pointer to the cairo path structure of @path. The return
199 * value is owned by @path and must not be freed.
201 * This function is similar to adg_path_get_cairo_path() but with
202 * two important differences: firstly the arc primitives are not
203 * expanded to Bézier curves and secondly the returned path is
204 * not read-only. This means it is allowed to modify the returned
205 * path as long as its size is retained and its data contains a
206 * valid path.
208 * Keep in mind any changes to @path makes the value returned by
209 * this function useless, as it is likely to contain plain garbage.
211 * Return value: a pointer to the internal cpml path or %NULL on errors
213 cairo_path_t *
214 adg_path_get_cpml_path(AdgPath *path)
216 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
218 clear_cairo_path(path);
220 return get_cpml_path(path);
224 * adg_path_get_current_point:
225 * @path: an #AdgPath
226 * @x: return value for x coordinate of the current point
227 * @y: return value for y coordinate of the current point
229 * Gets the current point of @path, which is conceptually the
230 * final point reached by the path so far.
232 * If there is no defined current point, @x and @y will both be set
233 * to 0 and a warning will be triggered. It is possible to check this
234 * in advance with adg_path_has_current_point().
236 * Most #AdgPath methods alter the current point and most of them
237 * expect a current point to be defined otherwise will fail triggering
238 * a warning. Check the description of every method for specific details.
240 void
241 adg_path_get_current_point(AdgPath *path, gdouble *x, gdouble *y)
243 AdgPathPrivate *data;
245 g_return_if_fail(ADG_IS_PATH(path));
247 data = path->data;
249 if (data->cp_is_valid) {
250 *x = data->cp.x;
251 *y = data->cp.y;
252 } else {
253 *x = *y = 0.;
254 g_return_if_reached();
259 * adg_path_has_current_point:
260 * @path: an #AdgPath
262 * Returns whether a current point is defined on @path.
263 * See adg_path_get_current_point() for details on the current point.
265 * Return value: whether a current point is defined
267 gboolean
268 adg_path_has_current_point(AdgPath *path)
270 AdgPathPrivate *data;
272 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
274 data = path->data;
276 return data->cp_is_valid;
280 * adg_path_clear:
281 * @path: an #AdgPath
283 * Releases the internal memory hold by @path and resets its status,
284 * so that after this call @path contains an empty path.
286 void
287 adg_path_clear(AdgPath *path)
289 AdgPathPrivate *data;
291 g_return_if_fail(ADG_IS_PATH(path));
293 data = path->data;
295 g_array_set_size(data->path, 0);
296 clear_cairo_path(path);
297 clear_operation(path);
302 * adg_path_append:
303 * @path: an #AdgPath
304 * @type: a #cairo_data_type_t value
305 * @...: point data, specified as #AdgPair pointers
307 * Generic method to append a primitive to @path. The number of #AdgPair
308 * structs depends on @type: there is no way with this function to
309 * reserve more cairo_path_data_t structs than what is needed by the
310 * primitive.
312 * This function accepts also the special %CAIRO_PATH_ARC_TO primitive.
314 * If @path has no current point while the requested primitive needs it,
315 * a warning message will be triggered without other effect.
317 void
318 adg_path_append(AdgPath *path, CpmlPrimitiveType type, ...)
320 va_list var_args;
322 va_start(var_args, type);
323 adg_path_append_valist(path, type, var_args);
324 va_end(var_args);
328 * adg_path_append_valist:
329 * @path: an #AdgPath
330 * @type: a #cairo_data_type_t value
331 * @var_args: point data, specified as #AdgPair pointers
333 * va_list version of adg_path_append().
335 void
336 adg_path_append_valist(AdgPath *path, CpmlPrimitiveType type, va_list var_args)
338 AdgPathPrivate *data;
339 AdgPrimitive primitive;
340 gint length, cnt;
341 cairo_path_data_t org;
342 cairo_path_data_t *path_data;
344 g_return_if_fail(ADG_IS_PATH(path));
346 data = path->data;
347 length = needed_pairs(type, data->cp_is_valid);
348 if (length == 0)
349 return;
351 /* Set a copy of the current point as the primitive origin */
352 cpml_pair_to_cairo(&data->cp, &org);
353 primitive.org = &org;
355 /* Build the cairo_path_data_t array */
356 primitive.data = path_data = g_new(cairo_path_data_t, length);
358 path_data->header.type = type;
359 path_data->header.length = length;
361 for (cnt = 1; cnt < length; ++ cnt) {
362 ++ path_data;
363 cpml_pair_to_cairo(va_arg(var_args, AdgPair *), path_data);
366 /* Terminate the creation of the temporary primitive */
367 primitive.segment = NULL;
369 /* Append this primitive to @path */
370 append_primitive(path, &primitive);
372 g_free(primitive.data);
376 * adg_path_append_primitive:
377 * @path: an #AdgPath
378 * @primitive: the #AdgPrimitive to append
380 * Appends @primitive to @path. The primitive to add is considered the
381 * continuation of the current path so the <structfield>org</structfield>
382 * component of @primitive is not used. Anyway the current poins is
383 * checked against it: they must be equal or the function will fail
384 * without further processing.
386 void
387 adg_path_append_primitive(AdgPath *path, const AdgPrimitive *primitive)
389 AdgPathPrivate *data;
390 AdgPrimitive *primitive_dup;
392 g_return_if_fail(ADG_IS_PATH(path));
393 g_return_if_fail(primitive != NULL);
395 data = path->data;
397 g_return_if_fail(primitive->org->point.x == data->cp.x &&
398 primitive->org->point.y == data->cp.y);
400 /* The primitive data could be modified by pending operations:
401 * work on a copy */
402 primitive_dup = adg_primitive_deep_dup(primitive);
404 append_primitive(path, primitive_dup);
406 g_free(primitive_dup);
410 * adg_path_append_segment:
411 * @path: an #AdgPath
412 * @segment: the #AdgSegment to append
414 * Appends @segment to @path.
416 void
417 adg_path_append_segment(AdgPath *path, const AdgSegment *segment)
419 AdgPathPrivate *data;
421 g_return_if_fail(ADG_IS_PATH(path));
422 g_return_if_fail(segment != NULL);
424 data = path->data;
426 clear_cairo_path(path);
427 data->path = g_array_append_vals(data->path,
428 segment->data, segment->num_data);
432 * adg_path_append_cairo_path:
433 * @path: an #AdgPath
434 * @cairo_path: the #cairo_path_t path to append
436 * Appends a whole cairo path to @path.
438 void
439 adg_path_append_cairo_path(AdgPath *path, const cairo_path_t *cairo_path)
441 AdgPathPrivate *data;
443 g_return_if_fail(ADG_IS_PATH(path));
445 data = path->data;
447 clear_cairo_path(path);
448 data->path = g_array_append_vals(data->path,
449 cairo_path->data, cairo_path->num_data);
453 * adg_path_move_to:
454 * @path: an #AdgPath
455 * @x: the new x coordinate
456 * @y: the new y coordinate
458 * Begins a new segment. After this call the current point will be (@x, @y).
460 void
461 adg_path_move_to(AdgPath *path, gdouble x, gdouble y)
463 AdgPair p;
465 p.x = x;
466 p.y = y;
468 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p);
472 * adg_path_line_to:
473 * @path: an #AdgPath
474 * @x: the new x coordinate
475 * @y: the new y coordinate
477 * Adds a line to @path from the current point to position (@x, @y).
478 * After this call the current point will be (@x, @y).
480 * If @path has no current point before this call, this function will
481 * trigger a warning without other effect.
483 void
484 adg_path_line_to(AdgPath *path, gdouble x, gdouble y)
486 AdgPair p;
488 p.x = x;
489 p.y = y;
491 adg_path_append(path, CAIRO_PATH_LINE_TO, &p);
495 * adg_path_arc_to:
496 * @path: an #AdgPath
497 * @x1: the x coordinate of an intermediate point
498 * @y1: the y coordinate of an intermediate point
499 * @x2: the x coordinate of the end of the arc
500 * @y2: the y coordinate of the end of the arc
502 * Adds an arc to the path from the current point to (@x2, @y2),
503 * passing throught (@x1, @y1). After this call the current point
504 * will be (@x2, @y2).
506 * If @path has no current point before this call, this function will
507 * trigger a warning without other effect.
509 void
510 adg_path_arc_to(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
512 AdgPair p[2];
514 p[0].x = x1;
515 p[0].y = y1;
516 p[1].x = x2;
517 p[1].y = y2;
519 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[0], &p[1]);
523 * adg_path_curve_to:
524 * @path: an #AdgPath
525 * @x1: the x coordinate of the first control point
526 * @y1: the y coordinate of the first control point
527 * @x2: the x coordinate of the second control point
528 * @y2: the y coordinate of the second control point
529 * @x3: the x coordinate of the end of the curve
530 * @y3: the y coordinate of the end of the curve
532 * Adds a cubic Bézier curve to the path from the current point to
533 * position (@x3, @y3), using (@x1, @y1) and (@x2, @y2) as the
534 * control points. After this call the current point will be (@x3, @y3).
536 * If @path has no current point before this call, this function will
537 * trigger a warning without other effect.
539 void
540 adg_path_curve_to(AdgPath *path, gdouble x1, gdouble y1,
541 gdouble x2, gdouble y2, gdouble x3, gdouble y3)
543 AdgPair p[3];
545 p[0].x = x1;
546 p[0].y = y1;
547 p[1].x = x2;
548 p[1].y = y2;
549 p[2].x = x3;
550 p[2].y = y3;
552 adg_path_append(path, CAIRO_PATH_CURVE_TO, &p[0], &p[1], &p[2]);
556 * adg_path_close:
557 * @path: an #AdgPath
559 * Adds a line segment to the path from the current point to the
560 * beginning of the current segment, (the most recent point passed
561 * to an adg_path_move_to()), and closes this segment.
562 * After this call the current point will be unset.
564 * The behavior of adg_path_close() is distinct from simply calling
565 * adg_line_to() with the coordinates of the segment starting point.
566 * When a closed segment is stroked, there are no caps on the ends.
567 * Instead, there is a line join connecting the final and initial
568 * primitive of the segment.
570 * If @path has no current point before this call, this function will
571 * trigger a warning without other effect.
573 void
574 adg_path_close(AdgPath *path)
576 adg_path_append(path, CAIRO_PATH_CLOSE_PATH);
580 * adg_path_arc
581 * @path: an #AdgPath
582 * @xc: x position of the center of the arc
583 * @yc: y position of the center of the arc
584 * @r: the radius of the arc
585 * @start: the start angle, in radians
586 * @end: the end angle, in radians
588 * A more usual way to add an arc to @path. After this call, the current
589 * point will be the computed end point of the arc. The arc will be
590 * rendered in increasing angle, accordling to @start and @end. This means
591 * if @start is less than @end, the arc will be rendered in clockwise
592 * direction (accordling to the default cairo coordinate system) while if
593 * @start is greather than @end, the arc will be rendered in couterclockwise
594 * direction.
596 * By explicitely setting the whole arc data, the start point could be
597 * different from the current point. In this case, if @path has no
598 * current point before the call a %CAIRO_PATH_MOVE_TO to the start
599 * point of the arc will be automatically prepended to the arc.
600 * If @path has a current point, a %CAIRO_PATH_LINE_TO to the start
601 * point of the arc will be used instead of the moveto.
603 void
604 adg_path_arc(AdgPath *path, gdouble xc, gdouble yc, gdouble r,
605 gdouble start, gdouble end)
607 AdgPathPrivate *data;
608 AdgPair center, p[3];
610 g_return_if_fail(ADG_IS_PATH(path));
612 data = path->data;
613 center.x = xc;
614 center.y = yc;
616 cpml_vector_from_angle(&p[0], start, r);
617 cpml_vector_from_angle(&p[1], (end-start) / 2, r);
618 cpml_vector_from_angle(&p[2], end, r);
620 cpml_pair_add(&p[0], &center);
621 cpml_pair_add(&p[1], &center);
622 cpml_pair_add(&p[2], &center);
624 if (!data->cp_is_valid)
625 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p[0]);
626 else if (p[0].x != data->cp.x || p[0].y != data->cp.y)
627 adg_path_append(path, CAIRO_PATH_LINE_TO, &p[0]);
629 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[1], &p[2]);
633 * adg_path_chamfer
634 * @path: an #AdgPath
635 * @delta1: the distance from the intersection point of the current primitive
636 * @delta2: the distance from the intersection point of the next primitive
638 * A binary operator that generates a chamfer between two primitives.
639 * The first primitive involved is the current primitive, the second will
640 * be the next primitive appended to @path after this call. The second
641 * primitive is required: if the chamfer operation is not properly
642 * terminated (by not providing the second primitive), any API accessing
643 * the path in reading mode will raise a warning.
645 * The chamfer operation requires two lengths: @delta1 specifies the
646 * "quantity" to trim on the first primitive while @delta2 is the same
647 * applied on the second primitive. The term "quantity" means the length
648 * of the portion to cut out from the original primitive (that is the
649 * primitive as would be without the chamfer).
651 void
652 adg_path_chamfer(AdgPath *path, gdouble delta1, gdouble delta2)
654 g_return_if_fail(ADG_IS_PATH(path));
656 if (!append_operation(path, ADG_OPERATOR_CHAMFER, delta1, delta2))
657 return;
661 * adg_path_fillet:
662 * @path: an #AdgPath
663 * @radius: the radius of the fillet
666 * A binary operator that joins to primitives with an arc.
667 * The first primitive involved is the current primitive, the second will
668 * be the next primitive appended to @path after this call. The second
669 * primitive is required: if the fillet operation is not properly
670 * terminated (by not providing the second primitive), any API accessing
671 * the path in reading mode will raise a warning.
673 void
674 adg_path_fillet(AdgPath *path, gdouble radius)
676 g_return_if_fail(ADG_IS_PATH(path));
678 if (!append_operation(path, ADG_OPERATOR_FILLET, radius))
679 return;
684 * adg_path_dump:
685 * @path: an #AdgPath
687 * Dumps the data content of @path to stdout in a human readable format.
689 void
690 adg_path_dump(AdgPath *path)
692 CpmlSegment segment;
693 cairo_path_t *cairo_path;
695 g_return_if_fail(ADG_IS_PATH(path));
697 cairo_path = get_cairo_path(path);
699 g_return_if_fail(cairo_path != NULL);
701 if (!cpml_segment_from_cairo(&segment, cairo_path)) {
702 g_warning("Invalid path data to dump!\n");
703 } else {
704 do {
705 cpml_segment_dump(&segment);
706 } while (cpml_segment_next(&segment));
711 static void
712 changed(AdgModel *model)
714 AdgModelClass *model_class = (AdgModelClass *) adg_path_parent_class;
716 adg_path_clear((AdgPath *) model);
718 if (model_class->changed != NULL)
719 model_class->changed(model);
722 static void
723 clear_cairo_path(AdgPath *path)
725 AdgPathPrivate *data;
726 cairo_path_t *cairo_path;
728 data = path->data;
729 cairo_path = &data->cairo_path;
731 if (cairo_path->data == NULL)
732 return;
734 g_free(cairo_path->data);
736 cairo_path->status = CAIRO_STATUS_INVALID_PATH_DATA;
737 cairo_path->data = NULL;
738 cairo_path->num_data = 0;
741 static cairo_path_t *
742 get_cairo_path(AdgPath *path)
744 AdgPathPrivate *data;
745 cairo_path_t *cairo_path;
746 const GArray *src;
747 GArray *dst;
748 const cairo_path_data_t *p_src;
749 int i;
751 data = path->data;
752 cairo_path = &data->cairo_path;
754 /* Check for cached result */
755 if (cairo_path->data != NULL)
756 return cairo_path;
758 src = data->path;
759 dst = g_array_sized_new(FALSE, FALSE, sizeof(cairo_path_data_t), src->len);
761 /* Cycle the path and convert arcs to Bézier curves */
762 for (i = 0; i < src->len; i += p_src->header.length) {
763 p_src = (const cairo_path_data_t *) src->data + i;
765 if (p_src->header.type == CAIRO_PATH_ARC_TO)
766 dst = arc_to_curves(dst, p_src);
767 else
768 dst = g_array_append_vals(dst, p_src, p_src->header.length);
771 cairo_path->status = CAIRO_STATUS_SUCCESS;
772 cairo_path->num_data = dst->len;
773 cairo_path->data = (cairo_path_data_t *) g_array_free(dst, FALSE);
775 return cairo_path;
778 static cairo_path_t *
779 get_cpml_path(AdgPath *path)
781 AdgPathPrivate *data;
782 cairo_path_t *cpml_path;
784 data = path->data;
785 cpml_path = &data->cpml_path;
787 cpml_path->status = CAIRO_STATUS_SUCCESS;
788 cpml_path->data = (cairo_path_data_t *) data->path->data;
789 cpml_path->num_data = data->path->len;
791 return cpml_path;
794 static GArray *
795 arc_to_curves(GArray *array, const cairo_path_data_t *src)
797 CpmlPrimitive arc;
798 double start, end;
800 /* Build the arc primitive: the arc origin is supposed to be the previous
801 * point (src-1): this means a primitive must exist before the arc */
802 arc.segment = NULL;
803 arc.org = (cairo_path_data_t *) (src-1);
804 arc.data = (cairo_path_data_t *) src;
806 if (cpml_arc_info(&arc, NULL, NULL, &start, &end)) {
807 CpmlSegment segment;
808 int n_curves;
809 cairo_path_data_t *curves;
811 n_curves = ceil(fabs(end-start) / M_PI_2);
812 curves = g_new(cairo_path_data_t, n_curves * 4);
813 segment.data = curves;
814 cpml_arc_to_curves(&arc, &segment, n_curves);
816 array = g_array_append_vals(array, curves, n_curves * 4);
818 g_free(curves);
821 return array;
824 static void
825 append_primitive(AdgPath *path, AdgPrimitive *current)
827 AdgPathPrivate *data;
828 cairo_path_data_t *path_data;
829 int length;
831 data = path->data;
832 path_data = current->data;
833 length = path_data[0].header.length;
835 /* Execute any pending operation */
836 do_operation(path, path_data);
838 /* Append the path data to the internal path array */
839 data->path = g_array_append_vals(data->path, path_data, length);
841 /* Set path data to point to the recently appended cairo_path_data_t
842 * primitive: the first struct is the header */
843 path_data = (cairo_path_data_t *) data->path->data +
844 data->path->len - length;
846 /* Set the last primitive for subsequent binary operations */
847 data->last.org = data->cp_is_valid ? path_data - 1 : NULL;
848 data->last.segment = NULL;
849 data->last.data = path_data;
851 /* Save the last point as the current point, if applicable */
852 data->cp_is_valid = length > 1;
853 if (length > 1)
854 cpml_pair_from_cairo(&data->cp, &path_data[length-1]);
856 /* Invalidate cairo_path: should be recomputed */
857 clear_cairo_path(path);
860 static gint
861 needed_pairs(CpmlPrimitiveType type, gboolean cp_is_valid)
863 switch (type) {
865 case CAIRO_PATH_CLOSE_PATH:
866 g_return_val_if_fail(cp_is_valid, 0);
867 return 1;
869 case CAIRO_PATH_MOVE_TO:
870 return 2;
872 case CAIRO_PATH_LINE_TO:
873 g_return_val_if_fail(cp_is_valid, 0);
874 return 2;
876 case CAIRO_PATH_ARC_TO:
877 g_return_val_if_fail(cp_is_valid, 0);
878 return 3;
880 case CAIRO_PATH_CURVE_TO:
881 g_return_val_if_fail(cp_is_valid, 0);
882 return 4;
884 default:
885 g_return_val_if_reached(0);
888 return 0;
891 static void
892 clear_operation(AdgPath *path)
894 AdgPathPrivate *data;
895 AdgOperation *operation;
897 data = path->data;
898 operation = &data->operation;
900 if (operation->operator == ADG_OPERATOR_NONE)
901 return;
903 g_warning("An operation is still active while clearing the path "
904 "(operator `%d')", operation->operator);
905 operation->operator = ADG_OPERATOR_NONE;
908 static gboolean
909 append_operation(AdgPath *path, AdgOperator operator, ...)
911 AdgPathPrivate *data;
912 AdgOperation *operation;
913 va_list var_args;
915 data = path->data;
917 if (!data->cp_is_valid) {
918 g_warning("Operation requested but path has no current primitive "
919 "(operator `%d')", operator);
920 return FALSE;
923 operation = &data->operation;
924 if (operation->operator != ADG_OPERATOR_NONE) {
925 /* TODO: this is a rude semplification, as a lot of operators can
926 * and may cohexist. As an example, a fillet followed by a
927 * polar chamfer is not difficult to compute */
928 g_warning("Operation requested but another operation is yet active"
929 "(operators: new `%d', old `%d')",
930 operator, operation->operator);
931 return FALSE;
934 va_start(var_args, operator);
936 switch (operator) {
938 case ADG_OPERATOR_CHAMFER:
939 operation->data.chamfer.delta1 = va_arg(var_args, double);
940 operation->data.chamfer.delta2 = va_arg(var_args, double);
941 break;
943 case ADG_OPERATOR_FILLET:
944 operation->data.fillet.radius = va_arg(var_args, double);
945 break;
947 case ADG_OPERATOR_NONE:
948 va_end(var_args);
949 return TRUE;
951 default:
952 g_warning("Operation not recognized (operator `%d')", operator);
953 va_end(var_args);
954 return FALSE;
957 operation->operator = operator;
958 va_end(var_args);
960 return TRUE;
963 static void
964 do_operation(AdgPath *path, cairo_path_data_t *path_data)
966 AdgPathPrivate *data;
967 AdgOperator operator;
968 CpmlSegment segment;
969 CpmlPrimitive current;
970 cairo_path_data_t current_org;
972 data = path->data;
973 operator = data->operation.operator;
974 cpml_segment_from_cairo(&segment, get_cpml_path(path));
976 /* Construct the current primitive, that is the primitive to be inserted.
977 * Its org is a copy of the end point of the last primitive: it can be
978 * modified without affecting anything else. It is expected the operation
979 * functions will add to @path the primitives required but NOT to add
980 * @current, as this one will be inserted automatically. */
981 current.segment = &segment;
982 current.org = &current_org;
983 current.data = path_data;
984 cpml_pair_to_cairo(&data->cp, &current_org);
986 switch (operator) {
988 case ADG_OPERATOR_NONE:
989 return;
991 case ADG_OPERATOR_CHAMFER:
992 do_chamfer(path, &current);
993 break;
995 case ADG_OPERATOR_FILLET:
996 do_fillet(path, &current);
997 break;
999 default:
1000 g_warning("Operation not implemented (operator `%d')", operator);
1001 return;
1005 static void
1006 do_chamfer(AdgPath *path, CpmlPrimitive *current)
1008 AdgPathPrivate *data;
1009 CpmlPrimitive *last;
1010 gdouble delta1, delta2;
1011 gdouble len1, len2;
1012 AdgPair pair;
1013 cairo_path_data_t line[2];
1015 data = path->data;
1016 last = &data->last;
1017 delta1 = data->operation.data.chamfer.delta1;
1018 len1 = cpml_primitive_length(last);
1020 if (delta1 >= len1) {
1021 g_warning("Chamfer too big for the last primitive (%lf >= %lf)",
1022 delta1, len1);
1023 return;
1026 delta2 = data->operation.data.chamfer.delta2;
1027 len2 = cpml_primitive_length(current);
1029 if (delta2 >= len2) {
1030 g_warning("Chamfer too big for the current primitive (%lf >= %lf)",
1031 delta2, len2);
1032 return;
1035 /* Change the end point of the last primitive */
1036 cpml_primitive_pair_at(last, &pair, 1. - delta1 / len1);
1037 cpml_pair_to_cairo(&pair, cpml_primitive_get_point(last, -1));
1039 /* Change the start point of the current primitive */
1040 cpml_primitive_pair_at(current, &pair, delta2 / len2);
1041 cpml_pair_to_cairo(&pair, cpml_primitive_get_point(current, 0));
1043 /* Add the chamfer line */
1044 line[0].header.type = CAIRO_PATH_LINE_TO;
1045 line[0].header.length = 2;
1046 line[1].point.x = pair.x;
1047 line[1].point.y = pair.y;
1048 data->path = g_array_append_vals(data->path, line, 2);
1050 data->operation.operator = ADG_OPERATOR_NONE;
1053 static void
1054 do_fillet(AdgPath *path, CpmlPrimitive *current)
1056 AdgPathPrivate *data;
1057 CpmlPrimitive *last, *current_dup, *last_dup;
1058 gdouble radius, offset, pos;
1059 AdgPair center, vector, p[3];
1060 cairo_path_data_t arc[3];
1062 data = path->data;
1063 last = &data->last;
1064 current_dup = adg_primitive_deep_dup(current);
1066 /* Force current_dup to point to the original segment so a
1067 * CAIRO_PATH_CLOSE_PATH primitive will work as expected */
1068 current_dup->segment = current->segment;
1070 last_dup = adg_primitive_deep_dup(last);
1071 radius = data->operation.data.fillet.radius;
1072 offset = is_convex(last_dup, current_dup) ? -radius : radius;
1074 /* Find the center of the fillet from the intersection between
1075 * the last and current primitives offseted by radius */
1076 cpml_primitive_offset(current_dup, offset);
1077 cpml_primitive_offset(last_dup, offset);
1078 if (cpml_primitive_intersection(current_dup, last_dup,
1079 &center, 1) == 0) {
1080 g_warning("Fillet not applicable (radius = %lf)", radius);
1081 g_free(current_dup);
1082 g_free(last_dup);
1083 return;
1086 /* Compute the start point of the fillet */
1087 pos = cpml_primitive_near_pos(last_dup, &center);
1088 cpml_primitive_vector_at(last_dup, &vector, pos);
1089 cpml_vector_set_length(&vector, offset);
1090 cpml_vector_normal(&vector);
1091 cpml_pair_sub(cpml_pair_copy(&p[0], &center), &vector);
1093 /* Compute the mid point of the fillet */
1094 cpml_pair_from_cairo(&vector, current->org);
1095 cpml_pair_sub(&vector, &center);
1096 cpml_vector_set_length(&vector, radius);
1097 cpml_pair_add(cpml_pair_copy(&p[1], &center), &vector);
1099 /* Compute the end point of the fillet */
1100 pos = cpml_primitive_near_pos(current_dup, &center);
1101 cpml_primitive_vector_at(current_dup, &vector, pos);
1102 cpml_vector_set_length(&vector, offset);
1103 cpml_vector_normal(&vector);
1104 cpml_pair_sub(cpml_pair_copy(&p[2], &center), &vector);
1106 g_free(current_dup);
1107 g_free(last_dup);
1109 /* Modify the end point of the last primitive */
1110 cpml_pair_to_cairo(&p[0], cpml_primitive_get_point(last, -1));
1112 /* Add the fillet arc */
1113 arc[0].header.type = CAIRO_PATH_ARC_TO;
1114 arc[0].header.length = 3;
1115 cpml_pair_to_cairo(&p[1], &arc[1]);
1116 cpml_pair_to_cairo(&p[2], &arc[2]);
1117 data->path = g_array_append_vals(data->path, arc, 3);
1119 data->operation.operator = ADG_OPERATOR_NONE;
1122 static gboolean
1123 is_convex(const CpmlPrimitive *primitive1, const CpmlPrimitive *primitive2)
1125 CpmlVector v1, v2;
1126 gdouble angle1, angle2;
1128 cpml_primitive_vector_at(primitive1, &v1, -1);
1129 cpml_primitive_vector_at(primitive2, &v2, 0);
1131 /* Probably there is a smarter way to get this without trygonometry */
1132 angle1 = cpml_vector_angle(&v1);
1133 angle2 = cpml_vector_angle(&v2);
1135 if (angle1 > angle2)
1136 angle1 -= M_PI*2;
1138 return angle2-angle1 > M_PI;