Updated docs for slots related changes
[adg.git] / adg / adg-path.c
blob17bf27ee27ef9b153742d0ae398a2eea831421ba
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2008, 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 Library 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 * Library 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., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 /**
22 * SECTION:path
23 * @title: AdgPath
24 * @short_description: A stroked path entity
26 * The #AdgPath object is peraphs the simplest entity.
28 * It contains a pointer to the desired #cairo_path_t structure.
31 #include "adg-path.h"
32 #include "adg-path-private.h"
33 #include "adg-line-style.h"
34 #include "adg-util.h"
35 #include "adg-canvas.h"
36 #include "adg-intl.h"
37 #include <math.h>
39 #define PARENT_CLASS ((AdgEntityClass *) adg_path_parent_class)
40 #define ARC_TOLERANCE 0.1
43 enum
45 PROP_0,
46 PROP_LINE_STYLE
49 typedef enum _Direction Direction;
50 enum _Direction
52 DIRECTION_FORWARD,
53 DIRECTION_REVERSE
57 static void get_property (GObject *object,
58 guint prop_id,
59 GValue *value,
60 GParamSpec *pspec);
61 static void set_property (GObject *object,
62 guint prop_id,
63 const GValue *value,
64 GParamSpec *pspec);
65 static void finalize (GObject *object);
66 static const AdgLineStyle *
67 get_line_style (AdgEntity *entity);
68 static void set_line_style (AdgEntity *entity,
69 AdgLineStyle *line_style);
70 static void render (AdgEntity *entity,
71 cairo_t *cr);
72 static void add_portion (AdgPath *path,
73 cairo_path_data_type_t type,
74 ...);
75 /* Adapted from cairo-1.3.8 */
76 static double arc_error_normalized (double angle);
77 static double arc_max_angle_for_tolerance_normalized
78 (double tolerance);
79 static int arc_segments_needed (double angle,
80 double radius,
81 double tolerance);
82 static void arc_segment (AdgPath *path,
83 double xc,
84 double yc,
85 double radius,
86 double angle_A,
87 double angle_B);
88 static void arc_in_direction (AdgPath *path,
89 double xc,
90 double yc,
91 double radius,
92 double angle_min,
93 double angle_max,
94 Direction dir);
97 G_DEFINE_TYPE (AdgPath, adg_path, ADG_TYPE_ENTITY);
100 static void
101 adg_path_class_init (AdgPathClass *klass)
103 GObjectClass *gobject_class;
104 AdgEntityClass *entity_class;
105 GParamSpec *param;
107 gobject_class = (GObjectClass *) klass;
108 entity_class = (AdgEntityClass *) klass;
110 g_type_class_add_private (klass, sizeof (AdgPathPrivate));
112 gobject_class->get_property = get_property;
113 gobject_class->set_property = set_property;
114 gobject_class->finalize = finalize;
116 entity_class->get_line_style = get_line_style;
117 entity_class->set_line_style = set_line_style;
118 entity_class->render = render;
120 param = g_param_spec_boxed ("line-style",
121 P_("Line Style"),
122 P_("Line style to use while rendering the path"),
123 ADG_TYPE_LINE_STYLE,
124 G_PARAM_READWRITE);
125 g_object_class_install_property (gobject_class, PROP_LINE_STYLE, param);
128 static void
129 adg_path_init (AdgPath *path)
131 AdgPathPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (path, ADG_TYPE_PATH,
132 AdgPathPrivate);
134 priv->line_style = NULL;
135 priv->cairo_path.status = CAIRO_STATUS_SUCCESS;
136 priv->cairo_path.data = NULL;
137 priv->cairo_path.num_data = 0;
138 priv->cp.x = 0.;
139 priv->cp.y = 0.;
140 priv->create_func = NULL;
141 priv->user_data = NULL;
143 path->priv = priv;
146 static void
147 get_property (GObject *object,
148 guint prop_id,
149 GValue *value,
150 GParamSpec *pspec)
152 AdgPathPrivate *priv = ((AdgPath *) object)->priv;
154 switch (prop_id)
156 case PROP_LINE_STYLE:
157 g_value_set_boxed (value, priv->line_style);
158 break;
159 default:
160 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
161 break;
165 static void
166 set_property (GObject *object,
167 guint prop_id,
168 const GValue *value,
169 GParamSpec *pspec)
171 AdgPath *path = (AdgPath *) object;
173 switch (prop_id)
175 case PROP_LINE_STYLE:
176 path->priv->line_style = g_value_get_boxed (value);
177 break;
178 default:
179 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
180 break;
185 static void
186 finalize (GObject *object)
188 adg_path_clear ((AdgPath *) object);
190 ((GObjectClass *) PARENT_CLASS)->finalize (object);
194 static const AdgLineStyle *
195 get_line_style (AdgEntity *entity)
197 return ((AdgPath *) entity)->priv->line_style;
200 static void
201 set_line_style (AdgEntity *entity,
202 AdgLineStyle *line_style)
204 ((AdgPath *) entity)->priv->line_style = line_style;
205 g_object_notify (G_OBJECT (entity), "line-style");
208 static void
209 render (AdgEntity *entity,
210 cairo_t *cr)
212 AdgPath *path = (AdgPath *) entity;
214 if (!adg_entity_model_applied (entity) && path->priv->create_func != NULL)
215 path->priv->create_func (entity, path->priv->user_data);
217 adg_entity_apply (entity, ADG_SLOT_LINE_STYLE, cr);
218 cairo_append_path (cr, &path->priv->cairo_path);
219 cairo_stroke (cr);
221 PARENT_CLASS->render (entity, cr);
224 static void
225 add_portion (AdgPath *path,
226 cairo_path_data_type_t type,
227 ...)
229 AdgPathPrivate *priv = path->priv;
230 cairo_path_data_t portion;
231 va_list var_args;
233 if (!priv->portions)
234 priv->portions = g_array_sized_new (FALSE, FALSE,
235 sizeof (cairo_path_data_t),
236 10 /* Preallocated elements */);
238 portion.header.type = type;
239 va_start (var_args, type);
241 switch (type)
243 case CAIRO_PATH_CLOSE_PATH:
244 portion.header.length = 1;
245 priv->portions = g_array_append_val (priv->portions, portion);
246 portion.point.x = 0.;
247 portion.point.y = 0.;
248 break;
250 case CAIRO_PATH_MOVE_TO:
251 portion.header.length = 2;
252 priv->portions = g_array_append_val (priv->portions, portion);
253 portion.point.x = va_arg (var_args, double);
254 portion.point.y = va_arg (var_args, double);
255 priv->portions = g_array_append_val (priv->portions, portion);
256 break;
258 case CAIRO_PATH_LINE_TO:
259 portion.header.length = 2;
260 priv->portions = g_array_append_val (priv->portions, portion);
261 portion.point.x = va_arg (var_args, double);
262 portion.point.y = va_arg (var_args, double);
263 priv->portions = g_array_append_val (priv->portions, portion);
264 break;
266 case CAIRO_PATH_CURVE_TO:
267 portion.header.length = 4;
268 priv->portions = g_array_append_val (priv->portions, portion);
269 portion.point.x = va_arg (var_args, double);
270 portion.point.y = va_arg (var_args, double);
271 priv->portions = g_array_append_val (priv->portions, portion);
272 portion.point.x = va_arg (var_args, double);
273 portion.point.y = va_arg (var_args, double);
274 priv->portions = g_array_append_val (priv->portions, portion);
275 portion.point.x = va_arg (var_args, double);
276 portion.point.y = va_arg (var_args, double);
277 priv->portions = g_array_append_val (priv->portions, portion);
278 break;
280 default:
281 g_assert_not_reached ();
284 priv->cairo_path.data = (cairo_path_data_t *) priv->portions->data;
285 priv->cairo_path.num_data = priv->portions->len;
286 priv->cairo_path.status = CAIRO_STATUS_SUCCESS;
288 priv->cp.x = portion.point.x;
289 priv->cp.y = portion.point.y;
294 * adg_path_new:
295 * @create_func: an #AdgCallback callback
296 * @user_data: user pointer
298 * Creates a new path entity using @create_func as creation callback.
300 * Return value: the new entity
302 AdgEntity *
303 adg_path_new (AdgCallback create_func,
304 gpointer user_data)
306 AdgEntity *entity;
307 AdgPathPrivate *priv;
309 entity = (AdgEntity *) g_object_new (ADG_TYPE_PATH, NULL);
310 priv = ((AdgPath *) entity)->priv;
312 priv->create_func = create_func;
313 priv->user_data = user_data;
315 return entity;
318 void
319 adg_path_clear (AdgPath *path)
321 AdgPathPrivate *priv;
323 g_return_if_fail (ADG_IS_PATH (path));
325 priv = path->priv;
327 g_array_free (priv->portions, TRUE);
328 priv->portions = NULL;
329 priv->cairo_path.status = CAIRO_STATUS_SUCCESS;
330 priv->cairo_path.data = NULL;
331 priv->cairo_path.num_data = 0;
332 priv->cp.x = 0.;
333 priv->cp.y = 0.;
337 const cairo_path_t *
338 adg_path_get_cairo_path (AdgPath *path)
340 g_return_val_if_fail (ADG_IS_PATH (path), NULL);
342 return &path->priv->cairo_path;
346 void
347 adg_path_chain_ymirror (AdgPath *path)
349 AdgPathPrivate *priv;
350 cairo_path_t *cairo_path;
351 cairo_path_data_t *src;
352 cairo_path_data_t *p_src, *p_dst;
353 gint length;
354 gint n_data, n_point;
355 double last_x, last_y;
357 g_return_if_fail (ADG_IS_PATH (path));
359 priv = path->priv;
360 cairo_path = &priv->cairo_path;
362 g_return_if_fail (cairo_path->num_data > 2);
363 g_return_if_fail (cairo_path->data->header.type == CAIRO_PATH_MOVE_TO);
365 src = g_memdup (cairo_path->data, cairo_path->num_data * sizeof (cairo_path_data_t));
366 priv->portions = g_array_set_size (priv->portions, cairo_path->num_data * 2);
367 p_src = src;
368 p_dst = (cairo_path_data_t *) priv->portions->data + cairo_path->num_data * 2;
369 n_data = 2;
371 ++ p_src;
372 last_x = p_src->point.x;
373 last_y = p_src->point.y;
374 priv->cp.x = last_x;
375 priv->cp.y = last_y;
376 ++ p_src;
378 while (n_data < cairo_path->num_data)
380 length = p_src->header.length;
381 p_dst -= length;
382 p_dst->header.type = p_src->header.type;
383 p_dst->header.length = length;
384 ++ p_src;
386 for (n_point = 1; n_point < length - 1; ++ n_point)
388 p_dst[length-n_point-1].point.x = p_src->point.x;
389 p_dst[length-n_point-1].point.y = -p_src->point.y;
390 ++ p_src;
393 p_dst[length-1].point.x = last_x;
394 p_dst[length-1].point.y = -last_y;
395 last_x = p_src->point.x;
396 last_y = p_src->point.y;
397 ++ p_src;
398 n_data += length;
401 p_dst -= 2;
402 p_dst->header.type = CAIRO_PATH_LINE_TO;
403 p_dst->header.length = 2;
404 ++ p_dst;
405 p_dst->point.x = last_x;
406 p_dst->point.y = -last_y;
408 g_free (src);
410 cairo_path->num_data = cairo_path->num_data * 2;
411 cairo_path->data = (cairo_path_data_t *) priv->portions->data;
414 void
415 adg_path_dump (AdgPath *path)
417 cairo_path_data_t *data;
418 gint n_data, n_point;
420 g_return_if_fail (ADG_IS_PATH (path));
422 for (n_data = 0; n_data < path->priv->cairo_path.num_data; ++ n_data)
424 data = path->priv->cairo_path.data + n_data;
426 switch (data->header.type)
428 case CAIRO_PATH_MOVE_TO:
429 g_print ("Move to ");
430 break;
431 case CAIRO_PATH_LINE_TO:
432 g_print ("Line to ");
433 break;
434 case CAIRO_PATH_CURVE_TO:
435 g_print ("Curve to ");
436 break;
437 case CAIRO_PATH_CLOSE_PATH:
438 g_print ("Path close");
439 break;
440 default:
441 g_print ("Unknown entity (%d)", data->header.type);
442 break;
445 for (n_point = 1; n_point < data->header.length; ++ n_point)
446 g_print ("(%lf, %lf) ", data[n_point].point.x, data[n_point].point.y);
448 n_data += n_point-1;
449 g_print ("\n");
454 /* Cairo wrappers */
456 gboolean
457 adg_path_get_current_point (AdgPath *path,
458 double *x,
459 double *y)
461 AdgPathPrivate *priv;
463 g_return_val_if_fail (ADG_IS_PATH (path), FALSE);
465 priv = path->priv;
467 if (x != NULL)
468 *x = priv->cp.x;
470 if (y != NULL)
471 *y = priv->cp.y;
473 return TRUE;
476 void
477 adg_path_close (AdgPath *path)
479 g_return_if_fail (ADG_IS_PATH (path));
481 add_portion (path, CAIRO_PATH_CLOSE_PATH);
484 void
485 adg_path_arc (AdgPath *path,
486 double x,
487 double y,
488 double radius,
489 double angle1,
490 double angle2)
492 g_return_if_fail (ADG_IS_PATH (path));
493 g_return_if_fail (radius > 0.0);
495 while (angle2 < angle1)
496 angle2 += 2 * G_PI;
498 adg_path_line_to (path, x + radius * cos (angle1), y + radius * sin (angle1));
499 arc_in_direction (path, x, y, radius, angle1, angle2, DIRECTION_FORWARD);
502 void
503 adg_path_arc_negative (AdgPath *path,
504 double x,
505 double y,
506 double radius,
507 double angle1,
508 double angle2)
510 g_return_if_fail (ADG_IS_PATH (path));
511 g_return_if_fail (radius > 0.0);
513 while (angle2 > angle1)
514 angle2 -= 2 * G_PI;
516 adg_path_line_to (path, x + radius * cos (angle1), y + radius * sin (angle1));
517 arc_in_direction (path, x, y, radius, angle2, angle1, DIRECTION_REVERSE);
520 void
521 adg_path_curve_to (AdgPath *path,
522 double x1,
523 double y1,
524 double x2,
525 double y2,
526 double x3,
527 double y3)
529 g_return_if_fail (ADG_IS_PATH (path));
531 add_portion (path, CAIRO_PATH_CURVE_TO, x1, y1, x2, y2, x3, y3);
534 void
535 adg_path_line_to (AdgPath *path,
536 double x,
537 double y)
539 g_return_if_fail (ADG_IS_PATH (path));
541 add_portion (path, CAIRO_PATH_LINE_TO, x, y);
544 void
545 adg_path_move_to (AdgPath *path,
546 double x,
547 double y)
549 g_return_if_fail (ADG_IS_PATH (path));
551 add_portion (path, CAIRO_PATH_MOVE_TO, x, y);
554 void
555 adg_path_rectangle (AdgPath *path,
556 double x,
557 double y,
558 double width,
559 double height)
561 g_return_if_fail (ADG_IS_PATH (path));
563 adg_path_move_to (path, x, y);
564 adg_path_rel_line_to (path, width, 0);
565 adg_path_rel_line_to (path, 0, height);
566 adg_path_rel_line_to (path, -width, 0);
567 adg_path_close (path);
570 void
571 adg_path_rel_curve_to (AdgPath *path,
572 double dx1,
573 double dy1,
574 double dx2,
575 double dy2,
576 double dx3,
577 double dy3)
579 double x, y;
581 g_return_if_fail (ADG_IS_PATH (path));
582 g_return_if_fail (adg_path_get_current_point (path, &x, &y));
584 adg_path_curve_to (path, x+dx1, y+dy1, x+dx2, y+dy2, x+dx3, y+dy3);
587 void
588 adg_path_rel_line_to (AdgPath *path,
589 double dx,
590 double dy)
592 double x, y;
594 g_return_if_fail (ADG_IS_PATH (path));
595 g_return_if_fail (adg_path_get_current_point (path, &x, &y));
597 adg_path_line_to (path, x+dx, y+dy);
600 void
601 adg_path_rel_move_to (AdgPath *path,
602 double dx,
603 double dy)
605 double x, y;
607 g_return_if_fail (ADG_IS_PATH (path));
608 g_return_if_fail (adg_path_get_current_point (path, &x, &y));
610 adg_path_move_to (path, x+dx, y+dy);
614 /* The following code is adapted from cairo-1.3.8 */
616 static double
617 arc_error_normalized (double angle)
619 return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
622 static double
623 arc_max_angle_for_tolerance_normalized (double tolerance)
625 double angle, error;
626 int i;
628 /* Use table lookup to reduce search time in most cases. */
629 struct
631 double angle;
632 double error;
633 } table[] = {
634 { M_PI / 1.0, 0.0185185185185185036127 },
635 { M_PI / 2.0, 0.000272567143730179811158 },
636 { M_PI / 3.0, 2.38647043651461047433e-05 },
637 { M_PI / 4.0, 4.2455377443222443279e-06 },
638 { M_PI / 5.0, 1.11281001494389081528e-06 },
639 { M_PI / 6.0, 3.72662000942734705475e-07 },
640 { M_PI / 7.0, 1.47783685574284411325e-07 },
641 { M_PI / 8.0, 6.63240432022601149057e-08 },
642 { M_PI / 9.0, 3.2715520137536980553e-08 },
643 { M_PI / 10.0, 1.73863223499021216974e-08 },
644 { M_PI / 11.0, 9.81410988043554039085e-09 },
646 int table_size = (sizeof (table) / sizeof (table[0]));
648 for (i = 0; i < table_size; i++)
649 if (table[i].error < tolerance)
650 return table[i].angle;
652 ++i;
655 angle = M_PI / i++;
656 error = arc_error_normalized (angle);
658 while (error > tolerance);
660 return angle;
663 /* XXX: 22-12-2006 Fontana Nicola <ntd at entidi.it>
664 * Removed the ctm parameter and the calculation of the major axis: I use
665 * the radius directly, instead. Hopefully, this will break only the
666 * calculations for ellipses ...
668 static int
669 arc_segments_needed (double angle,
670 double radius,
671 double tolerance)
673 double max_angle;
675 max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
677 return (int) ceil (angle / max_angle);
680 static void
681 arc_segment (AdgPath *path,
682 double xc,
683 double yc,
684 double radius,
685 double angle_A,
686 double angle_B)
688 double r_sin_A, r_cos_A;
689 double r_sin_B, r_cos_B;
690 double h;
692 r_sin_A = radius * sin (angle_A);
693 r_cos_A = radius * cos (angle_A);
694 r_sin_B = radius * sin (angle_B);
695 r_cos_B = radius * cos (angle_B);
697 h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
699 adg_path_curve_to (path,
700 xc + r_cos_A - h * r_sin_A,
701 yc + r_sin_A + h * r_cos_A,
702 xc + r_cos_B + h * r_sin_B,
703 yc + r_sin_B - h * r_cos_B,
704 xc + r_cos_B,
705 yc + r_sin_B);
708 static void
709 arc_in_direction (AdgPath *path,
710 double xc,
711 double yc,
712 double radius,
713 double angle_min,
714 double angle_max,
715 Direction dir)
717 while (angle_max - angle_min > 4 * M_PI)
718 angle_max -= 2 * M_PI;
720 /* Recurse if drawing arc larger than pi */
721 if (angle_max - angle_min > M_PI)
723 double angle_mid = angle_min + (angle_max - angle_min) / 2.0;
724 /* XXX: Something tells me this block could be condensed. */
725 if (dir == DIRECTION_FORWARD)
727 arc_in_direction (path, xc, yc, radius,
728 angle_min, angle_mid,
729 dir);
731 arc_in_direction (path, xc, yc, radius,
732 angle_mid, angle_max,
733 dir);
735 else
737 arc_in_direction (path, xc, yc, radius,
738 angle_mid, angle_max,
739 dir);
741 arc_in_direction (path, xc, yc, radius,
742 angle_min, angle_mid,
743 dir);
746 else
748 int i, segments;
749 double angle, angle_step;
751 /* XXX: 22-12-2006 Fontana Nicola <ntd at entidi.it>
752 * Used the ARC_TOLERANCE constant instead of the cairo context
753 * dependent variable, because I do not have any cairo context here.
755 segments = arc_segments_needed (angle_max - angle_min, radius, ARC_TOLERANCE);
756 angle_step = (angle_max - angle_min) / (double) segments;
758 if (dir == DIRECTION_FORWARD)
760 angle = angle_min;
762 else
764 angle = angle_max;
765 angle_step = - angle_step;
768 for (i = 0; i < segments; i++, angle += angle_step)
769 arc_segment (path, xc, yc, radius, angle, angle + angle_step);