[AdgPath] Corrected docblock in adg_path_arc()
[adg.git] / adg / adg-path.c
blobff7b7cdf85c03ea76106250b267037a4c834428a
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 GArray * arc_to_curves (GArray *array,
61 const cairo_path_data_t *src);
62 static void append_valist (AdgPath *path,
63 cairo_path_data_type_t type,
64 int length,
65 va_list var_args);
68 G_DEFINE_TYPE(AdgPath, adg_path, ADG_TYPE_MODEL);
71 static void
72 adg_path_class_init(AdgPathClass *klass)
74 GObjectClass *gobject_class;
75 AdgModelClass *model_class;
77 gobject_class = (GObjectClass *) klass;
78 model_class = (AdgModelClass *) klass;
80 g_type_class_add_private(klass, sizeof(AdgPathPrivate));
82 gobject_class->finalize = finalize;
84 model_class->changed = changed;
87 static void
88 adg_path_init(AdgPath *path)
90 AdgPathPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(path, ADG_TYPE_PATH,
91 AdgPathPrivate);
93 priv->cp_is_valid = FALSE;
94 priv->path = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t));
95 priv->cairo_path.status = CAIRO_STATUS_INVALID_PATH_DATA;
96 priv->cairo_path.data = NULL;
97 priv->cairo_path.num_data = 0;
99 path->priv = priv;
102 static void
103 finalize(GObject *object)
105 AdgPath *path = (AdgPath *) object;
107 g_array_free(path->priv->path, TRUE);
108 clear_cairo_path(path);
110 ((GObjectClass *) PARENT_CLASS)->finalize(object);
115 * adg_path_new:
117 * Creates a new path model. The path must be constructed in the @callback
118 * function: AdgPath will cache and reuse the cairo_copy_path() returned by
119 * the cairo context after the @callback call.
121 * Return value: the new model
123 AdgModel *
124 adg_path_new(void)
126 return (AdgModel *) g_object_new(ADG_TYPE_PATH, NULL);
131 * adg_path_get_cairo_path:
132 * @path: an #AdgPath
134 * Gets a pointer to the cairo path structure of @path. The return value
135 * is owned by @path and must be considered read-only.
137 * This function also converts %CAIRO_PATH_ARC_TO primitives, not
138 * recognized by cairo, into approximated Bézier curves. The conversion
139 * is cached so any furter request is O(1). This cache is cleared
140 * whenever @path is modified (by adding a new primitive or by calling
141 * adg_path_clear()).
143 * <important>
144 * <title>TODO</title>
145 * <itemizedlist>
146 * <listitem>Actually, the arcs are approximated to Bézier using the
147 * hardcoded max angle of PI/2. This should be customizable
148 * by adding, for instance, a property to the #AdgPath class
149 * with a default value of PI/2.</listitem>
150 * </itemizedlist>
151 * </important>
153 * Return value: a pointer to the internal #cairo_path_t structure
154 * 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_current_point:
166 * @path: an #AdgPath
167 * @x: return value for x coordinate of the current point
168 * @y: return value for y coordinate of the current point
170 * Gets the current point of @path, which is conceptually the
171 * final point reached by the path so far.
173 * If there is no defined current point, @x and @y will both be set
174 * to 0 and a warning will be triggered. It is possible to check this
175 * in advance with adg_path_has_current_point().
177 * Most #AdgPath methods alter the current point and most of them
178 * expect a current point to be defined otherwise will fail triggering
179 * a warning. Check the description of every method for specific details.
181 void
182 adg_path_get_current_point(AdgPath *path, gdouble *x, gdouble *y)
184 g_return_if_fail(ADG_IS_PATH(path));
186 if (path->priv->cp_is_valid) {
187 *x = path->priv->cp.x;
188 *y = path->priv->cp.y;
189 } else {
190 *x = *y = 0.;
191 g_return_if_reached();
196 * adg_path_has_current_point:
197 * @path: an #AdgPath
199 * Returns whether a current point is defined on @path.
200 * See adg_path_get_current_point() for details on the current point.
202 * Return value: whether a current point is defined
204 gboolean
205 adg_path_has_current_point(AdgPath *path)
207 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
209 return path->priv->cp_is_valid;
213 * adg_path_clear:
214 * @path: an #AdgPath
216 * Releases the internal memory hold by @path and resets its status,
217 * so that after this call @path contains an empty path.
219 void
220 adg_path_clear(AdgPath *path)
222 g_return_if_fail(ADG_IS_PATH(path));
224 g_array_set_size(path->priv->path, 0);
225 clear_cairo_path(path);
230 * adg_path_append:
231 * @path: an #AdgPath
232 * @type: a #cairo_data_type_t value
233 * @...: point data, specified as #AdgPair pointers
235 * Generic method to append a primitive to @path. The number of #AdgPair
236 * structs depends on @type: there is no way with this function to
237 * reserve more cairo_path_data_t structs than what is needed by the
238 * primitive.
240 * This function accepts also the special %CAIRO_PATH_ARC_TO primitive.
242 * If @path has no current point while the requested primitive needs it,
243 * a warning message will be triggered without other effect.
245 void
246 adg_path_append(AdgPath *path, cairo_path_data_type_t type, ...)
248 va_list var_args;
250 va_start(var_args, type);
251 adg_path_append_valist(path, type, var_args);
252 va_end(var_args);
256 * adg_path_append_valist:
257 * @path: an #AdgPath
258 * @type: a #cairo_data_type_t value
259 * @var_args: point data, specified as #AdgPair pointers
261 * va_list version of adg_path_append().
263 void
264 adg_path_append_valist(AdgPath *path, cairo_path_data_type_t type,
265 va_list var_args)
267 gint length;
269 g_return_if_fail(ADG_IS_PATH(path));
271 switch (type) {
273 case CAIRO_PATH_CLOSE_PATH:
274 g_return_if_fail(path->priv->cp_is_valid);
275 length = 1;
276 break;
278 case CAIRO_PATH_MOVE_TO:
279 length = 2;
280 break;
282 case CAIRO_PATH_LINE_TO:
283 g_return_if_fail(path->priv->cp_is_valid);
284 length = 2;
285 break;
287 case CAIRO_PATH_ARC_TO:
288 g_return_if_fail(path->priv->cp_is_valid);
289 length = 3;
290 break;
292 case CAIRO_PATH_CURVE_TO:
293 g_return_if_fail(path->priv->cp_is_valid);
294 length = 4;
295 break;
297 default:
298 g_assert_not_reached();
299 return;
302 append_valist(path, type, length, var_args);
307 * adg_path_move_to:
308 * @path: an #AdgPath
309 * @x: the new x coordinate
310 * @y: the new y coordinate
312 * Begins a new segment. After this call the current point will be (@x, @y).
314 void
315 adg_path_move_to(AdgPath *path, gdouble x, gdouble y)
317 AdgPair p;
319 p.x = x;
320 p.y = y;
322 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p);
326 * adg_path_line_to:
327 * @path: an #AdgPath
328 * @x: the new x coordinate
329 * @y: the new y coordinate
331 * Adds a line to @path from the current point to position (@x, @y).
332 * After this call the current point will be (@x, @y).
334 * If @path has no current point before this call, this function will
335 * trigger a warning without other effect.
337 void
338 adg_path_line_to(AdgPath *path, gdouble x, gdouble y)
340 AdgPair p;
342 p.x = x;
343 p.y = y;
345 adg_path_append(path, CAIRO_PATH_LINE_TO, &p);
349 * adg_path_arc_to:
350 * @path: an #AdgPath
351 * @x1: the x coordinate of an intermediate point
352 * @y1: the y coordinate of an intermediate point
353 * @x2: the x coordinate of the end of the arc
354 * @y2: the y coordinate of the end of the arc
356 * Adds an arc to the path from the current point to (@x2, @y2),
357 * passing throught (@x1, @y1). After this call the current point
358 * will be (@x2, @y2).
360 * If @path has no current point before this call, this function will
361 * trigger a warning without other effect.
363 void
364 adg_path_arc_to(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
366 AdgPair p[2];
368 p[0].x = x1;
369 p[0].y = y1;
370 p[1].x = x2;
371 p[1].y = y2;
373 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[0], &p[1]);
377 * adg_path_curve_to:
378 * @path: an #AdgPath
379 * @x1: the x coordinate of the first control point
380 * @y1: the y coordinate of the first control point
381 * @x2: the x coordinate of the second control point
382 * @y2: the y coordinate of the second control point
383 * @x3: the x coordinate of the end of the curve
384 * @y3: the y coordinate of the end of the curve
386 * Adds a cubic Bézier curve to the path from the current point to
387 * position (@x3, @y3), using (@x1, @y1) and (@x2, @y2) as the
388 * control points. After this call the current point will be (@x3, @y3).
390 * If @path has no current point before this call, this function will
391 * trigger a warning without other effect.
393 void
394 adg_path_curve_to(AdgPath *path, gdouble x1, gdouble y1,
395 gdouble x2, gdouble y2, gdouble x3, gdouble y3)
397 AdgPair p[3];
399 p[0].x = x1;
400 p[0].y = y1;
401 p[1].x = x2;
402 p[1].y = y2;
403 p[2].x = x3;
404 p[2].y = y3;
406 adg_path_append(path, CAIRO_PATH_CURVE_TO, &p[0], &p[1], &p[2]);
410 * adg_path_close:
411 * @path: an #AdgPath
413 * Adds a line segment to the path from the current point to the
414 * beginning of the current segment, (the most recent point passed
415 * to an adg_path_move_to()), and closes this segment.
416 * After this call the current point will be unset.
418 * The behavior of adg_path_close() is distinct from simply calling
419 * adg_line_to() with the coordinates of the segment starting point.
420 * When a closed segment is stroked, there are no caps on the ends.
421 * Instead, there is a line join connecting the final and initial
422 * primitive of the segment.
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_close(AdgPath *path)
430 adg_path_append(path, CAIRO_PATH_CLOSE_PATH);
434 * adg_path_arc
435 * @path: an #AdgPath
436 * @xc: x position of the center of the arc
437 * @yc: y position of the center of the arc
438 * @r: the radius of the arc
439 * @start: the start angle, in radians
440 * @end: the end angle, in radians
442 * A more usual way to add an arc to @path. After this call, the current
443 * point will be the computed end point of the arc. The arc will be
444 * rendered in increasing angle, accordling to @start and @end. This means
445 * if @start is less than @end, the arc will be rendered in clockwise
446 * direction (accordling to the default cairo coordinate system) while if
447 * @start is greather than @end, the arc will be rendered in couterclockwise
448 * direction.
450 * By explicitely setting the whole arc data, the start point could be
451 * different from the current point. In this case, if @path has no
452 * current point before the call a %CAIRO_PATH_MOVE_TO to the start
453 * point of the arc will be automatically prepended to the arc.
454 * If @path has a current point, a %CAIRO_PATH_LINE_TO to the start
455 * point of the arc will be used instead of the moveto.
457 void
458 adg_path_arc(AdgPath *path, gdouble xc, gdouble yc, gdouble r,
459 gdouble start, gdouble end)
461 AdgPair center, p[3];
463 g_return_if_fail(ADG_IS_PATH(path));
465 center.x = xc;
466 center.y = yc;
468 cpml_vector_from_angle(&p[0], start, r);
469 cpml_vector_from_angle(&p[1], (end-start) / 2, r);
470 cpml_vector_from_angle(&p[2], end, r);
472 cpml_pair_add(&p[0], &center);
473 cpml_pair_add(&p[1], &center);
474 cpml_pair_add(&p[2], &center);
476 if (!path->priv->cp_is_valid)
477 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p[0]);
478 else if (p[0].x != path->priv->cp.x || p[0].y != path->priv->cp.y)
479 adg_path_append(path, CAIRO_PATH_LINE_TO, &p[0]);
481 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[1], &p[2]);
486 * adg_path_dump:
487 * @path: an #AdgPath
489 * Dumps the data content of @path to stdout in a human readable format.
491 void
492 adg_path_dump(AdgPath *path)
494 CpmlSegment segment;
495 cairo_path_t *cairo_path;
497 g_return_if_fail(ADG_IS_PATH(path));
499 cairo_path = get_cairo_path(path);
501 g_return_if_fail(cairo_path != NULL);
503 if (!cpml_segment_from_cairo(&segment, cairo_path)) {
504 g_print("Invalid path data to dump!\n");
505 } else {
506 do {
507 cpml_segment_dump(&segment);
508 } while (cpml_segment_next(&segment));
513 static void
514 changed(AdgModel *model)
516 adg_path_clear((AdgPath *) model);
518 PARENT_CLASS->changed(model);
521 static void
522 clear_cairo_path(AdgPath *path)
524 cairo_path_t *cairo_path = &path->priv->cairo_path;
526 if (cairo_path->data == NULL)
527 return;
529 g_free(cairo_path->data);
531 cairo_path->status = CAIRO_STATUS_INVALID_PATH_DATA;
532 cairo_path->data = NULL;
533 cairo_path->num_data = 0;
536 static cairo_path_t *
537 get_cairo_path(AdgPath *path)
539 cairo_path_t *cairo_path;
540 const GArray *src;
541 GArray *dst;
542 const cairo_path_data_t *p_src;
543 int i;
545 /* Check for cached result */
546 cairo_path = &path->priv->cairo_path;
547 if (cairo_path->data != NULL)
548 return cairo_path;
550 src = path->priv->path;
551 dst = g_array_sized_new(FALSE, FALSE, sizeof(cairo_path_data_t), src->len);
553 /* Cycle the path and convert arcs to Bézier curves */
554 for (i = 0; i < src->len; i += p_src->header.length) {
555 p_src = (const cairo_path_data_t *) src->data + i;
557 if (p_src->header.type == CAIRO_PATH_ARC_TO)
558 dst = arc_to_curves(dst, p_src);
559 else
560 dst = g_array_append_vals(dst, p_src, p_src->header.length);
563 cairo_path->status = CAIRO_STATUS_SUCCESS;
564 cairo_path->num_data = dst->len;
565 cairo_path->data = (cairo_path_data_t *) g_array_free(dst, FALSE);
567 return cairo_path;
570 static GArray *
571 arc_to_curves(GArray *array, const cairo_path_data_t *src)
573 CpmlPrimitive arc;
574 double start, end;
576 /* Build the arc primitive: the arc origin is supposed to be the previous
577 * point (src-1): this means a primitive must exist before the arc */
578 arc.segment = NULL;
579 arc.org = (cairo_path_data_t *) (src-1);
580 arc.data = (cairo_path_data_t *) src;
582 if (cpml_arc_info(&arc, NULL, NULL, &start, &end)) {
583 CpmlSegment segment;
584 int n_curves;
585 cairo_path_data_t *curves;
587 n_curves = ceil(fabs(end-start) / M_PI_2);
588 curves = g_new(cairo_path_data_t, n_curves * 4);
589 segment.data = curves;
590 cpml_arc_to_curves(&arc, &segment, n_curves);
592 array = g_array_append_vals(array, curves, n_curves * 4);
594 g_free(curves);
597 return array;
600 static void
601 append_valist(AdgPath *path, cairo_path_data_type_t type,
602 int length, va_list var_args)
604 AdgPathPrivate *priv;
605 cairo_path_data_t item;
607 priv = path->priv;
609 /* Append the header item */
610 item.header.type = type;
611 item.header.length = length;
612 priv->path = g_array_append_val(priv->path, item);
613 priv->cp_is_valid = FALSE;
615 /* Append the data items (that is, the AdgPair points) */
616 while (--length) {
617 cpml_pair_to_cairo(va_arg(var_args, AdgPair *), &item);
618 priv->path = g_array_append_val(priv->path, item);
619 priv->cp_is_valid = TRUE;
622 /* Save the last point as the current point */
623 if (priv->cp_is_valid)
624 cpml_pair_from_cairo(&priv->cp, &item);
626 /* Invalidate cairo_path: should be recomputed */
627 clear_cairo_path(path);