adg: return NULL instead of invalid data from adg_path_last_primitive
[adg.git] / src / adg / adg-path.c
blobcdcfba68384e85d6969b8d15bd51a0ed6aa6eb74
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2015 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.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-path
23 * @short_description: The basic model representing a generic path
25 * The #AdgPath model represents a virtual #cairo_path_t: this class
26 * implements methods to create the path and provides additional
27 * operations specific to technical drawings.
29 * #AdgPath overrides the <function>get_cairo_path</function> method
30 * of the parent #AdgTrail class, avoiding the need of an
31 * #AdgTrailCallback. The path is constructed programmatically: keep
32 * in mind any method that modifies the path will invalidate the
33 * #cairo_path_t returned by adg_trail_get_cairo_path().
35 * Although some of the provided methods are clearly based on the
36 * original cairo path manipulation API, their behavior could be
37 * sligthly different. This is intentional, because the ADG provides
38 * additional path manipulation algorithms, sometime quite complex,
39 * and a more restrictive filter on the path quality is required.
40 * Also, the ADG is designed to be used by technicians while cairo
41 * targets a broader range of developers.
43 * As an example, following the rule of the less surprise, some
44 * cairo functions guess the current point when it is not defined,
45 * while the #AdgPath methods trigger a warning without other effect.
46 * Furthermore, after cairo_close_path() a %CPML_MOVE primitive to
47 * the starting point of the segment is automatically added by cairo;
48 * in ADG, after an adg_path_close() the current point is unset.
50 * Since: 1.0
51 **/
53 /**
54 * AdgPath:
56 * All fields are private and should not be used directly.
57 * Use its public methods instead.
59 * Since: 1.0
60 **/
63 #include "adg-internal.h"
65 #include "adg-model.h"
66 #include "adg-trail.h"
68 #include "adg-path.h"
69 #include "adg-path-private.h"
72 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_path_parent_class)
73 #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_path_parent_class)
75 #define REMAPPED(ptr, from, to) \
76 (ptr) == NULL ? NULL : \
77 (gpointer) ((to) + ((gchar *) (ptr) - (gchar *) (from)))
80 G_DEFINE_TYPE(AdgPath, adg_path, ADG_TYPE_TRAIL)
83 static void _adg_finalize (GObject *object);
84 static void _adg_clear (AdgModel *model);
85 static void _adg_clear_parent (AdgModel *model);
86 static void _adg_changed (AdgModel *model);
87 static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail);
88 static cairo_path_t * _adg_read_cairo_path (AdgPath *path);
89 static gint _adg_primitive_length (CpmlPrimitiveType type);
90 static void _adg_primitive_remap (CpmlPrimitive *primitive,
91 gpointer to,
92 const CpmlPrimitive
93 *old,
94 gconstpointer from);
95 static void _adg_append_primitive (AdgPath *path,
96 CpmlPrimitive *primitive);
97 static void _adg_clear_operation (AdgPath *path);
98 static gboolean _adg_append_operation (AdgPath *path,
99 AdgAction action,
100 ...);
101 static void _adg_do_operation (AdgPath *path,
102 cairo_path_data_t
103 *path_data);
104 static void _adg_do_action (AdgPath *path,
105 AdgAction action,
106 CpmlPrimitive *primitive);
107 static void _adg_do_chamfer (AdgPath *path,
108 CpmlPrimitive *current);
109 static void _adg_do_fillet (AdgPath *path,
110 CpmlPrimitive *current);
111 static gboolean _adg_is_convex (const CpmlPrimitive
112 *primitive1,
113 const CpmlPrimitive
114 *primitive2);
115 static const gchar * _adg_action_name (AdgAction action);
116 static void _adg_get_named_pair (AdgModel *model,
117 const gchar *name,
118 CpmlPair *pair,
119 gpointer user_data);
120 static void _adg_dup_reverse_named_pairs
121 (AdgModel *model,
122 const cairo_matrix_t
123 *matrix);
126 static void
127 adg_path_class_init(AdgPathClass *klass)
129 GObjectClass *gobject_class;
130 AdgModelClass *model_class;
131 AdgTrailClass *trail_class;
133 gobject_class = (GObjectClass *) klass;
134 model_class = (AdgModelClass *) klass;
135 trail_class = (AdgTrailClass *) klass;
137 g_type_class_add_private(klass, sizeof(AdgPathPrivate));
139 gobject_class->finalize = _adg_finalize;
141 model_class->clear = _adg_clear;
142 model_class->changed = _adg_changed;
144 trail_class->get_cairo_path = _adg_get_cairo_path;
147 static void
148 adg_path_init(AdgPath *path)
150 AdgPathPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(path, ADG_TYPE_PATH,
151 AdgPathPrivate);
153 data->cp_is_valid = FALSE;
154 data->cp.x = 0;
155 data->cp.y = 0;
156 data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA;
157 data->cairo.path.data = NULL;
158 data->cairo.path.num_data = 0;
159 data->cairo.array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
160 data->last.segment = NULL;
161 data->last.org = NULL;
162 data->last.data = NULL;
163 data->over.segment = NULL;
164 data->over.org = NULL;
165 data->over.data = NULL;
166 data->operation.action = ADG_ACTION_NONE;
168 path->data = data;
171 static void
172 _adg_finalize(GObject *object)
174 AdgPath *path;
175 AdgPathPrivate *data;
177 path = (AdgPath *) object;
178 data = path->data;
180 g_array_free(data->cairo.array, TRUE);
181 _adg_clear_operation(path);
183 if (_ADG_OLD_OBJECT_CLASS->finalize)
184 _ADG_OLD_OBJECT_CLASS->finalize(object);
189 * adg_path_new:
191 * Creates a new path model. The path should be constructed
192 * programmatically by using the methods provided by #AdgPath.
194 * Returns: the newly created path model
196 * Since: 1.0
198 AdgPath *
199 adg_path_new(void)
201 return g_object_new(ADG_TYPE_PATH, NULL);
205 * adg_path_get_current_point:
206 * @path: an #AdgPath
208 * Gets the current point of @path, which is conceptually the
209 * final point reached by the path so far.
211 * If there is no defined current point, <constant>NULL</constant> is returned.
212 * It is possible to check this in advance with
213 * adg_path_has_current_point().
215 * Most #AdgPath methods alter the current point and most of them
216 * expect a current point to be defined otherwise will fail triggering
217 * a warning. Check the description of every method for specific details.
219 * Returns: (transfer none): the current point or <constant>NULL</constant> on no current point set or errors.
221 * Since: 1.0
223 const CpmlPair *
224 adg_path_get_current_point(AdgPath *path)
226 AdgPathPrivate *data;
228 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
230 data = path->data;
232 if (!data->cp_is_valid)
233 return NULL;
235 return &data->cp;
239 * adg_path_has_current_point:
240 * @path: an #AdgPath
242 * Returns whether a current point is defined on @path.
243 * See adg_path_get_current_point() for details on the current point.
245 * Returns: whether a current point is defined
247 * Since: 1.0
249 gboolean
250 adg_path_has_current_point(AdgPath *path)
252 AdgPathPrivate *data;
254 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
256 data = path->data;
258 return data->cp_is_valid;
262 * adg_path_last_primitive:
263 * @path: an #AdgPath
265 * Gets the last primitive appended to @path. The returned struct
266 * is owned by @path and should not be freed or modified.
268 * Returns: (transfer none): a pointer to the last appended primitive or <constant>NULL</constant> on no last primitive or on errors.
270 * Since: 1.0
272 const CpmlPrimitive *
273 adg_path_last_primitive(AdgPath *path)
275 AdgPathPrivate *data;
277 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
279 data = path->data;
281 /* Directly return NULL instead of returning an undefined primitive */
282 if (data->last.org == NULL || data->last.data == NULL)
283 return NULL;
285 return &data->last;
289 * adg_path_over_primitive:
290 * @path: an #AdgPath
292 * Gets the primitive before the last one appended to @path. The
293 * "over" term comes from forth, where the <emphasis>OVER</emphasis>
294 * operator works on the stack in the same way as
295 * adg_path_over_primitive() works on @path. The returned struct
296 * is owned by @path and should not be freed or modified.
298 * Returns: (transfer none): a pointer to the primitive before the last appended one or <constant>NULL</constant> on errors.
300 * Since: 1.0
302 const CpmlPrimitive *
303 adg_path_over_primitive(AdgPath *path)
305 AdgPathPrivate *data;
307 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
309 data = path->data;
311 return &data->over;
315 * adg_path_append:
316 * @path: an #AdgPath
317 * @type: a #cairo_data_type_t value
318 * @...: point data, specified as #CpmlPair pointers
320 * Generic method to append a primitive to @path. The number of #CpmlPair
321 * pointers to pass as @Varargs depends on @type: %CPML_CLOSE does not
322 * require any pair, %CPML_MOVE and %CPML_LINE require one pair,
323 * %CPML_ARC two pairs, %CPML_CURVE three pairs and so on.
325 * All the needed pairs must be not <constant>NULL</constant> pointers,
326 * otherwise the function will fail. The pairs in excess, if any, are ignored.
328 * Since: 1.0
330 void
331 adg_path_append(AdgPath *path, CpmlPrimitiveType type, ...)
333 va_list var_args;
335 va_start(var_args, type);
336 adg_path_append_valist(path, type, var_args);
337 va_end(var_args);
341 * adg_path_append_valist:
342 * @path: an #AdgPath
343 * @type: a #cairo_data_type_t value
344 * @var_args: point data, specified as #CpmlPair pointers
346 * va_list version of adg_path_append().
348 * Since: 1.0
350 void
351 adg_path_append_valist(AdgPath *path, CpmlPrimitiveType type, va_list var_args)
353 GArray *array;
354 CpmlPair *pair;
355 gint length;
357 length = _adg_primitive_length(type);
358 if (length == 0)
359 return;
361 array = g_array_new(TRUE, FALSE, sizeof(pair));
362 while (-- length) {
363 pair = va_arg(var_args, CpmlPair *);
364 g_array_append_val(array, pair);
367 adg_path_append_array(path, type, (const CpmlPair **) array->data);
368 g_array_free(array, TRUE);
372 * adg_path_append_array: (rename-to adg_path_append)
373 * @path: an #AdgPath
374 * @type: a #cairo_data_type_t value
375 * @pairs: (array zero-terminated=1) (element-type Cpml.Pair) (transfer none): point data, specified as a <constant>NULL</constant> terminated array of #CpmlPair pointers.
377 * A bindingable version of adg_path_append() that uses a
378 * <constant>NULL</constant> terminated array of pairs instead of variable
379 * argument list and friends.
381 * Furthermore, because of the list is <constant>NULL</constant> terminated,
382 * an arbitrary number of pairs can be passed in @pairs. This allows to embed
383 * in a primitive element more data pairs than requested, something impossible
384 * to do with adg_path_append() and adg_path_append_valist().
386 * Since: 1.0
388 void
389 adg_path_append_array(AdgPath *path, CpmlPrimitiveType type,
390 const CpmlPair **pairs)
392 gint length;
393 GArray *array;
394 const CpmlPair **pair;
395 cairo_path_data_t path_data;
397 g_return_if_fail(ADG_IS_PATH(path));
398 g_return_if_fail(pairs != NULL);
400 length = _adg_primitive_length(type);
401 if (length == 0)
402 return;
404 array = g_array_new(FALSE, FALSE, sizeof(path_data));
405 for (pair = pairs; *pair != NULL; ++ pair) {
406 cpml_pair_to_cairo(*pair, &path_data);
407 g_array_append_val(array, path_data);
410 if (array->len < length - 1) {
411 /* Not enough pairs have been provided */
412 g_warning(_("%s: null pair caught while parsing arguments"), G_STRLOC);
413 } else {
414 AdgPathPrivate *data;
415 CpmlPrimitive primitive;
416 cairo_path_data_t org;
418 /* Save a copy of the current point as primitive origin */
419 data = path->data;
420 cpml_pair_to_cairo(&data->cp, &org);
422 /* Prepend the cairo header */
423 path_data.header.type = type;
424 path_data.header.length = array->len + 1;
425 g_array_prepend_val(array, path_data);
427 /* Append a new primitive to @path */
428 primitive.segment = NULL;
429 primitive.org = &org;
430 primitive.data = (cairo_path_data_t *) array->data;
431 _adg_append_primitive(path, &primitive);
434 g_array_free(array, TRUE);
439 * adg_path_append_primitive:
440 * @path: an #AdgPath
441 * @primitive: the #CpmlPrimitive to append
443 * Appends @primitive to @path. The primitive to add is considered the
444 * continuation of the current path so the <structfield>org</structfield>
445 * component of @primitive is not used. Anyway the current point is
446 * checked against it: they must be equal or the function will fail
447 * without further processing.
449 * Since: 1.0
451 void
452 adg_path_append_primitive(AdgPath *path, const CpmlPrimitive *primitive)
454 AdgPathPrivate *data;
455 CpmlPrimitive *primitive_dup;
457 g_return_if_fail(ADG_IS_PATH(path));
458 g_return_if_fail(primitive != NULL);
459 g_return_if_fail(primitive->org != NULL);
461 data = path->data;
463 g_return_if_fail(primitive->org->point.x == data->cp.x &&
464 primitive->org->point.y == data->cp.y);
466 /* The primitive data could be modified by pending operations:
467 * work on a copy */
468 primitive_dup = cpml_primitive_deep_dup(primitive);
470 _adg_append_primitive(path, primitive_dup);
472 g_free(primitive_dup);
476 * adg_path_append_segment:
477 * @path: an #AdgPath
478 * @segment: the #CpmlSegment to append
480 * Appends @segment to @path.
482 * Since: 1.0
484 void
485 adg_path_append_segment(AdgPath *path, const CpmlSegment *segment)
487 AdgPathPrivate *data;
489 g_return_if_fail(ADG_IS_PATH(path));
490 g_return_if_fail(segment != NULL);
492 data = path->data;
494 _adg_clear_parent((AdgModel *) path);
495 data->cairo.array = g_array_append_vals(data->cairo.array,
496 segment->data, segment->num_data);
500 * adg_path_append_cairo_path:
501 * @path: an #AdgPath
502 * @cairo_path: (type gpointer): the #cairo_path_t path to append
504 * Appends a whole #cairo_path_t to @path.
506 * Since: 1.0
508 void
509 adg_path_append_cairo_path(AdgPath *path, const cairo_path_t *cairo_path)
511 AdgPathPrivate *data;
513 g_return_if_fail(ADG_IS_PATH(path));
514 g_return_if_fail(cairo_path != NULL);
516 data = path->data;
518 _adg_clear_parent((AdgModel *) path);
519 data->cairo.array = g_array_append_vals(data->cairo.array,
520 cairo_path->data,
521 cairo_path->num_data);
525 * adg_path_move_to:
526 * @path: an #AdgPath
527 * @pair: the destination coordinates
529 * Begins a new segment. After this call the current point will be @pair.
531 * Since: 1.0
533 void
534 adg_path_move_to(AdgPath *path, const CpmlPair *pair)
536 adg_path_append(path, CPML_MOVE, pair);
540 * adg_path_move_to_explicit:
541 * @path: an #AdgPath
542 * @x: the new x coordinate
543 * @y: the new y coordinate
545 * Convenient function to call adg_path_move_to() using explicit
546 * coordinates instead of #CpmlPair.
548 * Since: 1.0
550 void
551 adg_path_move_to_explicit(AdgPath *path, gdouble x, gdouble y)
553 CpmlPair p;
555 p.x = x;
556 p.y = y;
558 adg_path_append(path, CPML_MOVE, &p);
562 * adg_path_line_to:
563 * @path: an #AdgPath
564 * @pair: the destination coordinates
566 * Adds a line to @path from the current point to @pair. After this
567 * call the current point will be @pair.
569 * If @path has no current point before this call, this function will
570 * trigger a warning without other effect.
572 * Since: 1.0
574 void
575 adg_path_line_to(AdgPath *path, const CpmlPair *pair)
577 adg_path_append(path, CPML_LINE, pair);
581 * adg_path_line_to_explicit:
582 * @path: an #AdgPath
583 * @x: the new x coordinate
584 * @y: the new y coordinate
586 * Convenient function to call adg_path_line_to() using explicit
587 * coordinates instead of #CpmlPair.
589 * Since: 1.0
591 void
592 adg_path_line_to_explicit(AdgPath *path, gdouble x, gdouble y)
594 CpmlPair p;
596 p.x = x;
597 p.y = y;
599 adg_path_append(path, CPML_LINE, &p);
603 * adg_path_arc_to:
604 * @path: an #AdgPath
605 * @throught: an arbitrary point on the arc
606 * @pair: the destination coordinates
608 * Adds an arc to the path from the current point to @pair, passing
609 * throught @throught. After this call the current point will be @pair.
611 * If @path has no current point before this call, this function will
612 * trigger a warning without other effect.
614 * Since: 1.0
616 void
617 adg_path_arc_to(AdgPath *path, const CpmlPair *throught, const CpmlPair *pair)
619 adg_path_append(path, CPML_ARC, throught, pair);
623 * adg_path_arc_to_explicit:
624 * @path: an #AdgPath
625 * @x1: the x coordinate of an intermediate point
626 * @y1: the y coordinate of an intermediate point
627 * @x2: the x coordinate of the end of the arc
628 * @y2: the y coordinate of the end of the arc
630 * Convenient function to call adg_path_arc_to() using explicit
631 * coordinates instead of #CpmlPair.
633 * Since: 1.0
635 void
636 adg_path_arc_to_explicit(AdgPath *path, gdouble x1, gdouble y1,
637 gdouble x2, gdouble y2)
639 CpmlPair p[2];
641 p[0].x = x1;
642 p[0].y = y1;
643 p[1].x = x2;
644 p[1].y = y2;
646 adg_path_append(path, CPML_ARC, &p[0], &p[1]);
650 * adg_path_curve_to:
651 * @path: an #AdgPath
652 * @control1: the first control point of the curve
653 * @control2: the second control point of the curve
654 * @pair: the destination coordinates
656 * Adds a cubic Bézier curve to the path from the current point to
657 * position @pair, using @control1 and @control2 as control points.
658 * After this call the current point will be @pair.
660 * If @path has no current point before this call, this function will
661 * trigger a warning without other effect.
663 * Since: 1.0
665 void
666 adg_path_curve_to(AdgPath *path, const CpmlPair *control1,
667 const CpmlPair *control2, const CpmlPair *pair)
669 adg_path_append(path, CPML_CURVE, control1, control2, pair);
673 * adg_path_curve_to_explicit:
674 * @path: an #AdgPath
675 * @x1: the x coordinate of the first control point
676 * @y1: the y coordinate of the first control point
677 * @x2: the x coordinate of the second control point
678 * @y2: the y coordinate of the second control point
679 * @x3: the x coordinate of the end of the curve
680 * @y3: the y coordinate of the end of the curve
682 * Convenient function to call adg_path_curve_to() using explicit
683 * coordinates instead of #CpmlPair.
685 * Since: 1.0
687 void
688 adg_path_curve_to_explicit(AdgPath *path, gdouble x1, gdouble y1,
689 gdouble x2, gdouble y2, gdouble x3, gdouble y3)
691 CpmlPair p[3];
693 p[0].x = x1;
694 p[0].y = y1;
695 p[1].x = x2;
696 p[1].y = y2;
697 p[2].x = x3;
698 p[2].y = y3;
700 adg_path_append(path, CPML_CURVE, &p[0], &p[1], &p[2]);
704 * adg_path_close:
705 * @path: an #AdgPath
707 * Adds a line segment to the path from the current point to the
708 * beginning of the current segment, (the most recent point passed
709 * to an adg_path_move_to()), and closes this segment.
710 * After this call the current point will be unset.
712 * The behavior of adg_path_close() is distinct from simply calling
713 * adg_path_line_to() with the coordinates of the segment starting
714 * point. When a closed segment is stroked, there are no caps on the
715 * ends. Instead, there is a line join connecting the final and
716 * initial primitive of the segment.
718 * If @path has no current point before this call, this function will
719 * trigger a warning without other effect.
721 * Since: 1.0
723 void
724 adg_path_close(AdgPath *path)
726 adg_path_append(path, CPML_CLOSE);
730 * adg_path_arc:
731 * @path: an #AdgPath
732 * @center: coordinates of the center of the arc
733 * @r: the radius of the arc
734 * @start: the start angle, in radians
735 * @end: the end angle, in radians
737 * A more usual way to add an arc to @path. After this call, the current
738 * point will be the computed end point of the arc. The arc will be
739 * rendered in increasing angle, accordling to @start and @end. This means
740 * if @start is less than @end, the arc will be rendered in clockwise
741 * direction (accordling to the default cairo coordinate system) while if
742 * @start is greather than @end, the arc will be rendered in couterclockwise
743 * direction.
745 * By explicitely setting the whole arc data, the start point could be
746 * different from the current point. In this case, if @path has no
747 * current point before the call a %CPML_MOVE to the start point of
748 * the arc will be automatically prepended to the arc. If @path has a
749 * current point, a %CPML_LINE to the start point of the arc will be
750 * used instead of the "move to" primitive.
752 * Since: 1.0
754 void
755 adg_path_arc(AdgPath *path, const CpmlPair *center, gdouble r,
756 gdouble start, gdouble end)
758 AdgPathPrivate *data;
759 CpmlPair p[3];
761 g_return_if_fail(ADG_IS_PATH(path));
762 g_return_if_fail(center != NULL);
764 data = path->data;
765 cpml_vector_from_angle(&p[0], start);
766 cpml_vector_from_angle(&p[1], (end-start) / 2);
767 cpml_vector_from_angle(&p[2], end);
769 cpml_vector_set_length(&p[0], r);
770 cpml_vector_set_length(&p[1], r);
771 cpml_vector_set_length(&p[2], r);
773 p[0].x += center->x;
774 p[0].y += center->y;
775 p[1].x += center->x;
776 p[1].y += center->y;
777 p[2].x += center->x;
778 p[2].y += center->y;
780 if (!data->cp_is_valid)
781 adg_path_append(path, CPML_MOVE, &p[0]);
782 else if (p[0].x != data->cp.x || p[0].y != data->cp.y)
783 adg_path_append(path, CPML_LINE, &p[0]);
785 adg_path_append(path, CPML_ARC, &p[1], &p[2]);
789 * adg_path_arc_explicit:
790 * @path: an #AdgPath
791 * @xc: x position of the center of the arc
792 * @yc: y position of the center of the arc
793 * @r: the radius of the arc
794 * @start: the start angle, in radians
795 * @end: the end angle, in radians
797 * Convenient function to call adg_path_arc() using explicit
798 * coordinates instead of #CpmlPair.
800 * Since: 1.0
802 void
803 adg_path_arc_explicit(AdgPath *path, gdouble xc, gdouble yc, gdouble r,
804 gdouble start, gdouble end)
806 CpmlPair center;
808 center.x = xc;
809 center.y = yc;
811 adg_path_arc(path, &center, r, start, end);
815 * adg_path_chamfer:
816 * @path: an #AdgPath
817 * @delta1: the distance from the intersection point of the current primitive
818 * @delta2: the distance from the intersection point of the next primitive
820 * A binary action that generates a chamfer between two primitives.
821 * The first primitive involved is the current primitive, the second will
822 * be the next primitive appended to @path after this call. The second
823 * primitive is required: if the chamfer operation is not properly
824 * terminated (by not providing the second primitive), any API accessing
825 * the path in reading mode will raise a warning.
827 * An exception is a chamfer after a %CPML_CLOSE primitive. In this case,
828 * the second primitive is not required: the current close path is used
829 * as first operand while the first primitive of the current segment is
830 * used as second operand.
832 * The chamfer operation requires two lengths: @delta1 specifies the
833 * "quantity" to trim on the first primitive while @delta2 is the same
834 * applied on the second primitive. The term "quantity" means the length
835 * of the portion to cut out from the original primitive (that is the
836 * primitive as would be without the chamfer).
838 * Since: 1.0
840 void
841 adg_path_chamfer(AdgPath *path, gdouble delta1, gdouble delta2)
843 g_return_if_fail(ADG_IS_PATH(path));
845 if (!_adg_append_operation(path, ADG_ACTION_CHAMFER, delta1, delta2))
846 return;
850 * adg_path_fillet:
851 * @path: an #AdgPath
852 * @radius: the radius of the fillet
854 * A binary action that joins to primitives with an arc.
855 * The first primitive involved is the current primitive, the second will
856 * be the next primitive appended to @path after this call. The second
857 * primitive is required: if the fillet operation is not properly
858 * terminated (by not providing the second primitive), any API accessing
859 * the path in reading mode will raise a warning.
861 * An exception is a fillet after a %CPML_CLOSE primitive. In this case,
862 * the second primitive is not required: the current close path is used
863 * as first operand while the first primitive of the current segment is
864 * used as second operand.
866 * Since: 1.0
868 void
869 adg_path_fillet(AdgPath *path, gdouble radius)
871 g_return_if_fail(ADG_IS_PATH(path));
873 if (!_adg_append_operation(path, ADG_ACTION_FILLET, radius))
874 return;
878 * adg_path_reflect:
879 * @path: an #AdgPath
880 * @vector: (allow-none): the slope of the axis
882 * Reflects the first segment or @path around the axis passing
883 * throught (0, 0) and with a @vector slope. The internal segment
884 * is duplicated and the proper transformation (computed from
885 * @vector) to mirror the segment is applied on all its points.
886 * The result is then reversed with cpml_segment_reverse() and
887 * appended to the original path with adg_path_append_segment().
889 * For convenience, if @vector is <constant>NULL</constant> the
890 * path is reversed around the x axis <constant>(y = 0)</constant>.
892 * Since: 1.0
894 void
895 adg_path_reflect(AdgPath *path, const CpmlVector *vector)
897 AdgModel *model;
898 cairo_matrix_t matrix;
899 CpmlSegment segment, *dup_segment;
901 g_return_if_fail(ADG_IS_PATH(path));
902 g_return_if_fail(vector == NULL || vector->x != 0 || vector->y != 0);
904 model = (AdgModel *) path;
906 if (vector == NULL) {
907 cairo_matrix_init_scale(&matrix, 1, -1);
908 } else {
909 CpmlVector slope;
910 gdouble cos2angle, sin2angle;
912 cpml_pair_copy(&slope, vector);
913 cpml_vector_set_length(&slope, 1);
915 if (slope.x == 0 && slope.y == 0) {
916 g_warning(_("%s: the axis of the reflection is not known"),
917 G_STRLOC);
918 return;
921 sin2angle = 2. * vector->x * vector->y;
922 cos2angle = 2. * vector->x * vector->x - 1;
924 cairo_matrix_init(&matrix, cos2angle, sin2angle,
925 sin2angle, -cos2angle, 0, 0);
928 if (!adg_trail_put_segment((AdgTrail *) path, 1, &segment))
929 return;
931 /* No need to reverse an empty segment */
932 if (segment.num_data == 0 || segment.num_data == 0)
933 return;
935 dup_segment = cpml_segment_deep_dup(&segment);
936 if (dup_segment == NULL)
937 return;
939 cpml_segment_reverse(dup_segment);
940 cpml_segment_transform(dup_segment, &matrix);
941 dup_segment->data[0].header.type = CPML_LINE;
943 adg_path_append_segment(path, dup_segment);
945 g_free(dup_segment);
947 _adg_dup_reverse_named_pairs(model, &matrix);
951 * adg_path_reflect_explicit:
952 * @path: an #AdgPath
953 * @x: the vector x component
954 * @y: the vector y component
956 * Convenient function to call adg_path_reflect() using explicit
957 * vector components instead of #CpmlVector.
959 * Since: 1.0
961 void
962 adg_path_reflect_explicit(AdgPath *path, gdouble x, gdouble y)
964 CpmlVector vector;
966 vector.x = x;
967 vector.y = y;
969 adg_path_reflect(path, &vector);
973 static void
974 _adg_clear(AdgModel *model)
976 AdgPath *path;
977 AdgPathPrivate *data;
979 path = (AdgPath *) model;
980 data = path->data;
982 g_array_set_size(data->cairo.array, 0);
983 _adg_clear_operation(path);
984 _adg_clear_parent(model);
987 static void
988 _adg_clear_parent(AdgModel *model)
990 if (_ADG_OLD_MODEL_CLASS->clear)
991 _ADG_OLD_MODEL_CLASS->clear(model);
994 static void
995 _adg_changed(AdgModel *model)
997 _adg_clear_parent(model);
999 if (_ADG_OLD_MODEL_CLASS->changed)
1000 _ADG_OLD_MODEL_CLASS->changed(model);
1003 static cairo_path_t *
1004 _adg_get_cairo_path(AdgTrail *trail)
1006 _adg_clear_parent((AdgModel *) trail);
1007 return _adg_read_cairo_path((AdgPath *) trail);
1010 static cairo_path_t *
1011 _adg_read_cairo_path(AdgPath *path)
1013 AdgPathPrivate *data = path->data;
1014 cairo_path_t *cairo_path = &data->cairo.path;
1015 GArray *array = data->cairo.array;
1017 /* Always regenerate the cairo_path_t as it is a trivial operation */
1018 cairo_path->status = CAIRO_STATUS_SUCCESS;
1019 cairo_path->data = (cairo_path_data_t *) array->data;
1020 cairo_path->num_data = array->len;
1022 return cairo_path;
1025 static gint
1026 _adg_primitive_length(CpmlPrimitiveType type)
1028 if (type == CPML_CLOSE)
1029 return 1;
1030 else if (type == CPML_MOVE)
1031 return 2;
1033 return cpml_primitive_type_get_n_points(type);
1036 static void
1037 _adg_primitive_remap(CpmlPrimitive *primitive, gpointer to,
1038 const CpmlPrimitive *old, gconstpointer from)
1040 primitive->org = REMAPPED(old->org, from, to);
1041 primitive->segment = REMAPPED(old->segment, from, to);
1042 primitive->data = REMAPPED(old->data, from, to);
1045 static void
1046 _adg_append_primitive(AdgPath *path, CpmlPrimitive *current)
1048 AdgPathPrivate *data;
1049 cairo_path_data_t *path_data;
1050 int length;
1051 gconstpointer old_data;
1053 data = path->data;
1054 path_data = current->data;
1055 length = path_data[0].header.length;
1057 /* Execute any pending operation */
1058 _adg_do_operation(path, path_data);
1060 /* Append the path data to the internal path array */
1061 old_data = (data->cairo.array)->data;
1062 data->cairo.array = g_array_append_vals(data->cairo.array,
1063 path_data, length);
1065 /* Set path data to point to the recently appended cairo_path_data_t
1066 * primitive: the first struct is the header */
1067 path_data = (cairo_path_data_t *) (data->cairo.array)->data +
1068 (data->cairo.array)->len - length;
1070 /* Store the last primitive into over */
1071 _adg_primitive_remap(&data->over, (data->cairo.array)->data,
1072 &data->last, old_data);
1074 /* Set the last primitive for subsequent binary operations */
1075 data->last.org = data->cp_is_valid ? path_data - 1 : NULL;
1076 data->last.segment = NULL;
1077 data->last.data = path_data;
1079 /* Save the last point as the current point, if applicable */
1080 data->cp_is_valid = length > 1;
1081 if (length > 1)
1082 cpml_pair_from_cairo(&data->cp, &path_data[length-1]);
1084 /* Invalidate cairo_path: should be recomputed */
1085 _adg_clear_parent((AdgModel *) path);
1088 static void
1089 _adg_clear_operation(AdgPath *path)
1091 AdgPathPrivate *data;
1092 AdgOperation *operation;
1094 data = path->data;
1095 operation = &data->operation;
1097 if (operation->action != ADG_ACTION_NONE) {
1098 g_warning(_("%s: a '%s' operation is still active while clearing the path"),
1099 G_STRLOC, _adg_action_name(operation->action));
1100 operation->action = ADG_ACTION_NONE;
1103 data->cp_is_valid = FALSE;
1104 data->last.data = NULL;
1105 data->over.data = NULL;
1108 static gboolean
1109 _adg_append_operation(AdgPath *path, AdgAction action, ...)
1111 AdgPathPrivate *data;
1112 AdgOperation *operation;
1113 va_list var_args;
1115 data = path->data;
1117 if (data->last.data == NULL) {
1118 g_warning(_("%s: requested a '%s' operation on a path without current primitive"),
1119 G_STRLOC, _adg_action_name(action));
1120 return FALSE;
1123 operation = &data->operation;
1124 if (operation->action != ADG_ACTION_NONE) {
1125 g_warning(_("%s: requested a '%s' operation while a '%s' operation was active"),
1126 G_STRLOC, _adg_action_name(action),
1127 _adg_action_name(operation->action));
1128 /* XXX: http://dev.entidi.com/p/adg/issues/50/ */
1129 return FALSE;
1132 va_start(var_args, action);
1134 switch (action) {
1136 case ADG_ACTION_CHAMFER:
1137 operation->data.chamfer.delta1 = va_arg(var_args, double);
1138 operation->data.chamfer.delta2 = va_arg(var_args, double);
1139 break;
1141 case ADG_ACTION_FILLET:
1142 operation->data.fillet.radius = va_arg(var_args, double);
1143 break;
1145 case ADG_ACTION_NONE:
1146 va_end(var_args);
1147 return TRUE;
1149 default:
1150 g_warning(_("%s: %d path operation not recognized"), G_STRLOC, action);
1151 va_end(var_args);
1152 return FALSE;
1155 operation->action = action;
1156 va_end(var_args);
1158 if (data->last.data[0].header.type == CPML_CLOSE) {
1159 /* Special case: an action with the close primitive should
1160 * be resolved now by changing the close primitive to a
1161 * line-to and using it as second operand and use the first
1162 * primitive of the current segment as first operand */
1163 guint length;
1164 cairo_path_data_t *path_data;
1165 CpmlSegment segment;
1166 CpmlPrimitive current;
1168 length = data->cairo.array->len;
1170 /* Ensure the close path primitive is not the only data */
1171 g_return_val_if_fail(length > 1, FALSE);
1173 /* Allocate one more item once for all to accept the
1174 * conversion from a close to line-to primitive */
1175 data->cairo.array = g_array_set_size(data->cairo.array, length + 1);
1176 path_data = (cairo_path_data_t *) data->cairo.array->data;
1177 --data->cairo.array->len;
1179 /* Set segment and current (the first primitive of segment) */
1180 cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path));
1181 while (cpml_segment_next(&segment))
1183 cpml_primitive_from_segment(&current, &segment);
1185 /* Convert close path to a line-to primitive */
1186 ++data->cairo.array->len;
1187 path_data[length - 1].header.type = CPML_LINE;
1188 path_data[length - 1].header.length = 2;
1189 path_data[length] = *current.org;
1191 data->last.segment = &segment;
1192 data->last.org = &path_data[length - 2];
1193 data->last.data = &path_data[length - 1];
1195 _adg_do_action(path, action, &current);
1198 return TRUE;
1201 static void
1202 _adg_do_operation(AdgPath *path, cairo_path_data_t *path_data)
1204 AdgPathPrivate *data;
1205 AdgAction action;
1206 CpmlSegment segment;
1207 CpmlPrimitive current;
1208 cairo_path_data_t current_org;
1210 data = path->data;
1211 action = data->operation.action;
1212 cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path));
1214 /* Construct the current primitive, that is the primitive to be
1215 * mixed with the last primitive with the specified operation.
1216 * Its org is a copy of the end point of the last primitive: it can be
1217 * modified without affecting anything else. It is expected the operation
1218 * functions will add to @path the primitives required but NOT to add
1219 * @current, as this one will be inserted automatically. */
1220 current.segment = &segment;
1221 current.org = &current_org;
1222 current.data = path_data;
1223 cpml_pair_to_cairo(&data->cp, &current_org);
1225 _adg_do_action(path, action, &current);
1228 static void
1229 _adg_do_action(AdgPath *path, AdgAction action, CpmlPrimitive *primitive)
1231 switch (action) {
1232 case ADG_ACTION_NONE:
1233 return;
1234 case ADG_ACTION_CHAMFER:
1235 _adg_do_chamfer(path, primitive);
1236 break;
1237 case ADG_ACTION_FILLET:
1238 _adg_do_fillet(path, primitive);
1239 break;
1240 default:
1241 g_return_if_reached();
1245 static void
1246 _adg_do_chamfer(AdgPath *path, CpmlPrimitive *current)
1248 AdgPathPrivate *data;
1249 CpmlPrimitive *last;
1250 gdouble delta1, delta2;
1251 gdouble len1, len2;
1252 CpmlPair pair;
1254 data = path->data;
1255 last = &data->last;
1256 delta1 = data->operation.data.chamfer.delta1;
1257 len1 = cpml_primitive_get_length(last);
1259 if (delta1 >= len1) {
1260 g_warning(_("%s: first chamfer delta of %lf is greather than the available %lf length"),
1261 G_STRLOC, delta1, len1);
1262 return;
1265 delta2 = data->operation.data.chamfer.delta2;
1266 len2 = cpml_primitive_get_length(current);
1268 if (delta2 >= len2) {
1269 g_warning(_("%s: second chamfer delta of %lf is greather than the available %lf length"),
1270 G_STRLOC, delta1, len1);
1271 return;
1274 /* Change the end point of the last primitive */
1275 cpml_primitive_put_pair_at(last, 1. - delta1 / len1, &pair);
1276 cpml_primitive_set_point(last, -1, &pair);
1278 /* Change the start point of the current primitive */
1279 cpml_primitive_put_pair_at(current, delta2 / len2, &pair);
1280 cpml_primitive_set_point(current, 0, &pair);
1282 /* Add the chamfer line */
1283 data->operation.action = ADG_ACTION_NONE;
1284 adg_path_append(path, CPML_LINE, &pair);
1287 static void
1288 _adg_do_fillet(AdgPath *path, CpmlPrimitive *current)
1290 AdgPathPrivate *data;
1291 CpmlPrimitive *last, *current_dup, *last_dup;
1292 gdouble radius, offset, pos;
1293 CpmlPair center, vector, p[3];
1295 data = path->data;
1296 last = &data->last;
1297 current_dup = cpml_primitive_deep_dup(current);
1298 last_dup = cpml_primitive_deep_dup(last);
1299 radius = data->operation.data.fillet.radius;
1300 offset = _adg_is_convex(last_dup, current_dup) ? -radius : radius;
1302 /* Find the center of the fillet from the intersection between
1303 * the last and current primitives offseted by radius */
1304 cpml_primitive_offset(current_dup, offset);
1305 cpml_primitive_offset(last_dup, offset);
1306 if (cpml_primitive_put_intersections(current_dup, last_dup, 1, &center) == 0) {
1307 g_warning(_("%s: fillet with radius of %lf is not applicable here"),
1308 G_STRLOC, radius);
1309 g_free(current_dup);
1310 g_free(last_dup);
1311 return;
1314 /* Compute the start point of the fillet */
1315 pos = cpml_primitive_get_closest_pos(last_dup, &center);
1316 cpml_primitive_put_vector_at(last_dup, pos, &vector);
1317 cpml_vector_set_length(&vector, offset);
1318 cpml_vector_normal(&vector);
1319 p[0].x = center.x - vector.x;
1320 p[0].y = center.y - vector.y;
1322 /* Compute the mid point of the fillet */
1323 cpml_pair_from_cairo(&vector, current->org);
1324 vector.x -= center.x;
1325 vector.y -= center.y;
1326 cpml_vector_set_length(&vector, radius);
1327 p[1].x = center.x + vector.x;
1328 p[1].y = center.y + vector.y;
1330 /* Compute the end point of the fillet */
1331 pos = cpml_primitive_get_closest_pos(current_dup, &center);
1332 cpml_primitive_put_vector_at(current_dup, pos, &vector);
1333 cpml_vector_set_length(&vector, offset);
1334 cpml_vector_normal(&vector);
1335 p[2].x = center.x - vector.x;
1336 p[2].y = center.y - vector.y;
1338 g_free(current_dup);
1339 g_free(last_dup);
1341 /* Change the end point of the last primitive */
1342 cpml_primitive_set_point(last, -1, &p[0]);
1344 /* Change the start point of the current primitive */
1345 cpml_primitive_set_point(current, 0, &p[2]);
1347 /* Add the fillet arc */
1348 data->operation.action = ADG_ACTION_NONE;
1349 adg_path_append(path, CPML_ARC, &p[1], &p[2]);
1352 static gboolean
1353 _adg_is_convex(const CpmlPrimitive *primitive1, const CpmlPrimitive *primitive2)
1355 CpmlVector v1, v2;
1356 gdouble angle1, angle2;
1358 cpml_primitive_put_vector_at(primitive1, -1, &v1);
1359 cpml_primitive_put_vector_at(primitive2, 0, &v2);
1361 /* Probably there is a smarter way to get this without trygonometry */
1362 angle1 = cpml_vector_angle(&v1);
1363 angle2 = cpml_vector_angle(&v2);
1365 if (angle1 > angle2)
1366 angle1 -= G_PI*2;
1368 return angle2-angle1 > G_PI;
1371 static const gchar *
1372 _adg_action_name(AdgAction action)
1374 switch (action) {
1375 case ADG_ACTION_NONE:
1376 return "NULL";
1377 case ADG_ACTION_CHAMFER:
1378 return "CHAMFER";
1379 case ADG_ACTION_FILLET:
1380 return "FILLET";
1383 return "undefined";
1386 static void
1387 _adg_get_named_pair(AdgModel *model, const gchar *name,
1388 CpmlPair *pair, gpointer user_data)
1390 GSList **named_pairs;
1391 AdgNamedPair *named_pair;
1393 named_pairs = user_data;
1395 named_pair = g_new(AdgNamedPair, 1);
1396 named_pair->name = name;
1397 named_pair->pair = *pair;
1399 *named_pairs = g_slist_prepend(*named_pairs, named_pair);
1402 static void
1403 _adg_dup_reverse_named_pairs(AdgModel *model, const cairo_matrix_t *matrix)
1405 AdgNamedPair *old_named_pair;
1406 AdgNamedPair named_pair;
1407 GSList *named_pairs;
1409 /* Populate named_pairs with all the named pairs of model */
1410 named_pairs = NULL;
1411 adg_model_foreach_named_pair(model, _adg_get_named_pair, &named_pairs);
1413 /* Readd the pairs applying the reversing transformation matrix to
1414 * their coordinates and prepending a "-" to their name */
1415 while (named_pairs) {
1416 old_named_pair = named_pairs->data;
1418 named_pair.name = g_strdup_printf("-%s", old_named_pair->name);
1419 named_pair.pair = old_named_pair->pair;
1420 cpml_pair_transform(&named_pair.pair, matrix);
1422 adg_model_set_named_pair(model, named_pair.name, &named_pair.pair);
1424 g_free((gpointer) named_pair.name);
1425 named_pairs = g_slist_delete_link(named_pairs, named_pairs);