[AdgPath] Added adg_path_dup_cpml_path()
[adg.git] / adg / adg-path.c
blob49c4d22b766e7a68338d8ec22b1e80efd609797f
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>
53 #define PARENT_CLASS ((AdgModelClass *) adg_path_parent_class)
56 static void finalize (GObject *object);
57 static void changed (AdgModel *model);
58 static void clear_cairo_path (AdgPath *path);
59 static cairo_path_t * get_cairo_path (AdgPath *path);
60 static cairo_path_t * get_cpml_path (AdgPath *path);
61 static GArray * arc_to_curves (GArray *array,
62 const cairo_path_data_t *src);
63 static void append_valist (AdgPath *path,
64 cairo_path_data_type_t type,
65 int length,
66 va_list var_args);
69 G_DEFINE_TYPE(AdgPath, adg_path, ADG_TYPE_MODEL);
72 static void
73 adg_path_class_init(AdgPathClass *klass)
75 GObjectClass *gobject_class;
76 AdgModelClass *model_class;
78 gobject_class = (GObjectClass *) klass;
79 model_class = (AdgModelClass *) klass;
81 g_type_class_add_private(klass, sizeof(AdgPathPrivate));
83 gobject_class->finalize = finalize;
85 model_class->changed = changed;
88 static void
89 adg_path_init(AdgPath *path)
91 AdgPathPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(path, ADG_TYPE_PATH,
92 AdgPathPrivate);
94 priv->cp_is_valid = FALSE;
95 priv->path = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
96 priv->cairo_path.status = CAIRO_STATUS_INVALID_PATH_DATA;
97 priv->cairo_path.data = NULL;
98 priv->cairo_path.num_data = 0;
100 path->priv = priv;
103 static void
104 finalize(GObject *object)
106 AdgPath *path = (AdgPath *) object;
108 g_array_free(path->priv->path, TRUE);
109 clear_cairo_path(path);
111 ((GObjectClass *) PARENT_CLASS)->finalize(object);
116 * adg_path_new:
118 * Creates a new path model. The path must be constructed in the @callback
119 * function: AdgPath will cache and reuse the cairo_copy_path() returned by
120 * the cairo context after the @callback call.
122 * Return value: the new model
124 AdgModel *
125 adg_path_new(void)
127 return (AdgModel *) g_object_new(ADG_TYPE_PATH, NULL);
132 * adg_path_get_cairo_path:
133 * @path: an #AdgPath
135 * Gets a pointer to the cairo path structure of @path. The return value
136 * is owned by @path and must be considered read-only.
138 * This function also converts %CAIRO_PATH_ARC_TO primitives, not
139 * recognized by cairo, into approximated Bézier curves. The conversion
140 * is cached so any furter request is O(1). This cache is cleared
141 * whenever @path is modified (by adding a new primitive or by calling
142 * adg_path_clear()).
144 * <important>
145 * <title>TODO</title>
146 * <itemizedlist>
147 * <listitem>Actually, the arcs are approximated to Bézier using the
148 * hardcoded max angle of PI/2. This should be customizable
149 * by adding, for instance, a property to the #AdgPath class
150 * with a default value of PI/2.</listitem>
151 * </itemizedlist>
152 * </important>
154 * Return value: a pointer to the internal cairo path or %NULL on errors
156 const cairo_path_t *
157 adg_path_get_cairo_path(AdgPath *path)
159 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
161 return get_cairo_path(path);
165 * adg_path_get_cpml_path:
166 * @path: an #AdgPath
168 * Gets a pointer to the cairo path structure of @path. The return
169 * value is owned by @path and must not be freed.
171 * This function is similar to adg_path_get_cairo_path() but with
172 * two important differences: firstly the arc primitives are not
173 * expanded to Bézier curves and secondly the returned path is
174 * not read-only. This means it is allowed to modify the returned
175 * path as long as its size is retained and its data contains a
176 * valid path.
178 * Keep in mind any changes to @path makes the value returned by
179 * this function useless, as it is likely to contain plain garbage.
181 * Return value: a pointer to the internal cpml path or %NULL on errors
183 cairo_path_t *
184 adg_path_get_cpml_path(AdgPath *path)
186 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
188 clear_cairo_path(path);
190 return get_cpml_path(path);
194 * adg_path_dup_cpml_path:
195 * @path: an #AdgPath
197 * Gets a duplicate of the cairo path structure of @path. The return
198 * value should be freed with g_free(). The returned #cairo_path_t
199 * struct also contains the #cairo_path_data_t array in the same
200 * chunk of memory, so a g_free() to the returned pointer also frees
201 * the data array.
203 * Although quite similar to adg_path_get_cpml_path(), the use case
204 * of this method is different: being a duplicate, modifying the
205 * returned path does not modify @path itsself.
207 * Return value: a newly allocated #cairo_path_t pointer to be freed
208 * with g_free() or %NULL on errors
210 cairo_path_t *
211 adg_path_dup_cpml_path(AdgPath *path)
213 cairo_path_t *cpml_path;
214 gsize head_size, data_size;
216 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
218 head_size = sizeof(cairo_path_t);
219 data_size = sizeof(cairo_path_data_t) * path->priv->path->len;
221 /* Both the cairo_path_t struct and the cairo_path_data_t array
222 * are stored in the same chunk of memory, so only a single
223 * g_free() is needed */
224 cpml_path = g_malloc(head_size + data_size);
225 cpml_path->status = CAIRO_STATUS_SUCCESS;
226 cpml_path->data = (cairo_path_data_t *) ((gchar *) cpml_path + head_size);
227 cpml_path->num_data = path->priv->path->len;
228 memcpy(cpml_path->data, path->priv->path->data, data_size);
230 return cpml_path;
234 * adg_path_get_current_point:
235 * @path: an #AdgPath
236 * @x: return value for x coordinate of the current point
237 * @y: return value for y coordinate of the current point
239 * Gets the current point of @path, which is conceptually the
240 * final point reached by the path so far.
242 * If there is no defined current point, @x and @y will both be set
243 * to 0 and a warning will be triggered. It is possible to check this
244 * in advance with adg_path_has_current_point().
246 * Most #AdgPath methods alter the current point and most of them
247 * expect a current point to be defined otherwise will fail triggering
248 * a warning. Check the description of every method for specific details.
250 void
251 adg_path_get_current_point(AdgPath *path, gdouble *x, gdouble *y)
253 g_return_if_fail(ADG_IS_PATH(path));
255 if (path->priv->cp_is_valid) {
256 *x = path->priv->cp.x;
257 *y = path->priv->cp.y;
258 } else {
259 *x = *y = 0.;
260 g_return_if_reached();
265 * adg_path_has_current_point:
266 * @path: an #AdgPath
268 * Returns whether a current point is defined on @path.
269 * See adg_path_get_current_point() for details on the current point.
271 * Return value: whether a current point is defined
273 gboolean
274 adg_path_has_current_point(AdgPath *path)
276 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
278 return path->priv->cp_is_valid;
282 * adg_path_clear:
283 * @path: an #AdgPath
285 * Releases the internal memory hold by @path and resets its status,
286 * so that after this call @path contains an empty path.
288 void
289 adg_path_clear(AdgPath *path)
291 g_return_if_fail(ADG_IS_PATH(path));
293 g_array_set_size(path->priv->path, 0);
294 clear_cairo_path(path);
299 * adg_path_append:
300 * @path: an #AdgPath
301 * @type: a #cairo_data_type_t value
302 * @...: point data, specified as #AdgPair pointers
304 * Generic method to append a primitive to @path. The number of #AdgPair
305 * structs depends on @type: there is no way with this function to
306 * reserve more cairo_path_data_t structs than what is needed by the
307 * primitive.
309 * This function accepts also the special %CAIRO_PATH_ARC_TO primitive.
311 * If @path has no current point while the requested primitive needs it,
312 * a warning message will be triggered without other effect.
314 void
315 adg_path_append(AdgPath *path, cairo_path_data_type_t type, ...)
317 va_list var_args;
319 va_start(var_args, type);
320 adg_path_append_valist(path, type, var_args);
321 va_end(var_args);
325 * adg_path_append_valist:
326 * @path: an #AdgPath
327 * @type: a #cairo_data_type_t value
328 * @var_args: point data, specified as #AdgPair pointers
330 * va_list version of adg_path_append().
332 void
333 adg_path_append_valist(AdgPath *path, cairo_path_data_type_t type,
334 va_list var_args)
336 gint length;
338 g_return_if_fail(ADG_IS_PATH(path));
340 switch (type) {
342 case CAIRO_PATH_CLOSE_PATH:
343 g_return_if_fail(path->priv->cp_is_valid);
344 length = 1;
345 break;
347 case CAIRO_PATH_MOVE_TO:
348 length = 2;
349 break;
351 case CAIRO_PATH_LINE_TO:
352 g_return_if_fail(path->priv->cp_is_valid);
353 length = 2;
354 break;
356 case CAIRO_PATH_ARC_TO:
357 g_return_if_fail(path->priv->cp_is_valid);
358 length = 3;
359 break;
361 case CAIRO_PATH_CURVE_TO:
362 g_return_if_fail(path->priv->cp_is_valid);
363 length = 4;
364 break;
366 default:
367 g_assert_not_reached();
368 return;
371 append_valist(path, type, length, var_args);
376 * adg_path_move_to:
377 * @path: an #AdgPath
378 * @x: the new x coordinate
379 * @y: the new y coordinate
381 * Begins a new segment. After this call the current point will be (@x, @y).
383 void
384 adg_path_move_to(AdgPath *path, gdouble x, gdouble y)
386 AdgPair p;
388 p.x = x;
389 p.y = y;
391 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p);
395 * adg_path_line_to:
396 * @path: an #AdgPath
397 * @x: the new x coordinate
398 * @y: the new y coordinate
400 * Adds a line to @path from the current point to position (@x, @y).
401 * After this call the current point will be (@x, @y).
403 * If @path has no current point before this call, this function will
404 * trigger a warning without other effect.
406 void
407 adg_path_line_to(AdgPath *path, gdouble x, gdouble y)
409 AdgPair p;
411 p.x = x;
412 p.y = y;
414 adg_path_append(path, CAIRO_PATH_LINE_TO, &p);
418 * adg_path_arc_to:
419 * @path: an #AdgPath
420 * @x1: the x coordinate of an intermediate point
421 * @y1: the y coordinate of an intermediate point
422 * @x2: the x coordinate of the end of the arc
423 * @y2: the y coordinate of the end of the arc
425 * Adds an arc to the path from the current point to (@x2, @y2),
426 * passing throught (@x1, @y1). After this call the current point
427 * will be (@x2, @y2).
429 * If @path has no current point before this call, this function will
430 * trigger a warning without other effect.
432 void
433 adg_path_arc_to(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
435 AdgPair p[2];
437 p[0].x = x1;
438 p[0].y = y1;
439 p[1].x = x2;
440 p[1].y = y2;
442 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[0], &p[1]);
446 * adg_path_curve_to:
447 * @path: an #AdgPath
448 * @x1: the x coordinate of the first control point
449 * @y1: the y coordinate of the first control point
450 * @x2: the x coordinate of the second control point
451 * @y2: the y coordinate of the second control point
452 * @x3: the x coordinate of the end of the curve
453 * @y3: the y coordinate of the end of the curve
455 * Adds a cubic Bézier curve to the path from the current point to
456 * position (@x3, @y3), using (@x1, @y1) and (@x2, @y2) as the
457 * control points. After this call the current point will be (@x3, @y3).
459 * If @path has no current point before this call, this function will
460 * trigger a warning without other effect.
462 void
463 adg_path_curve_to(AdgPath *path, gdouble x1, gdouble y1,
464 gdouble x2, gdouble y2, gdouble x3, gdouble y3)
466 AdgPair p[3];
468 p[0].x = x1;
469 p[0].y = y1;
470 p[1].x = x2;
471 p[1].y = y2;
472 p[2].x = x3;
473 p[2].y = y3;
475 adg_path_append(path, CAIRO_PATH_CURVE_TO, &p[0], &p[1], &p[2]);
479 * adg_path_close:
480 * @path: an #AdgPath
482 * Adds a line segment to the path from the current point to the
483 * beginning of the current segment, (the most recent point passed
484 * to an adg_path_move_to()), and closes this segment.
485 * After this call the current point will be unset.
487 * The behavior of adg_path_close() is distinct from simply calling
488 * adg_line_to() with the coordinates of the segment starting point.
489 * When a closed segment is stroked, there are no caps on the ends.
490 * Instead, there is a line join connecting the final and initial
491 * primitive of the segment.
493 * If @path has no current point before this call, this function will
494 * trigger a warning without other effect.
496 void
497 adg_path_close(AdgPath *path)
499 adg_path_append(path, CAIRO_PATH_CLOSE_PATH);
503 * adg_path_arc
504 * @path: an #AdgPath
505 * @xc: x position of the center of the arc
506 * @yc: y position of the center of the arc
507 * @r: the radius of the arc
508 * @start: the start angle, in radians
509 * @end: the end angle, in radians
511 * A more usual way to add an arc to @path. After this call, the current
512 * point will be the computed end point of the arc. The arc will be
513 * rendered in increasing angle, accordling to @start and @end. This means
514 * if @start is less than @end, the arc will be rendered in clockwise
515 * direction (accordling to the default cairo coordinate system) while if
516 * @start is greather than @end, the arc will be rendered in couterclockwise
517 * direction.
519 * By explicitely setting the whole arc data, the start point could be
520 * different from the current point. In this case, if @path has no
521 * current point before the call a %CAIRO_PATH_MOVE_TO to the start
522 * point of the arc will be automatically prepended to the arc.
523 * If @path has a current point, a %CAIRO_PATH_LINE_TO to the start
524 * point of the arc will be used instead of the moveto.
526 void
527 adg_path_arc(AdgPath *path, gdouble xc, gdouble yc, gdouble r,
528 gdouble start, gdouble end)
530 AdgPair center, p[3];
532 g_return_if_fail(ADG_IS_PATH(path));
534 center.x = xc;
535 center.y = yc;
537 cpml_vector_from_angle(&p[0], start, r);
538 cpml_vector_from_angle(&p[1], (end-start) / 2, r);
539 cpml_vector_from_angle(&p[2], end, r);
541 cpml_pair_add(&p[0], &center);
542 cpml_pair_add(&p[1], &center);
543 cpml_pair_add(&p[2], &center);
545 if (!path->priv->cp_is_valid)
546 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p[0]);
547 else if (p[0].x != path->priv->cp.x || p[0].y != path->priv->cp.y)
548 adg_path_append(path, CAIRO_PATH_LINE_TO, &p[0]);
550 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[1], &p[2]);
555 * adg_path_dump:
556 * @path: an #AdgPath
558 * Dumps the data content of @path to stdout in a human readable format.
560 void
561 adg_path_dump(AdgPath *path)
563 CpmlSegment segment;
564 cairo_path_t *cairo_path;
566 g_return_if_fail(ADG_IS_PATH(path));
568 cairo_path = get_cairo_path(path);
570 g_return_if_fail(cairo_path != NULL);
572 if (!cpml_segment_from_cairo(&segment, cairo_path)) {
573 g_print("Invalid path data to dump!\n");
574 } else {
575 do {
576 cpml_segment_dump(&segment);
577 } while (cpml_segment_next(&segment));
582 static void
583 changed(AdgModel *model)
585 adg_path_clear((AdgPath *) model);
587 PARENT_CLASS->changed(model);
590 static void
591 clear_cairo_path(AdgPath *path)
593 cairo_path_t *cairo_path = &path->priv->cairo_path;
595 if (cairo_path->data == NULL)
596 return;
598 g_free(cairo_path->data);
600 cairo_path->status = CAIRO_STATUS_INVALID_PATH_DATA;
601 cairo_path->data = NULL;
602 cairo_path->num_data = 0;
605 static cairo_path_t *
606 get_cairo_path(AdgPath *path)
608 cairo_path_t *cairo_path;
609 const GArray *src;
610 GArray *dst;
611 const cairo_path_data_t *p_src;
612 int i;
614 /* Check for cached result */
615 cairo_path = &path->priv->cairo_path;
616 if (cairo_path->data != NULL)
617 return cairo_path;
619 src = path->priv->path;
620 dst = g_array_sized_new(FALSE, FALSE, sizeof(cairo_path_data_t), src->len);
622 /* Cycle the path and convert arcs to Bézier curves */
623 for (i = 0; i < src->len; i += p_src->header.length) {
624 p_src = (const cairo_path_data_t *) src->data + i;
626 if (p_src->header.type == CAIRO_PATH_ARC_TO)
627 dst = arc_to_curves(dst, p_src);
628 else
629 dst = g_array_append_vals(dst, p_src, p_src->header.length);
632 cairo_path->status = CAIRO_STATUS_SUCCESS;
633 cairo_path->num_data = dst->len;
634 cairo_path->data = (cairo_path_data_t *) g_array_free(dst, FALSE);
636 return cairo_path;
639 static cairo_path_t *
640 get_cpml_path(AdgPath *path)
642 cairo_path_t *cpml_path = &path->priv->cpml_path;
644 cpml_path->status = CAIRO_STATUS_SUCCESS;
645 cpml_path->data = (cairo_path_data_t *) path->priv->path->data;
646 cpml_path->num_data = path->priv->path->len;
648 return cpml_path;
651 static GArray *
652 arc_to_curves(GArray *array, const cairo_path_data_t *src)
654 CpmlPrimitive arc;
655 double start, end;
657 /* Build the arc primitive: the arc origin is supposed to be the previous
658 * point (src-1): this means a primitive must exist before the arc */
659 arc.segment = NULL;
660 arc.org = (cairo_path_data_t *) (src-1);
661 arc.data = (cairo_path_data_t *) src;
663 if (cpml_arc_info(&arc, NULL, NULL, &start, &end)) {
664 CpmlSegment segment;
665 int n_curves;
666 cairo_path_data_t *curves;
668 n_curves = ceil(fabs(end-start) / M_PI_2);
669 curves = g_new(cairo_path_data_t, n_curves * 4);
670 segment.data = curves;
671 cpml_arc_to_curves(&arc, &segment, n_curves);
673 array = g_array_append_vals(array, curves, n_curves * 4);
675 g_free(curves);
678 return array;
681 static void
682 append_valist(AdgPath *path, cairo_path_data_type_t type,
683 int length, va_list var_args)
685 AdgPathPrivate *priv;
686 cairo_path_data_t item;
688 priv = path->priv;
690 /* Append the header item */
691 item.header.type = type;
692 item.header.length = length;
693 priv->path = g_array_append_val(priv->path, item);
694 priv->cp_is_valid = FALSE;
696 /* Append the data items (that is, the AdgPair points) */
697 while (--length) {
698 cpml_pair_to_cairo(va_arg(var_args, AdgPair *), &item);
699 priv->path = g_array_append_val(priv->path, item);
700 priv->cp_is_valid = TRUE;
703 /* Save the last point as the current point */
704 if (priv->cp_is_valid)
705 cpml_pair_from_cairo(&priv->cp, &item);
707 /* Invalidate cairo_path: should be recomputed */
708 clear_cairo_path(path);