[AdgPath] Removed PARENT_CLASS define
[adg.git] / adg / adg-path.c
blob453d8a95ece25ae6f1d0bd21d725182cebd84b8b
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.
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:path
23 * @title: AdgPath
24 * @short_description: The basic model representing a generic path
26 * The #AdgPath model is a virtual path: in a few words, it is a
27 * simple conceptual #cairo_path_t struct. This class implements
28 * methods to manipulate the underlying cairo path.
30 * Although some of the provided methods are clearly based on the
31 * original cairo path manipulation API, their behavior could be
32 * sligthly different. This is intentional, because the ADG provides
33 * additional path manipulation algorithms, sometime quite complex,
34 * and a more restrictive filter on the path quality is required.
35 * Also, the ADG is designed to be used by technicians while cairo
36 * targets a broader range of developers.
38 * As an example, following the rule of the less surprise, some
39 * cairo functions guess the current point when it is not defined,
40 * while the #AdgPath methods trigger a warning without other effect.
41 * Furthermore, after a cairo_path_close_path() call a MOVE_TO
42 * primitive to the starting point of the segment is automatically
43 * added by cairo while in the ADG, after an adg_path_close(), the
44 * current point is simply unset.
45 **/
47 #include "adg-path.h"
48 #include "adg-path-private.h"
49 #include "adg-intl.h"
51 #include <math.h>
54 static void finalize (GObject *object);
55 static void changed (AdgModel *model);
56 static void clear_cairo_path (AdgPath *path);
57 static cairo_path_t * get_cairo_path (AdgPath *path);
58 static cairo_path_t * get_cpml_path (AdgPath *path);
59 static GArray * arc_to_curves (GArray *array,
60 const cairo_path_data_t *src);
61 static void append_valist (AdgPath *path,
62 cairo_path_data_type_t type,
63 int length,
64 va_list var_args);
67 G_DEFINE_TYPE(AdgPath, adg_path, ADG_TYPE_MODEL);
70 static void
71 adg_path_class_init(AdgPathClass *klass)
73 GObjectClass *gobject_class;
74 AdgModelClass *model_class;
76 gobject_class = (GObjectClass *) klass;
77 model_class = (AdgModelClass *) klass;
79 g_type_class_add_private(klass, sizeof(AdgPathPrivate));
81 gobject_class->finalize = finalize;
83 model_class->changed = changed;
86 static void
87 adg_path_init(AdgPath *path)
89 AdgPathPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(path, ADG_TYPE_PATH,
90 AdgPathPrivate);
92 priv->cp_is_valid = FALSE;
93 priv->path = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
94 priv->cairo_path.status = CAIRO_STATUS_INVALID_PATH_DATA;
95 priv->cairo_path.data = NULL;
96 priv->cairo_path.num_data = 0;
98 path->priv = priv;
101 static void
102 finalize(GObject *object)
104 AdgPath *path;
105 GObjectClass *object_class;
107 path = (AdgPath *) object;
108 object_class = (GObjectClass *) adg_path_parent_class;
110 g_array_free(path->priv->path, TRUE);
111 clear_cairo_path(path);
113 if (object_class->finalize != NULL)
114 object_class->finalize(object);
119 * adg_path_new:
121 * Creates a new path model. The path must be constructed in the @callback
122 * function: AdgPath will cache and reuse the cairo_copy_path() returned by
123 * the cairo context after the @callback call.
125 * Return value: the new model
127 AdgModel *
128 adg_path_new(void)
130 return (AdgModel *) g_object_new(ADG_TYPE_PATH, NULL);
135 * adg_path_get_cairo_path:
136 * @path: an #AdgPath
138 * Gets a pointer to the cairo path structure of @path. The return value
139 * is owned by @path and must be considered read-only.
141 * This function also converts %CAIRO_PATH_ARC_TO primitives, not
142 * recognized by cairo, into approximated Bézier curves. The conversion
143 * is cached so any furter request is O(1). This cache is cleared
144 * whenever @path is modified (by adding a new primitive or by calling
145 * adg_path_clear()).
147 * <important>
148 * <title>TODO</title>
149 * <itemizedlist>
150 * <listitem>Actually, the arcs are approximated to Bézier using the
151 * hardcoded max angle of PI/2. This should be customizable
152 * by adding, for instance, a property to the #AdgPath class
153 * with a default value of PI/2.</listitem>
154 * </itemizedlist>
155 * </important>
157 * Return value: a pointer to the internal cairo path or %NULL on errors
159 const cairo_path_t *
160 adg_path_get_cairo_path(AdgPath *path)
162 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
164 return get_cairo_path(path);
168 * adg_path_get_cpml_path:
169 * @path: an #AdgPath
171 * Gets a pointer to the cairo path structure of @path. The return
172 * value is owned by @path and must not be freed.
174 * This function is similar to adg_path_get_cairo_path() but with
175 * two important differences: firstly the arc primitives are not
176 * expanded to Bézier curves and secondly the returned path is
177 * not read-only. This means it is allowed to modify the returned
178 * path as long as its size is retained and its data contains a
179 * valid path.
181 * Keep in mind any changes to @path makes the value returned by
182 * this function useless, as it is likely to contain plain garbage.
184 * Return value: a pointer to the internal cpml path or %NULL on errors
186 cairo_path_t *
187 adg_path_get_cpml_path(AdgPath *path)
189 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
191 clear_cairo_path(path);
193 return get_cpml_path(path);
197 * adg_path_dup_cpml_path:
198 * @path: an #AdgPath
200 * Gets a duplicate of the cairo path structure of @path. The return
201 * value should be freed with g_free(). The returned #cairo_path_t
202 * struct also contains the #cairo_path_data_t array in the same
203 * chunk of memory, so a g_free() to the returned pointer also frees
204 * the data array.
206 * Although quite similar to adg_path_get_cpml_path(), the use case
207 * of this method is different: being a duplicate, modifying the
208 * returned path does not modify @path itsself.
210 * Return value: a newly allocated #cairo_path_t pointer to be freed
211 * with g_free() or %NULL on errors
213 cairo_path_t *
214 adg_path_dup_cpml_path(AdgPath *path)
216 cairo_path_t *cpml_path;
217 gsize head_size, data_size;
219 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
221 head_size = sizeof(cairo_path_t);
222 data_size = sizeof(cairo_path_data_t) * path->priv->path->len;
224 /* Both the cairo_path_t struct and the cairo_path_data_t array
225 * are stored in the same chunk of memory, so only a single
226 * g_free() is needed */
227 cpml_path = g_malloc(head_size + data_size);
228 cpml_path->status = CAIRO_STATUS_SUCCESS;
229 cpml_path->data = (cairo_path_data_t *) ((gchar *) cpml_path + head_size);
230 cpml_path->num_data = path->priv->path->len;
231 memcpy(cpml_path->data, path->priv->path->data, data_size);
233 return cpml_path;
237 * adg_path_get_current_point:
238 * @path: an #AdgPath
239 * @x: return value for x coordinate of the current point
240 * @y: return value for y coordinate of the current point
242 * Gets the current point of @path, which is conceptually the
243 * final point reached by the path so far.
245 * If there is no defined current point, @x and @y will both be set
246 * to 0 and a warning will be triggered. It is possible to check this
247 * in advance with adg_path_has_current_point().
249 * Most #AdgPath methods alter the current point and most of them
250 * expect a current point to be defined otherwise will fail triggering
251 * a warning. Check the description of every method for specific details.
253 void
254 adg_path_get_current_point(AdgPath *path, gdouble *x, gdouble *y)
256 g_return_if_fail(ADG_IS_PATH(path));
258 if (path->priv->cp_is_valid) {
259 *x = path->priv->cp.x;
260 *y = path->priv->cp.y;
261 } else {
262 *x = *y = 0.;
263 g_return_if_reached();
268 * adg_path_has_current_point:
269 * @path: an #AdgPath
271 * Returns whether a current point is defined on @path.
272 * See adg_path_get_current_point() for details on the current point.
274 * Return value: whether a current point is defined
276 gboolean
277 adg_path_has_current_point(AdgPath *path)
279 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
281 return path->priv->cp_is_valid;
285 * adg_path_clear:
286 * @path: an #AdgPath
288 * Releases the internal memory hold by @path and resets its status,
289 * so that after this call @path contains an empty path.
291 void
292 adg_path_clear(AdgPath *path)
294 g_return_if_fail(ADG_IS_PATH(path));
296 g_array_set_size(path->priv->path, 0);
297 clear_cairo_path(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, cairo_path_data_type_t 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, cairo_path_data_type_t type,
337 va_list var_args)
339 gint length;
341 g_return_if_fail(ADG_IS_PATH(path));
343 switch (type) {
345 case CAIRO_PATH_CLOSE_PATH:
346 g_return_if_fail(path->priv->cp_is_valid);
347 length = 1;
348 break;
350 case CAIRO_PATH_MOVE_TO:
351 length = 2;
352 break;
354 case CAIRO_PATH_LINE_TO:
355 g_return_if_fail(path->priv->cp_is_valid);
356 length = 2;
357 break;
359 case CAIRO_PATH_ARC_TO:
360 g_return_if_fail(path->priv->cp_is_valid);
361 length = 3;
362 break;
364 case CAIRO_PATH_CURVE_TO:
365 g_return_if_fail(path->priv->cp_is_valid);
366 length = 4;
367 break;
369 default:
370 g_assert_not_reached();
371 return;
374 append_valist(path, type, length, var_args);
378 * adg_path_append_cairo_path:
379 * @path: an #AdgPath
380 * @cairo_path: the #cairo_path_t path to append
382 * Appends a whole cairo path to @path.
384 void
385 adg_path_append_cairo_path(AdgPath *path, const cairo_path_t *cairo_path)
387 g_return_if_fail(ADG_IS_PATH(path));
389 clear_cairo_path(path);
390 path->priv->path = g_array_append_vals(path->priv->path,
391 cairo_path->data,
392 cairo_path->num_data);
397 * adg_path_move_to:
398 * @path: an #AdgPath
399 * @x: the new x coordinate
400 * @y: the new y coordinate
402 * Begins a new segment. After this call the current point will be (@x, @y).
404 void
405 adg_path_move_to(AdgPath *path, gdouble x, gdouble y)
407 AdgPair p;
409 p.x = x;
410 p.y = y;
412 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p);
416 * adg_path_line_to:
417 * @path: an #AdgPath
418 * @x: the new x coordinate
419 * @y: the new y coordinate
421 * Adds a line to @path from the current point to position (@x, @y).
422 * After this call the current point will be (@x, @y).
424 * If @path has no current point before this call, this function will
425 * trigger a warning without other effect.
427 void
428 adg_path_line_to(AdgPath *path, gdouble x, gdouble y)
430 AdgPair p;
432 p.x = x;
433 p.y = y;
435 adg_path_append(path, CAIRO_PATH_LINE_TO, &p);
439 * adg_path_arc_to:
440 * @path: an #AdgPath
441 * @x1: the x coordinate of an intermediate point
442 * @y1: the y coordinate of an intermediate point
443 * @x2: the x coordinate of the end of the arc
444 * @y2: the y coordinate of the end of the arc
446 * Adds an arc to the path from the current point to (@x2, @y2),
447 * passing throught (@x1, @y1). After this call the current point
448 * will be (@x2, @y2).
450 * If @path has no current point before this call, this function will
451 * trigger a warning without other effect.
453 void
454 adg_path_arc_to(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
456 AdgPair p[2];
458 p[0].x = x1;
459 p[0].y = y1;
460 p[1].x = x2;
461 p[1].y = y2;
463 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[0], &p[1]);
467 * adg_path_curve_to:
468 * @path: an #AdgPath
469 * @x1: the x coordinate of the first control point
470 * @y1: the y coordinate of the first control point
471 * @x2: the x coordinate of the second control point
472 * @y2: the y coordinate of the second control point
473 * @x3: the x coordinate of the end of the curve
474 * @y3: the y coordinate of the end of the curve
476 * Adds a cubic Bézier curve to the path from the current point to
477 * position (@x3, @y3), using (@x1, @y1) and (@x2, @y2) as the
478 * control points. After this call the current point will be (@x3, @y3).
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_curve_to(AdgPath *path, gdouble x1, gdouble y1,
485 gdouble x2, gdouble y2, gdouble x3, gdouble y3)
487 AdgPair p[3];
489 p[0].x = x1;
490 p[0].y = y1;
491 p[1].x = x2;
492 p[1].y = y2;
493 p[2].x = x3;
494 p[2].y = y3;
496 adg_path_append(path, CAIRO_PATH_CURVE_TO, &p[0], &p[1], &p[2]);
500 * adg_path_close:
501 * @path: an #AdgPath
503 * Adds a line segment to the path from the current point to the
504 * beginning of the current segment, (the most recent point passed
505 * to an adg_path_move_to()), and closes this segment.
506 * After this call the current point will be unset.
508 * The behavior of adg_path_close() is distinct from simply calling
509 * adg_line_to() with the coordinates of the segment starting point.
510 * When a closed segment is stroked, there are no caps on the ends.
511 * Instead, there is a line join connecting the final and initial
512 * primitive of the segment.
514 * If @path has no current point before this call, this function will
515 * trigger a warning without other effect.
517 void
518 adg_path_close(AdgPath *path)
520 adg_path_append(path, CAIRO_PATH_CLOSE_PATH);
524 * adg_path_arc
525 * @path: an #AdgPath
526 * @xc: x position of the center of the arc
527 * @yc: y position of the center of the arc
528 * @r: the radius of the arc
529 * @start: the start angle, in radians
530 * @end: the end angle, in radians
532 * A more usual way to add an arc to @path. After this call, the current
533 * point will be the computed end point of the arc. The arc will be
534 * rendered in increasing angle, accordling to @start and @end. This means
535 * if @start is less than @end, the arc will be rendered in clockwise
536 * direction (accordling to the default cairo coordinate system) while if
537 * @start is greather than @end, the arc will be rendered in couterclockwise
538 * direction.
540 * By explicitely setting the whole arc data, the start point could be
541 * different from the current point. In this case, if @path has no
542 * current point before the call a %CAIRO_PATH_MOVE_TO to the start
543 * point of the arc will be automatically prepended to the arc.
544 * If @path has a current point, a %CAIRO_PATH_LINE_TO to the start
545 * point of the arc will be used instead of the moveto.
547 void
548 adg_path_arc(AdgPath *path, gdouble xc, gdouble yc, gdouble r,
549 gdouble start, gdouble end)
551 AdgPair center, p[3];
553 g_return_if_fail(ADG_IS_PATH(path));
555 center.x = xc;
556 center.y = yc;
558 cpml_vector_from_angle(&p[0], start, r);
559 cpml_vector_from_angle(&p[1], (end-start) / 2, r);
560 cpml_vector_from_angle(&p[2], end, r);
562 cpml_pair_add(&p[0], &center);
563 cpml_pair_add(&p[1], &center);
564 cpml_pair_add(&p[2], &center);
566 if (!path->priv->cp_is_valid)
567 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p[0]);
568 else if (p[0].x != path->priv->cp.x || p[0].y != path->priv->cp.y)
569 adg_path_append(path, CAIRO_PATH_LINE_TO, &p[0]);
571 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[1], &p[2]);
576 * adg_path_dump:
577 * @path: an #AdgPath
579 * Dumps the data content of @path to stdout in a human readable format.
581 void
582 adg_path_dump(AdgPath *path)
584 CpmlSegment segment;
585 cairo_path_t *cairo_path;
587 g_return_if_fail(ADG_IS_PATH(path));
589 cairo_path = get_cairo_path(path);
591 g_return_if_fail(cairo_path != NULL);
593 if (!cpml_segment_from_cairo(&segment, cairo_path)) {
594 g_print("Invalid path data to dump!\n");
595 } else {
596 do {
597 cpml_segment_dump(&segment);
598 } while (cpml_segment_next(&segment));
603 static void
604 changed(AdgModel *model)
606 AdgModelClass *model_class = (AdgModelClass *) adg_path_parent_class;
608 adg_path_clear((AdgPath *) model);
610 if (model_class->changed != NULL)
611 model_class->changed(model);
614 static void
615 clear_cairo_path(AdgPath *path)
617 cairo_path_t *cairo_path = &path->priv->cairo_path;
619 if (cairo_path->data == NULL)
620 return;
622 g_free(cairo_path->data);
624 cairo_path->status = CAIRO_STATUS_INVALID_PATH_DATA;
625 cairo_path->data = NULL;
626 cairo_path->num_data = 0;
629 static cairo_path_t *
630 get_cairo_path(AdgPath *path)
632 cairo_path_t *cairo_path;
633 const GArray *src;
634 GArray *dst;
635 const cairo_path_data_t *p_src;
636 int i;
638 /* Check for cached result */
639 cairo_path = &path->priv->cairo_path;
640 if (cairo_path->data != NULL)
641 return cairo_path;
643 src = path->priv->path;
644 dst = g_array_sized_new(FALSE, FALSE, sizeof(cairo_path_data_t), src->len);
646 /* Cycle the path and convert arcs to Bézier curves */
647 for (i = 0; i < src->len; i += p_src->header.length) {
648 p_src = (const cairo_path_data_t *) src->data + i;
650 if (p_src->header.type == CAIRO_PATH_ARC_TO)
651 dst = arc_to_curves(dst, p_src);
652 else
653 dst = g_array_append_vals(dst, p_src, p_src->header.length);
656 cairo_path->status = CAIRO_STATUS_SUCCESS;
657 cairo_path->num_data = dst->len;
658 cairo_path->data = (cairo_path_data_t *) g_array_free(dst, FALSE);
660 return cairo_path;
663 static cairo_path_t *
664 get_cpml_path(AdgPath *path)
666 cairo_path_t *cpml_path = &path->priv->cpml_path;
668 cpml_path->status = CAIRO_STATUS_SUCCESS;
669 cpml_path->data = (cairo_path_data_t *) path->priv->path->data;
670 cpml_path->num_data = path->priv->path->len;
672 return cpml_path;
675 static GArray *
676 arc_to_curves(GArray *array, const cairo_path_data_t *src)
678 CpmlPrimitive arc;
679 double start, end;
681 /* Build the arc primitive: the arc origin is supposed to be the previous
682 * point (src-1): this means a primitive must exist before the arc */
683 arc.segment = NULL;
684 arc.org = (cairo_path_data_t *) (src-1);
685 arc.data = (cairo_path_data_t *) src;
687 if (cpml_arc_info(&arc, NULL, NULL, &start, &end)) {
688 CpmlSegment segment;
689 int n_curves;
690 cairo_path_data_t *curves;
692 n_curves = ceil(fabs(end-start) / M_PI_2);
693 curves = g_new(cairo_path_data_t, n_curves * 4);
694 segment.data = curves;
695 cpml_arc_to_curves(&arc, &segment, n_curves);
697 array = g_array_append_vals(array, curves, n_curves * 4);
699 g_free(curves);
702 return array;
705 static void
706 append_valist(AdgPath *path, cairo_path_data_type_t type,
707 int length, va_list var_args)
709 AdgPathPrivate *priv;
710 cairo_path_data_t item;
712 priv = path->priv;
714 /* Append the header item */
715 item.header.type = type;
716 item.header.length = length;
717 priv->path = g_array_append_val(priv->path, item);
718 priv->cp_is_valid = FALSE;
720 /* Append the data items (that is, the AdgPair points) */
721 while (--length) {
722 cpml_pair_to_cairo(va_arg(var_args, AdgPair *), &item);
723 priv->path = g_array_append_val(priv->path, item);
724 priv->cp_is_valid = TRUE;
727 /* Save the last point as the current point */
728 if (priv->cp_is_valid)
729 cpml_pair_from_cairo(&priv->cp, &item);
731 /* Invalidate cairo_path: should be recomputed */
732 clear_cairo_path(path);