Updated "GNU Library General License" to "GNU Lesser General License
[adg.git] / adg / adg-path.c
blob5f74fd8e8f19a74ecc0cd28f9bc5d352f2cf21c9
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 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.
20 /**
21 * SECTION:path
22 * @title: AdgPath
23 * @short_description: A stroked path entity
25 * The #AdgPath object is peraphs the simplest entity.
27 * It contains a pointer to the desired #cairo_path_t structure.
30 #include "adg-path.h"
31 #include "adg-path-private.h"
32 #include "adg-line-style.h"
33 #include "adg-util.h"
34 #include "adg-canvas.h"
35 #include "adg-intl.h"
36 #include <math.h>
38 #define PARENT_CLASS ((AdgEntityClass *) adg_path_parent_class)
39 #define ARC_TOLERANCE 0.1
42 typedef enum _Direction Direction;
43 enum _Direction {
44 DIRECTION_FORWARD,
45 DIRECTION_REVERSE
49 static void finalize (GObject *object);
50 static void render (AdgEntity *entity,
51 cairo_t *cr);
52 static void add_portion (AdgPath *path,
53 cairo_path_data_type_t type,
54 ...);
55 /* Adapted from cairo-1.3.8 */
56 static double arc_error_normalized (double angle);
57 static double arc_max_angle_for_tolerance_normalized
58 (double tolerance);
59 static int arc_segments_needed (double angle,
60 double radius,
61 double tolerance);
62 static void arc_segment (AdgPath *path,
63 double xc,
64 double yc,
65 double radius,
66 double angle_A,
67 double angle_B);
68 static void arc_in_direction (AdgPath *path,
69 double xc,
70 double yc,
71 double radius,
72 double angle_min,
73 double angle_max,
74 Direction dir);
77 G_DEFINE_TYPE(AdgPath, adg_path, ADG_TYPE_ENTITY);
80 static void
81 adg_path_class_init(AdgPathClass *klass)
83 GObjectClass *gobject_class;
84 AdgEntityClass *entity_class;
86 gobject_class = (GObjectClass *) klass;
87 entity_class = (AdgEntityClass *) klass;
89 g_type_class_add_private(klass, sizeof(AdgPathPrivate));
91 gobject_class->finalize = finalize;
93 entity_class->render = render;
96 static void
97 adg_path_init(AdgPath *path)
99 AdgPathPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(path, ADG_TYPE_PATH,
100 AdgPathPrivate);
102 priv->cairo_path.status = CAIRO_STATUS_SUCCESS;
103 priv->cairo_path.data = NULL;
104 priv->cairo_path.num_data = 0;
105 priv->cp.x = 0.;
106 priv->cp.y = 0.;
107 priv->create_func = NULL;
108 priv->user_data = NULL;
110 path->priv = priv;
113 static void
114 finalize(GObject *object)
116 adg_path_clear((AdgPath *) object);
118 ((GObjectClass *) PARENT_CLASS)->finalize(object);
121 static void
122 render(AdgEntity *entity, cairo_t *cr)
124 AdgPath *path = (AdgPath *) entity;
126 if (!adg_entity_model_applied(entity)
127 && path->priv->create_func != NULL)
128 path->priv->create_func(entity, path->priv->user_data);
130 adg_entity_apply(entity, ADG_SLOT_LINE_STYLE, cr);
131 cairo_append_path(cr, &path->priv->cairo_path);
132 cairo_stroke(cr);
134 PARENT_CLASS->render(entity, cr);
137 static void
138 add_portion(AdgPath *path, cairo_path_data_type_t type, ...)
140 AdgPathPrivate *priv = path->priv;
141 cairo_path_data_t portion;
142 va_list var_args;
144 if (!priv->portions)
145 priv->portions = g_array_sized_new(FALSE, FALSE,
146 sizeof(cairo_path_data_t), 10);
148 portion.header.type = type;
149 va_start(var_args, type);
151 switch (type) {
152 case CAIRO_PATH_CLOSE_PATH:
153 portion.header.length = 1;
154 priv->portions = g_array_append_val(priv->portions, portion);
155 portion.point.x = 0.;
156 portion.point.y = 0.;
157 break;
159 case CAIRO_PATH_MOVE_TO:
160 portion.header.length = 2;
161 priv->portions = g_array_append_val(priv->portions, portion);
162 portion.point.x = va_arg(var_args, double);
163 portion.point.y = va_arg(var_args, double);
164 priv->portions = g_array_append_val(priv->portions, portion);
165 break;
167 case CAIRO_PATH_LINE_TO:
168 portion.header.length = 2;
169 priv->portions = g_array_append_val(priv->portions, portion);
170 portion.point.x = va_arg(var_args, double);
171 portion.point.y = va_arg(var_args, double);
172 priv->portions = g_array_append_val(priv->portions, portion);
173 break;
175 case CAIRO_PATH_CURVE_TO:
176 portion.header.length = 4;
177 priv->portions = g_array_append_val(priv->portions, portion);
178 portion.point.x = va_arg(var_args, double);
179 portion.point.y = va_arg(var_args, double);
180 priv->portions = g_array_append_val(priv->portions, portion);
181 portion.point.x = va_arg(var_args, double);
182 portion.point.y = va_arg(var_args, double);
183 priv->portions = g_array_append_val(priv->portions, portion);
184 portion.point.x = va_arg(var_args, double);
185 portion.point.y = va_arg(var_args, double);
186 priv->portions = g_array_append_val(priv->portions, portion);
187 break;
189 default:
190 g_assert_not_reached();
193 priv->cairo_path.data = (cairo_path_data_t *) priv->portions->data;
194 priv->cairo_path.num_data = priv->portions->len;
195 priv->cairo_path.status = CAIRO_STATUS_SUCCESS;
197 priv->cp.x = portion.point.x;
198 priv->cp.y = portion.point.y;
203 * adg_path_new:
204 * @create_func: an #AdgCallback callback
205 * @user_data: user pointer
207 * Creates a new path entity using @create_func as creation callback.
209 * Return value: the new entity
211 AdgEntity *
212 adg_path_new(AdgCallback create_func, gpointer user_data)
214 AdgEntity *entity;
215 AdgPathPrivate *priv;
217 entity = (AdgEntity *) g_object_new(ADG_TYPE_PATH, NULL);
218 priv = ((AdgPath *) entity)->priv;
220 priv->create_func = create_func;
221 priv->user_data = user_data;
223 return entity;
226 void
227 adg_path_clear(AdgPath *path)
229 AdgPathPrivate *priv;
231 g_return_if_fail(ADG_IS_PATH(path));
233 priv = path->priv;
235 g_array_free(priv->portions, TRUE);
236 priv->portions = NULL;
237 priv->cairo_path.status = CAIRO_STATUS_SUCCESS;
238 priv->cairo_path.data = NULL;
239 priv->cairo_path.num_data = 0;
240 priv->cp.x = 0.;
241 priv->cp.y = 0.;
245 const cairo_path_t *
246 adg_path_get_cairo_path(AdgPath *path)
248 g_return_val_if_fail(ADG_IS_PATH(path), NULL);
250 return &path->priv->cairo_path;
254 void
255 adg_path_chain_ymirror(AdgPath *path)
257 AdgPathPrivate *priv;
258 cairo_path_t *cairo_path;
259 cairo_path_data_t *src;
260 cairo_path_data_t *p_src, *p_dst;
261 gint length;
262 gint n_data, n_point;
263 double last_x, last_y;
265 g_return_if_fail(ADG_IS_PATH(path));
267 priv = path->priv;
268 cairo_path = &priv->cairo_path;
270 g_return_if_fail(cairo_path->num_data > 2);
271 g_return_if_fail(cairo_path->data->header.type == CAIRO_PATH_MOVE_TO);
273 src =
274 g_memdup(cairo_path->data,
275 cairo_path->num_data * sizeof(cairo_path_data_t));
276 priv->portions =
277 g_array_set_size(priv->portions, cairo_path->num_data * 2);
278 p_src = src;
279 p_dst =
280 (cairo_path_data_t *) priv->portions->data +
281 cairo_path->num_data * 2;
282 n_data = 2;
284 ++p_src;
285 last_x = p_src->point.x;
286 last_y = p_src->point.y;
287 priv->cp.x = last_x;
288 priv->cp.y = last_y;
289 ++p_src;
291 while (n_data < cairo_path->num_data) {
292 length = p_src->header.length;
293 p_dst -= length;
294 p_dst->header.type = p_src->header.type;
295 p_dst->header.length = length;
296 ++p_src;
298 for (n_point = 1; n_point < length - 1; ++n_point) {
299 p_dst[length - n_point - 1].point.x = p_src->point.x;
300 p_dst[length - n_point - 1].point.y = -p_src->point.y;
301 ++p_src;
304 p_dst[length - 1].point.x = last_x;
305 p_dst[length - 1].point.y = -last_y;
306 last_x = p_src->point.x;
307 last_y = p_src->point.y;
308 ++p_src;
309 n_data += length;
312 p_dst -= 2;
313 p_dst->header.type = CAIRO_PATH_LINE_TO;
314 p_dst->header.length = 2;
315 ++p_dst;
316 p_dst->point.x = last_x;
317 p_dst->point.y = -last_y;
319 g_free(src);
321 cairo_path->num_data = cairo_path->num_data * 2;
322 cairo_path->data = (cairo_path_data_t *) priv->portions->data;
325 void
326 adg_path_dump(AdgPath *path)
328 cairo_path_data_t *data;
329 gint n_data, n_point;
331 g_return_if_fail(ADG_IS_PATH(path));
333 for (n_data = 0; n_data < path->priv->cairo_path.num_data; ++n_data) {
334 data = path->priv->cairo_path.data + n_data;
336 switch (data->header.type) {
337 case CAIRO_PATH_MOVE_TO:
338 g_print("Move to ");
339 break;
340 case CAIRO_PATH_LINE_TO:
341 g_print("Line to ");
342 break;
343 case CAIRO_PATH_CURVE_TO:
344 g_print("Curve to ");
345 break;
346 case CAIRO_PATH_CLOSE_PATH:
347 g_print("Path close");
348 break;
349 default:
350 g_print("Unknown entity (%d)", data->header.type);
351 break;
354 for (n_point = 1; n_point < data->header.length; ++n_point)
355 g_print("(%lf, %lf) ", data[n_point].point.x,
356 data[n_point].point.y);
358 n_data += n_point - 1;
359 g_print("\n");
364 /* Cairo wrappers */
366 gboolean
367 adg_path_get_current_point(AdgPath *path, double *x, double *y)
369 AdgPathPrivate *priv;
371 g_return_val_if_fail(ADG_IS_PATH(path), FALSE);
373 priv = path->priv;
375 if (x != NULL)
376 *x = priv->cp.x;
378 if (y != NULL)
379 *y = priv->cp.y;
381 return TRUE;
384 void
385 adg_path_close(AdgPath *path)
387 g_return_if_fail(ADG_IS_PATH(path));
389 add_portion(path, CAIRO_PATH_CLOSE_PATH);
392 void
393 adg_path_arc(AdgPath *path,
394 double x,
395 double y, double radius, double angle1, double angle2)
397 g_return_if_fail(ADG_IS_PATH(path));
398 g_return_if_fail(radius > 0.0);
400 while (angle2 < angle1)
401 angle2 += 2 * G_PI;
403 adg_path_line_to(path, x + radius * cos(angle1),
404 y + radius * sin(angle1));
405 arc_in_direction(path, x, y, radius, angle1, angle2,
406 DIRECTION_FORWARD);
409 void
410 adg_path_arc_negative(AdgPath *path,
411 double x,
412 double y,
413 double radius, double angle1, double angle2)
415 g_return_if_fail(ADG_IS_PATH(path));
416 g_return_if_fail(radius > 0.0);
418 while (angle2 > angle1)
419 angle2 -= 2 * G_PI;
421 adg_path_line_to(path, x + radius * cos(angle1),
422 y + radius * sin(angle1));
423 arc_in_direction(path, x, y, radius, angle2, angle1,
424 DIRECTION_REVERSE);
427 void
428 adg_path_curve_to(AdgPath *path,
429 double x1,
430 double y1, double x2, double y2, double x3, double y3)
432 g_return_if_fail(ADG_IS_PATH(path));
434 add_portion(path, CAIRO_PATH_CURVE_TO, x1, y1, x2, y2, x3, y3);
437 void
438 adg_path_line_to(AdgPath *path, double x, double y)
440 g_return_if_fail(ADG_IS_PATH(path));
442 add_portion(path, CAIRO_PATH_LINE_TO, x, y);
445 void
446 adg_path_move_to(AdgPath *path, double x, double y)
448 g_return_if_fail(ADG_IS_PATH(path));
450 add_portion(path, CAIRO_PATH_MOVE_TO, x, y);
453 void
454 adg_path_rectangle(AdgPath *path,
455 double x, double y, double width, double height)
457 g_return_if_fail(ADG_IS_PATH(path));
459 adg_path_move_to(path, x, y);
460 adg_path_rel_line_to(path, width, 0);
461 adg_path_rel_line_to(path, 0, height);
462 adg_path_rel_line_to(path, -width, 0);
463 adg_path_close(path);
466 void
467 adg_path_rel_curve_to(AdgPath *path,
468 double dx1,
469 double dy1,
470 double dx2, double dy2, double dx3, double dy3)
472 double x, y;
474 g_return_if_fail(ADG_IS_PATH(path));
475 g_return_if_fail(adg_path_get_current_point(path, &x, &y));
477 adg_path_curve_to(path, x + dx1, y + dy1, x + dx2, y + dy2, x + dx3,
478 y + dy3);
481 void
482 adg_path_rel_line_to(AdgPath *path, double dx, double dy)
484 double x, y;
486 g_return_if_fail(ADG_IS_PATH(path));
487 g_return_if_fail(adg_path_get_current_point(path, &x, &y));
489 adg_path_line_to(path, x + dx, y + dy);
492 void
493 adg_path_rel_move_to(AdgPath *path, double dx, double dy)
495 double x, y;
497 g_return_if_fail(ADG_IS_PATH(path));
498 g_return_if_fail(adg_path_get_current_point(path, &x, &y));
500 adg_path_move_to(path, x + dx, y + dy);
504 /* The following code is adapted from cairo-1.3.8 */
506 static double
507 arc_error_normalized(double angle)
509 return 2.0 / 27.0 * pow(sin(angle / 4), 6) / pow(cos(angle / 4), 2);
512 static double
513 arc_max_angle_for_tolerance_normalized(double tolerance)
515 double angle, error;
516 int i;
518 /* Use table lookup to reduce search time in most cases. */
519 struct {
520 double angle;
521 double error;
522 } table[] = {
524 M_PI / 1.0, 0.0185185185185185036127}, {
525 M_PI / 2.0, 0.000272567143730179811158}, {
526 M_PI / 3.0, 2.38647043651461047433e-05}, {
527 M_PI / 4.0, 4.2455377443222443279e-06}, {
528 M_PI / 5.0, 1.11281001494389081528e-06}, {
529 M_PI / 6.0, 3.72662000942734705475e-07}, {
530 M_PI / 7.0, 1.47783685574284411325e-07}, {
531 M_PI / 8.0, 6.63240432022601149057e-08}, {
532 M_PI / 9.0, 3.2715520137536980553e-08}, {
533 M_PI / 10.0, 1.73863223499021216974e-08}, {
534 M_PI / 11.0, 9.81410988043554039085e-09},};
535 int table_size = (sizeof(table) / sizeof(table[0]));
537 for (i = 0; i < table_size; i++)
538 if (table[i].error < tolerance)
539 return table[i].angle;
541 ++i;
542 do {
543 angle = M_PI / i++;
544 error = arc_error_normalized(angle);
546 while (error > tolerance);
548 return angle;
551 /* XXX: 22-12-2006 Fontana Nicola <ntd at entidi.it>
552 * Removed the ctm parameter and the calculation of the major axis: I use
553 * the radius directly, instead. Hopefully, this will break only the
554 * calculations for ellipses ...
556 static int
557 arc_segments_needed(double angle, double radius, double tolerance)
559 double max_angle;
561 max_angle = arc_max_angle_for_tolerance_normalized(tolerance / radius);
563 return (int) ceil(angle / max_angle);
566 static void
567 arc_segment(AdgPath *path,
568 double xc,
569 double yc, double radius, double angle_A, double angle_B)
571 double r_sin_A, r_cos_A;
572 double r_sin_B, r_cos_B;
573 double h;
575 r_sin_A = radius * sin(angle_A);
576 r_cos_A = radius * cos(angle_A);
577 r_sin_B = radius * sin(angle_B);
578 r_cos_B = radius * cos(angle_B);
580 h = 4.0 / 3.0 * tan((angle_B - angle_A) / 4.0);
582 adg_path_curve_to(path,
583 xc + r_cos_A - h * r_sin_A,
584 yc + r_sin_A + h * r_cos_A,
585 xc + r_cos_B + h * r_sin_B,
586 yc + r_sin_B - h * r_cos_B,
587 xc + r_cos_B, yc + r_sin_B);
590 static void
591 arc_in_direction(AdgPath *path,
592 double xc,
593 double yc,
594 double radius,
595 double angle_min, double angle_max, Direction dir)
597 while (angle_max - angle_min > 4 * M_PI)
598 angle_max -= 2 * M_PI;
600 /* Recurse if drawing arc larger than pi */
601 if (angle_max - angle_min > M_PI) {
602 double angle_mid = angle_min + (angle_max - angle_min) / 2.0;
603 /* XXX: Something tells me this block could be condensed. */
604 if (dir == DIRECTION_FORWARD) {
605 arc_in_direction(path, xc, yc, radius,
606 angle_min, angle_mid, dir);
608 arc_in_direction(path, xc, yc, radius,
609 angle_mid, angle_max, dir);
610 } else {
611 arc_in_direction(path, xc, yc, radius,
612 angle_mid, angle_max, dir);
614 arc_in_direction(path, xc, yc, radius,
615 angle_min, angle_mid, dir);
617 } else {
618 int i, segments;
619 double angle, angle_step;
621 /* XXX: 22-12-2006 Fontana Nicola <ntd at entidi.it>
622 * Used the ARC_TOLERANCE constant instead of the cairo context
623 * dependent variable, because I do not have any cairo context here.
625 segments =
626 arc_segments_needed(angle_max - angle_min, radius,
627 ARC_TOLERANCE);
628 angle_step = (angle_max - angle_min) / (double) segments;
630 if (dir == DIRECTION_FORWARD) {
631 angle = angle_min;
632 } else {
633 angle = angle_max;
634 angle_step = -angle_step;
637 for (i = 0; i < segments; i++, angle += angle_step)
638 arc_segment(path, xc, yc, radius, angle, angle + angle_step);