[CpmlCurve] Added stub for cpml_curve_length()
[adg.git] / adg / adg-path.c
blob5a7b58ef775a9ec2736848b38ded1e2df201fb3d
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;
97 priv->operator = ADG_OPERATOR_NONE;
99 path->priv = priv;
102 static void
103 finalize(GObject *object)
105 AdgPath *path;
106 GObjectClass *object_class;
108 path = (AdgPath *) object;
109 object_class = (GObjectClass *) adg_path_parent_class;
111 g_array_free(path->priv->path, TRUE);
112 clear_cairo_path(path);
114 if (object_class->finalize != NULL)
115 object_class->finalize(object);
120 * adg_path_new:
122 * Creates a new path model. The path must be constructed in the @callback
123 * function: AdgPath will cache and reuse the cairo_copy_path() returned by
124 * the cairo context after the @callback call.
126 * Return value: the new model
128 AdgModel *
129 adg_path_new(void)
131 return (AdgModel *) g_object_new(ADG_TYPE_PATH, NULL);
136 * adg_path_get_cairo_path:
137 * @path: an #AdgPath
139 * Gets a pointer to the cairo path structure of @path. The return value
140 * is owned by @path and must be considered read-only.
142 * This function also converts %CAIRO_PATH_ARC_TO primitives, not
143 * recognized by cairo, into approximated Bézier curves. The conversion
144 * is cached so any furter request is O(1). This cache is cleared
145 * whenever @path is modified (by adding a new primitive or by calling
146 * adg_path_clear()).
148 * <important>
149 * <title>TODO</title>
150 * <itemizedlist>
151 * <listitem>Actually, the arcs are approximated to Bézier using the
152 * hardcoded max angle of PI/2. This should be customizable
153 * by adding, for instance, a property to the #AdgPath class
154 * with a default value of PI/2.</listitem>
155 * </itemizedlist>
156 * </important>
158 * Return value: a pointer to the internal cairo path or %NULL on errors
160 const cairo_path_t *
161 adg_path_get_cairo_path(AdgPath *path)
163 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
165 return get_cairo_path(path);
169 * adg_path_get_cpml_path:
170 * @path: an #AdgPath
172 * Gets a pointer to the cairo path structure of @path. The return
173 * value is owned by @path and must not be freed.
175 * This function is similar to adg_path_get_cairo_path() but with
176 * two important differences: firstly the arc primitives are not
177 * expanded to Bézier curves and secondly the returned path is
178 * not read-only. This means it is allowed to modify the returned
179 * path as long as its size is retained and its data contains a
180 * valid path.
182 * Keep in mind any changes to @path makes the value returned by
183 * this function useless, as it is likely to contain plain garbage.
185 * Return value: a pointer to the internal cpml path or %NULL on errors
187 cairo_path_t *
188 adg_path_get_cpml_path(AdgPath *path)
190 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
192 clear_cairo_path(path);
194 return get_cpml_path(path);
198 * adg_path_dup_cpml_path:
199 * @path: an #AdgPath
201 * Gets a duplicate of the cairo path structure of @path. The return
202 * value should be freed with g_free(). The returned #cairo_path_t
203 * struct also contains the #cairo_path_data_t array in the same
204 * chunk of memory, so a g_free() to the returned pointer also frees
205 * the data array.
207 * Although quite similar to adg_path_get_cpml_path(), the use case
208 * of this method is different: being a duplicate, modifying the
209 * returned path does not modify @path itsself.
211 * Return value: a newly allocated #cairo_path_t pointer to be freed
212 * with g_free() or %NULL on errors
214 cairo_path_t *
215 adg_path_dup_cpml_path(AdgPath *path)
217 cairo_path_t *cpml_path;
218 gsize head_size, data_size;
220 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
222 head_size = sizeof(cairo_path_t);
223 data_size = sizeof(cairo_path_data_t) * path->priv->path->len;
225 /* Both the cairo_path_t struct and the cairo_path_data_t array
226 * are stored in the same chunk of memory, so only a single
227 * g_free() is needed */
228 cpml_path = g_malloc(head_size + data_size);
229 cpml_path->status = CAIRO_STATUS_SUCCESS;
230 cpml_path->data = (cairo_path_data_t *) ((gchar *) cpml_path + head_size);
231 cpml_path->num_data = path->priv->path->len;
232 memcpy(cpml_path->data, path->priv->path->data, data_size);
234 return cpml_path;
238 * adg_path_get_current_point:
239 * @path: an #AdgPath
240 * @x: return value for x coordinate of the current point
241 * @y: return value for y coordinate of the current point
243 * Gets the current point of @path, which is conceptually the
244 * final point reached by the path so far.
246 * If there is no defined current point, @x and @y will both be set
247 * to 0 and a warning will be triggered. It is possible to check this
248 * in advance with adg_path_has_current_point().
250 * Most #AdgPath methods alter the current point and most of them
251 * expect a current point to be defined otherwise will fail triggering
252 * a warning. Check the description of every method for specific details.
254 void
255 adg_path_get_current_point(AdgPath *path, gdouble *x, gdouble *y)
257 g_return_if_fail(ADG_IS_PATH(path));
259 if (path->priv->cp_is_valid) {
260 *x = path->priv->cp.x;
261 *y = path->priv->cp.y;
262 } else {
263 *x = *y = 0.;
264 g_return_if_reached();
269 * adg_path_has_current_point:
270 * @path: an #AdgPath
272 * Returns whether a current point is defined on @path.
273 * See adg_path_get_current_point() for details on the current point.
275 * Return value: whether a current point is defined
277 gboolean
278 adg_path_has_current_point(AdgPath *path)
280 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
282 return path->priv->cp_is_valid;
286 * adg_path_clear:
287 * @path: an #AdgPath
289 * Releases the internal memory hold by @path and resets its status,
290 * so that after this call @path contains an empty path.
292 void
293 adg_path_clear(AdgPath *path)
295 g_return_if_fail(ADG_IS_PATH(path));
297 g_array_set_size(path->priv->path, 0);
298 clear_cairo_path(path);
303 * adg_path_append:
304 * @path: an #AdgPath
305 * @type: a #cairo_data_type_t value
306 * @...: point data, specified as #AdgPair pointers
308 * Generic method to append a primitive to @path. The number of #AdgPair
309 * structs depends on @type: there is no way with this function to
310 * reserve more cairo_path_data_t structs than what is needed by the
311 * primitive.
313 * This function accepts also the special %CAIRO_PATH_ARC_TO primitive.
315 * If @path has no current point while the requested primitive needs it,
316 * a warning message will be triggered without other effect.
318 void
319 adg_path_append(AdgPath *path, cairo_path_data_type_t type, ...)
321 va_list var_args;
323 va_start(var_args, type);
324 adg_path_append_valist(path, type, var_args);
325 va_end(var_args);
329 * adg_path_append_valist:
330 * @path: an #AdgPath
331 * @type: a #cairo_data_type_t value
332 * @var_args: point data, specified as #AdgPair pointers
334 * va_list version of adg_path_append().
336 void
337 adg_path_append_valist(AdgPath *path, cairo_path_data_type_t type,
338 va_list var_args)
340 gint length;
342 g_return_if_fail(ADG_IS_PATH(path));
344 switch (type) {
346 case CAIRO_PATH_CLOSE_PATH:
347 g_return_if_fail(path->priv->cp_is_valid);
348 length = 1;
349 break;
351 case CAIRO_PATH_MOVE_TO:
352 length = 2;
353 break;
355 case CAIRO_PATH_LINE_TO:
356 g_return_if_fail(path->priv->cp_is_valid);
357 length = 2;
358 break;
360 case CAIRO_PATH_ARC_TO:
361 g_return_if_fail(path->priv->cp_is_valid);
362 length = 3;
363 break;
365 case CAIRO_PATH_CURVE_TO:
366 g_return_if_fail(path->priv->cp_is_valid);
367 length = 4;
368 break;
370 default:
371 g_assert_not_reached();
372 return;
375 append_valist(path, type, length, var_args);
379 * adg_path_append_cairo_path:
380 * @path: an #AdgPath
381 * @cairo_path: the #cairo_path_t path to append
383 * Appends a whole cairo path to @path.
385 void
386 adg_path_append_cairo_path(AdgPath *path, const cairo_path_t *cairo_path)
388 g_return_if_fail(ADG_IS_PATH(path));
390 clear_cairo_path(path);
391 path->priv->path = g_array_append_vals(path->priv->path,
392 cairo_path->data,
393 cairo_path->num_data);
398 * adg_path_move_to:
399 * @path: an #AdgPath
400 * @x: the new x coordinate
401 * @y: the new y coordinate
403 * Begins a new segment. After this call the current point will be (@x, @y).
405 void
406 adg_path_move_to(AdgPath *path, gdouble x, gdouble y)
408 AdgPair p;
410 p.x = x;
411 p.y = y;
413 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p);
417 * adg_path_line_to:
418 * @path: an #AdgPath
419 * @x: the new x coordinate
420 * @y: the new y coordinate
422 * Adds a line to @path from the current point to position (@x, @y).
423 * After this call the current point will be (@x, @y).
425 * If @path has no current point before this call, this function will
426 * trigger a warning without other effect.
428 void
429 adg_path_line_to(AdgPath *path, gdouble x, gdouble y)
431 AdgPair p;
433 p.x = x;
434 p.y = y;
436 adg_path_append(path, CAIRO_PATH_LINE_TO, &p);
440 * adg_path_arc_to:
441 * @path: an #AdgPath
442 * @x1: the x coordinate of an intermediate point
443 * @y1: the y coordinate of an intermediate point
444 * @x2: the x coordinate of the end of the arc
445 * @y2: the y coordinate of the end of the arc
447 * Adds an arc to the path from the current point to (@x2, @y2),
448 * passing throught (@x1, @y1). After this call the current point
449 * will be (@x2, @y2).
451 * If @path has no current point before this call, this function will
452 * trigger a warning without other effect.
454 void
455 adg_path_arc_to(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
457 AdgPair p[2];
459 p[0].x = x1;
460 p[0].y = y1;
461 p[1].x = x2;
462 p[1].y = y2;
464 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[0], &p[1]);
468 * adg_path_curve_to:
469 * @path: an #AdgPath
470 * @x1: the x coordinate of the first control point
471 * @y1: the y coordinate of the first control point
472 * @x2: the x coordinate of the second control point
473 * @y2: the y coordinate of the second control point
474 * @x3: the x coordinate of the end of the curve
475 * @y3: the y coordinate of the end of the curve
477 * Adds a cubic Bézier curve to the path from the current point to
478 * position (@x3, @y3), using (@x1, @y1) and (@x2, @y2) as the
479 * control points. After this call the current point will be (@x3, @y3).
481 * If @path has no current point before this call, this function will
482 * trigger a warning without other effect.
484 void
485 adg_path_curve_to(AdgPath *path, gdouble x1, gdouble y1,
486 gdouble x2, gdouble y2, gdouble x3, gdouble y3)
488 AdgPair p[3];
490 p[0].x = x1;
491 p[0].y = y1;
492 p[1].x = x2;
493 p[1].y = y2;
494 p[2].x = x3;
495 p[2].y = y3;
497 adg_path_append(path, CAIRO_PATH_CURVE_TO, &p[0], &p[1], &p[2]);
501 * adg_path_close:
502 * @path: an #AdgPath
504 * Adds a line segment to the path from the current point to the
505 * beginning of the current segment, (the most recent point passed
506 * to an adg_path_move_to()), and closes this segment.
507 * After this call the current point will be unset.
509 * The behavior of adg_path_close() is distinct from simply calling
510 * adg_line_to() with the coordinates of the segment starting point.
511 * When a closed segment is stroked, there are no caps on the ends.
512 * Instead, there is a line join connecting the final and initial
513 * primitive of the segment.
515 * If @path has no current point before this call, this function will
516 * trigger a warning without other effect.
518 void
519 adg_path_close(AdgPath *path)
521 adg_path_append(path, CAIRO_PATH_CLOSE_PATH);
525 * adg_path_arc
526 * @path: an #AdgPath
527 * @xc: x position of the center of the arc
528 * @yc: y position of the center of the arc
529 * @r: the radius of the arc
530 * @start: the start angle, in radians
531 * @end: the end angle, in radians
533 * A more usual way to add an arc to @path. After this call, the current
534 * point will be the computed end point of the arc. The arc will be
535 * rendered in increasing angle, accordling to @start and @end. This means
536 * if @start is less than @end, the arc will be rendered in clockwise
537 * direction (accordling to the default cairo coordinate system) while if
538 * @start is greather than @end, the arc will be rendered in couterclockwise
539 * direction.
541 * By explicitely setting the whole arc data, the start point could be
542 * different from the current point. In this case, if @path has no
543 * current point before the call a %CAIRO_PATH_MOVE_TO to the start
544 * point of the arc will be automatically prepended to the arc.
545 * If @path has a current point, a %CAIRO_PATH_LINE_TO to the start
546 * point of the arc will be used instead of the moveto.
548 void
549 adg_path_arc(AdgPath *path, gdouble xc, gdouble yc, gdouble r,
550 gdouble start, gdouble end)
552 AdgPair center, p[3];
554 g_return_if_fail(ADG_IS_PATH(path));
556 center.x = xc;
557 center.y = yc;
559 cpml_vector_from_angle(&p[0], start, r);
560 cpml_vector_from_angle(&p[1], (end-start) / 2, r);
561 cpml_vector_from_angle(&p[2], end, r);
563 cpml_pair_add(&p[0], &center);
564 cpml_pair_add(&p[1], &center);
565 cpml_pair_add(&p[2], &center);
567 if (!path->priv->cp_is_valid)
568 adg_path_append(path, CAIRO_PATH_MOVE_TO, &p[0]);
569 else if (p[0].x != path->priv->cp.x || p[0].y != path->priv->cp.y)
570 adg_path_append(path, CAIRO_PATH_LINE_TO, &p[0]);
572 adg_path_append(path, CAIRO_PATH_ARC_TO, &p[1], &p[2]);
576 * adg_path_chamfer
577 * @path: an #AdgPath
578 * @delta1: the distance from the intersection point of the current primitive
579 * @delta2: the distance from the intersection point of the next primitive
581 * A binary operator that generates a chamfer between two primitives.
582 * The first primitive involved is the current primitive, the second will
583 * be the next primitive appended to @path after this call. The second
584 * primitive is required: if the chamfer operation is not properly
585 * terminated not providing the second primitive, any API accessing the
586 * path in reading mode will fail.
588 void
589 adg_path_chamfer(AdgPath *path, gdouble delta1, gdouble delta2)
591 g_return_if_fail(ADG_IS_PATH(path));
593 if (!path->priv->cp_is_valid) {
594 g_warning("Requested a chamfer operation without a current primitive");
595 return;
598 path->priv->operator = ADG_OPERATOR_CHAMFER;
599 path->priv->operator_data.chamfer.delta1 = delta1;
600 path->priv->operator_data.chamfer.delta2 = delta2;
605 * adg_path_dump:
606 * @path: an #AdgPath
608 * Dumps the data content of @path to stdout in a human readable format.
610 void
611 adg_path_dump(AdgPath *path)
613 CpmlSegment segment;
614 cairo_path_t *cairo_path;
616 g_return_if_fail(ADG_IS_PATH(path));
618 cairo_path = get_cairo_path(path);
620 g_return_if_fail(cairo_path != NULL);
622 if (!cpml_segment_from_cairo(&segment, cairo_path)) {
623 g_print("Invalid path data to dump!\n");
624 } else {
625 do {
626 cpml_segment_dump(&segment);
627 } while (cpml_segment_next(&segment));
632 static void
633 changed(AdgModel *model)
635 AdgModelClass *model_class = (AdgModelClass *) adg_path_parent_class;
637 adg_path_clear((AdgPath *) model);
639 if (model_class->changed != NULL)
640 model_class->changed(model);
643 static void
644 clear_cairo_path(AdgPath *path)
646 cairo_path_t *cairo_path = &path->priv->cairo_path;
648 if (cairo_path->data == NULL)
649 return;
651 g_free(cairo_path->data);
653 cairo_path->status = CAIRO_STATUS_INVALID_PATH_DATA;
654 cairo_path->data = NULL;
655 cairo_path->num_data = 0;
658 static cairo_path_t *
659 get_cairo_path(AdgPath *path)
661 cairo_path_t *cairo_path;
662 const GArray *src;
663 GArray *dst;
664 const cairo_path_data_t *p_src;
665 int i;
667 /* Check for cached result */
668 cairo_path = &path->priv->cairo_path;
669 if (cairo_path->data != NULL)
670 return cairo_path;
672 src = path->priv->path;
673 dst = g_array_sized_new(FALSE, FALSE, sizeof(cairo_path_data_t), src->len);
675 /* Cycle the path and convert arcs to Bézier curves */
676 for (i = 0; i < src->len; i += p_src->header.length) {
677 p_src = (const cairo_path_data_t *) src->data + i;
679 if (p_src->header.type == CAIRO_PATH_ARC_TO)
680 dst = arc_to_curves(dst, p_src);
681 else
682 dst = g_array_append_vals(dst, p_src, p_src->header.length);
685 cairo_path->status = CAIRO_STATUS_SUCCESS;
686 cairo_path->num_data = dst->len;
687 cairo_path->data = (cairo_path_data_t *) g_array_free(dst, FALSE);
689 return cairo_path;
692 static cairo_path_t *
693 get_cpml_path(AdgPath *path)
695 cairo_path_t *cpml_path = &path->priv->cpml_path;
697 cpml_path->status = CAIRO_STATUS_SUCCESS;
698 cpml_path->data = (cairo_path_data_t *) path->priv->path->data;
699 cpml_path->num_data = path->priv->path->len;
701 return cpml_path;
704 static GArray *
705 arc_to_curves(GArray *array, const cairo_path_data_t *src)
707 CpmlPrimitive arc;
708 double start, end;
710 /* Build the arc primitive: the arc origin is supposed to be the previous
711 * point (src-1): this means a primitive must exist before the arc */
712 arc.segment = NULL;
713 arc.org = (cairo_path_data_t *) (src-1);
714 arc.data = (cairo_path_data_t *) src;
716 if (cpml_arc_info(&arc, NULL, NULL, &start, &end)) {
717 CpmlSegment segment;
718 int n_curves;
719 cairo_path_data_t *curves;
721 n_curves = ceil(fabs(end-start) / M_PI_2);
722 curves = g_new(cairo_path_data_t, n_curves * 4);
723 segment.data = curves;
724 cpml_arc_to_curves(&arc, &segment, n_curves);
726 array = g_array_append_vals(array, curves, n_curves * 4);
728 g_free(curves);
731 return array;
734 static void
735 append_valist(AdgPath *path, cairo_path_data_type_t type,
736 int length, va_list var_args)
738 AdgPathPrivate *priv;
739 cairo_path_data_t item;
741 priv = path->priv;
743 /* Append the header item */
744 item.header.type = type;
745 item.header.length = length;
746 priv->path = g_array_append_val(priv->path, item);
747 priv->cp_is_valid = FALSE;
749 /* Append the data items (that is, the AdgPair points) */
750 while (--length) {
751 cpml_pair_to_cairo(va_arg(var_args, AdgPair *), &item);
752 priv->path = g_array_append_val(priv->path, item);
753 priv->cp_is_valid = TRUE;
756 /* Save the last point as the current point */
757 if (priv->cp_is_valid)
758 cpml_pair_from_cairo(&priv->cp, &item);
760 /* Invalidate cairo_path: should be recomputed */
761 clear_cairo_path(path);