doc: update copyright line for 2019
[adg.git] / src / adg / adg-trail.c
blob29cff2d0e885db385a38e5973abf376ee4c279b5
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 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:adg-trail
23 * @short_description: A bare model built around cairo path
25 * The #AdgTrail model is a really basic model built around the #cairo_path_t
26 * struct: for a full fledged path model consider using #AdgPath.
28 * A trail is a path model that demands all the implementation details to
29 * the caller: this requires a deep knowledge of the ADG details but
30 * provides a great customization level. It should be used when an
31 * #AdgPath is not enough, such as when a model is subject to change
32 * dynamically and the global and local maps do not suffice to express
33 * this alteration. A typical example is the path used to draw extension
34 * lines and base line of #AdgLDim: every point is subject to different
35 * constrains not expressible with a single affine transformation.
37 * Since: 1.0
38 **/
40 /**
41 * AdgTrail:
43 * All fields are private and should not be used directly.
44 * Use its public methods instead.
46 * Since: 1.0
47 **/
49 /**
50 * AdgTrailClass:
51 * @get_cairo_path: virtual method to get the #cairo_path_t bound to the trail.
53 * The default @get_cairo_path calls the #AdgTrailCallback callback passed
54 * to adg_trail_new() during construction. No caching is performed in
55 * between.
57 * Since: 1.0
58 **/
60 /**
61 * AdgTrailCallback:
62 * @trail: an #AdgTrail
63 * @user_data: the general purpose pointer set by adg_trail_new()
65 * This is the callback used to generate the #cairo_path_t and it is
66 * called directly by adg_trail_cairo_path(). The caller owns
67 * the returned path, that is the finalization of the returned
68 * #cairo_path_t should be made by the caller when appropriate.
70 * Returns: the #cairo_path_t of this trail model
72 * Since: 1.0
73 **/
76 #include "adg-internal.h"
77 #include <math.h>
79 #include "adg-model.h"
81 #include "adg-trail.h"
82 #include "adg-trail-private.h"
85 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_trail_parent_class)
86 #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_trail_parent_class)
88 #define EMPTY_PATH(p) ((p) == NULL || (p)->data == NULL || (p)->num_data <= 0)
90 G_DEFINE_TYPE(AdgTrail, adg_trail, ADG_TYPE_MODEL)
92 enum {
93 PROP_0,
94 PROP_MAX_ANGLE
98 static void _adg_finalize (GObject *object);
99 static void _adg_get_property (GObject *object,
100 guint param_id,
101 GValue *value,
102 GParamSpec *pspec);
103 static void _adg_set_property (GObject *object,
104 guint param_id,
105 const GValue *value,
106 GParamSpec *pspec);
107 static void _adg_clear (AdgModel *model);
108 static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail);
109 static GArray * _adg_arc_to_curves (GArray *array,
110 const cairo_path_data_t *src,
111 gdouble max_angle);
114 static void
115 adg_trail_class_init(AdgTrailClass *klass)
117 GObjectClass *gobject_class;
118 AdgModelClass *model_class;
119 GParamSpec *param;
121 gobject_class = (GObjectClass *) klass;
122 model_class = (AdgModelClass *) klass;
124 g_type_class_add_private(klass, sizeof(AdgTrailPrivate));
126 gobject_class->finalize = _adg_finalize;
127 gobject_class->get_property = _adg_get_property;
128 gobject_class->set_property = _adg_set_property;
130 model_class->clear = _adg_clear;
132 klass->get_cairo_path = _adg_get_cairo_path;
134 param = g_param_spec_double("max-angle",
135 P_("Max Angle"),
136 P_("Max arc angle to approximate with a single Bezier curve: check adg_trail_set_max_angle() for details"),
137 0, G_PI, G_PI_2,
138 G_PARAM_READWRITE);
139 g_object_class_install_property(gobject_class, PROP_MAX_ANGLE, param);
142 static void
143 adg_trail_init(AdgTrail *trail)
145 AdgTrailPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(trail, ADG_TYPE_TRAIL,
146 AdgTrailPrivate);
148 data->cairo_path.status = CAIRO_STATUS_INVALID_PATH_DATA;
149 data->cairo_path.data = NULL;
150 data->cairo_path.num_data = 0;
151 data->callback = NULL;
152 data->user_data = NULL;
153 data->max_angle = G_PI_2;
154 data->in_construction = FALSE;
155 data->extents.is_defined = FALSE;
157 trail->data = data;
160 static void
161 _adg_finalize(GObject *object)
163 _adg_clear((AdgModel *) object);
165 if (_ADG_OLD_OBJECT_CLASS->finalize)
166 _ADG_OLD_OBJECT_CLASS->finalize(object);
169 static void
170 _adg_get_property(GObject *object, guint prop_id,
171 GValue *value, GParamSpec *pspec)
173 AdgTrail *trail;
174 AdgTrailPrivate *data;
176 trail = (AdgTrail *) object;
177 data = trail->data;
179 switch (prop_id) {
180 case PROP_MAX_ANGLE:
181 g_value_set_double(value, data->max_angle);
182 break;
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 break;
189 static void
190 _adg_set_property(GObject *object, guint prop_id,
191 const GValue *value, GParamSpec *pspec)
193 AdgTrail *trail;
194 AdgTrailPrivate *data;
196 trail = (AdgTrail *) object;
197 data = trail->data;
199 switch (prop_id) {
200 case PROP_MAX_ANGLE:
201 data->max_angle = g_value_get_double(value);
202 break;
203 default:
204 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
205 break;
211 * adg_trail_new:
212 * @callback: (scope notified): the #cairo_path_t constructor function
213 * @user_data: generic pointer to pass to the callback
215 * Creates a new trail model. The #cairo_path_t must be constructed by
216 * the @callback function: #AdgTrail will not cache anything, so you
217 * should implement any caching mechanism in the callback, if needed.
219 * Returns: (transfer full): a new trail model.
221 * Since: 1.0
223 AdgTrail *
224 adg_trail_new(AdgTrailCallback callback, gpointer user_data)
226 AdgTrail *trail;
227 AdgTrailPrivate *data;
229 trail = g_object_new(ADG_TYPE_TRAIL, NULL);
230 data = trail->data;
232 data->callback = callback;
233 data->user_data = user_data;
235 return trail;
239 * adg_trail_get_cairo_path:
240 * @trail: an #AdgTrail
242 * Gets a pointer to the cairo path of @trail. The returned path is
243 * owned by @trail and must be considered read-only.
245 * This function gets the #cairo_path_t of @trail by calling
246 * adg_trail_cairo_path() and converts its #CPML_ARC primitives,
247 * not recognized by cairo, into approximated Bézier curves
248 * primitives (#CPML_CURVE). The conversion is cached, so any further
249 * request is O(1). This cache is cleared only by the
250 * adg_model_clear() method.
252 * Returns: (transfer none): a pointer to the internal cairo path or <constant>NULL</constant> on errors.
254 * Since: 1.0
256 const cairo_path_t *
257 adg_trail_get_cairo_path(AdgTrail *trail)
259 AdgTrailPrivate *data;
260 cairo_path_t *cairo_path;
261 GArray *dst;
262 const cairo_path_data_t *p_src;
263 int i;
265 g_return_val_if_fail(ADG_IS_TRAIL(trail), NULL);
267 data = trail->data;
269 /* Check for cached result */
270 if (data->cairo_path.data != NULL)
271 return &data->cairo_path;
273 cairo_path = adg_trail_cairo_path(trail);
274 if (EMPTY_PATH(cairo_path))
275 return NULL;
277 dst = g_array_sized_new(FALSE, FALSE,
278 sizeof(cairo_path_data_t), cairo_path->num_data);
280 /* Cycle the cairo_path_t and convert arcs to Bézier curves */
281 for (i = 0; i < cairo_path->num_data; i += p_src->header.length) {
282 p_src = (const cairo_path_data_t *) cairo_path->data + i;
284 if (p_src->header.type == CPML_ARC)
285 dst = _adg_arc_to_curves(dst, p_src, data->max_angle);
286 else
287 dst = g_array_append_vals(dst, p_src, p_src->header.length);
290 cairo_path = &data->cairo_path;
291 cairo_path->status = CAIRO_STATUS_SUCCESS;
292 cairo_path->num_data = dst->len;
293 cairo_path->data = (cairo_path_data_t *) g_array_free(dst, FALSE);
295 return cairo_path;
299 * adg_trail_cairo_path:
300 * @trail: an #AdgTrail
302 * Gets the cairo path structure defined by @trail. The returned
303 * value is managed by the #AdgTrail implementation, that is this
304 * function directly calls the <function>get_cairo_path</function>
305 * virtual method that any trail instance must have.
307 * Whenever used internally by the ADG project, the returned path
308 * is (by convention) owned by @trail and so it should not be freed.
309 * Anyway, callers are allowed to modify it as long as its size is
310 * retained and its data contains a valid path: this is needed to
311 * let the #AdgMarker infrastructure work properly (the markers
312 * should be able to modify the trail where they are applied).
314 * Any further call to this method will probably make the pointer
315 * previously returned useless because the #cairo_path_t could be
316 * relocated and the old #cairo_path_t will likely contain rubbish.
318 * Returns: (transfer none): a pointer to the #cairo_path_t or <constant>NULL</constant> on errors.
320 * Since: 1.0
322 cairo_path_t *
323 adg_trail_cairo_path(AdgTrail *trail)
325 AdgTrailClass *klass;
326 AdgTrailPrivate *data;
327 cairo_path_t *cairo_path;
329 g_return_val_if_fail(ADG_IS_TRAIL(trail), NULL);
331 klass = ADG_TRAIL_GET_CLASS(trail);
332 if (klass->get_cairo_path == NULL)
333 return NULL;
335 data = trail->data;
336 if (data->in_construction) {
337 g_warning(_("%s: you cannot access the path from the callback you provided to build it"),
338 G_STRLOC);
339 return NULL;
342 data->in_construction = TRUE;
343 cairo_path = klass->get_cairo_path(trail);
344 data->in_construction = FALSE;
346 return cairo_path;
350 * adg_trail_n_segments:
351 * @trail: an #AdgTrail
353 * Convenient function that returns the number of non-empty segments defined
354 * by the cairo path embedded in @trail.
356 * Returns: the number of segments or 0 on errrors.
358 * Since: 1.0
360 guint
361 adg_trail_n_segments(AdgTrail *trail)
363 cairo_path_t *cairo_path;
364 CpmlSegment iterator;
365 guint n;
367 g_return_val_if_fail(ADG_IS_TRAIL(trail), 0);
369 cairo_path = adg_trail_cairo_path(trail);
371 n = 0;
372 if (! EMPTY_PATH(cairo_path) && cpml_segment_from_cairo(&iterator, cairo_path)) {
373 for (n = 1; cpml_segment_next(&iterator); ++n) {
377 return n;
381 * adg_trail_put_segment:
382 * @trail: an #AdgTrail
383 * @n_segment: the segment to retrieve, where 1 is the first segment
384 * @segment: (out): the destination #CpmlSegment
386 * Convenient function to get a segment from @trail. The segment is
387 * got from the cairo path: check out adg_trail_cairo_path() for
388 * further information.
390 * When the segment is not found, either because @n_segment is out
391 * of range or because there is still no path bound to @trail, this
392 * function will return <constant>FALSE</constant> leaving @segment
393 * untouched. If the segment is found and @segment is
394 * not <constant>NULL</constant>, the resulting segment is copied in @segment.
396 * Returns: <constant>TRUE</constant> on success or <constant>FALSE</constant> on errors.
398 * Since: 1.0
400 gboolean
401 adg_trail_put_segment(AdgTrail *trail, guint n_segment, CpmlSegment *segment)
403 cairo_path_t *cairo_path;
404 gboolean found;
405 CpmlSegment iterator;
406 guint cnt;
408 g_return_val_if_fail(ADG_IS_TRAIL(trail), FALSE);
410 if (n_segment == 0) {
411 g_warning(_("%s: requested undefined segment for type '%s'"),
412 G_STRLOC, g_type_name(G_OBJECT_TYPE(trail)));
413 return FALSE;
416 cairo_path = adg_trail_cairo_path(trail);
417 found = ! EMPTY_PATH(cairo_path) &&
418 cpml_segment_from_cairo(&iterator, cairo_path);
420 for (cnt = 1; found && cnt < n_segment; ++cnt)
421 found = cpml_segment_next(&iterator);
423 if (found && segment != NULL)
424 cpml_segment_copy(segment, &iterator);
426 return found;
430 * adg_trail_get_extents:
431 * @trail: an #AdgTrail
433 * Gets the extents of @trail. The returned pointer is owned by
434 * @trail and should not be freed nor modified.
436 * Returns: the requested extents or <constant>NULL</constant> on errors.
438 * Since: 1.0
440 const CpmlExtents *
441 adg_trail_get_extents(AdgTrail *trail)
443 AdgTrailPrivate *data;
445 g_return_val_if_fail(ADG_IS_TRAIL(trail), NULL);
447 data = trail->data;
449 if (!data->extents.is_defined) {
450 cairo_path_t *cairo_path;
451 CpmlSegment segment;
452 CpmlExtents extents;
454 cairo_path = adg_trail_cairo_path(trail);
455 if (! EMPTY_PATH(cairo_path) &&
456 cpml_segment_from_cairo(&segment, cairo_path)) {
457 do {
458 cpml_segment_put_extents(&segment, &extents);
459 cpml_extents_add(&data->extents, &extents);
460 } while (cpml_segment_next(&segment));
464 return &data->extents;
468 * adg_trail_dump:
469 * @trail: an #AdgTrail
471 * Dumps the data content of @trail to stdout in a human readable format.
473 * Since: 1.0
475 void
476 adg_trail_dump(AdgTrail *trail)
478 CpmlSegment segment;
479 cairo_path_t *cairo_path;
481 g_return_if_fail(ADG_IS_TRAIL(trail));
483 cairo_path = (cairo_path_t *) adg_trail_get_cairo_path(trail);
485 g_return_if_fail(cairo_path != NULL);
487 if (!cpml_segment_from_cairo(&segment, cairo_path)) {
488 g_warning(_("%s: invalid path data"), G_STRLOC);
489 } else {
490 do {
491 cpml_segment_dump(&segment);
492 } while (cpml_segment_next(&segment));
497 * adg_trail_set_max_angle:
498 * @trail: an #AdgTrail
499 * @angle: the new angle (in radians)
501 * Sets the max angle of @trail to @angle, basically setting
502 * the #AdgTrail:max-angle property.
504 * This property is used to specify the maximum ciruclar arc
505 * that will be approximated by a single Bézier curve in the
506 * adg_trail_get_cairo_path() method. Basically this can be
507 * used to fine tune the fitting algorithm: lower values mean
508 * an arc will be approximated with more curves, lowering the
509 * error but incrementing time and memory needed. The default
510 * value of %G_PI_2 is usually good in most cases.
512 * Check the cairo-arc.c source file (part of the cairo project)
513 * for mathematical details. A copy can probably be consulted
514 * online at the cairo repository on freedesktop. Here is a link
515 * to the 1.10.2 version:
517 * http://cgit.freedesktop.org/cairo/tree/src/cairo-arc.c?id=1.10.2
519 * Since: 1.0
521 void
522 adg_trail_set_max_angle(AdgTrail *trail, gdouble angle)
524 g_return_if_fail(ADG_IS_TRAIL(trail));
525 g_object_set(trail, "max-angle", angle, NULL);
529 * adg_trail_get_max_angle:
530 * @trail: an #AdgTrail
532 * Gets the #AdgTrail:max-angle property value of @trail.
533 * Refer to adg_trail_set_max_angle() for details of what
534 * this parameter is used for.
536 * Returns: the value (in radians) of the max angle
538 * Since: 1.0
540 gdouble
541 adg_trail_get_max_angle(AdgTrail *trail)
543 AdgTrailPrivate *data;
545 g_return_val_if_fail(ADG_IS_TRAIL(trail), 0);
547 data = trail->data;
548 return data->max_angle;
552 static void
553 _adg_clear(AdgModel *model)
555 AdgTrailPrivate *data = ((AdgTrail *) model)->data;
557 g_free(data->cairo_path.data);
559 data->cairo_path.status = CAIRO_STATUS_INVALID_PATH_DATA;
560 data->cairo_path.data = NULL;
561 data->cairo_path.num_data = 0;
562 data->extents.is_defined = FALSE;
564 if (_ADG_OLD_MODEL_CLASS->clear)
565 _ADG_OLD_MODEL_CLASS->clear(model);
568 static cairo_path_t *
569 _adg_get_cairo_path(AdgTrail *trail)
571 AdgTrailPrivate *data = trail->data;
573 if (data->callback == NULL) {
574 g_warning(_("%s: callback not defined for instance of type '%s'"),
575 G_STRLOC, g_type_name(G_OBJECT_TYPE(trail)));
576 return NULL;
579 return data->callback(trail, data->user_data);
582 static GArray *
583 _adg_arc_to_curves(GArray *array, const cairo_path_data_t *src,
584 gdouble max_angle)
586 CpmlPrimitive arc;
587 double start, end;
589 /* Build the arc primitive: the arc origin is supposed to be the previous
590 * point (src-1): this means a primitive must exist before the arc */
591 arc.segment = NULL;
592 arc.org = (cairo_path_data_t *) (src-1);
593 arc.data = (cairo_path_data_t *) src;
595 if (cpml_arc_info(&arc, NULL, NULL, &start, &end)) {
596 CpmlSegment segment;
597 int n_curves;
598 cairo_path_data_t *curves;
600 n_curves = ceil(fabs(end-start) / max_angle);
601 curves = g_new(cairo_path_data_t, n_curves * 4);
602 segment.data = curves;
603 cpml_arc_to_curves(&arc, &segment, n_curves);
605 array = g_array_append_vals(array, curves, n_curves * 4);
607 g_free(curves);
610 return array;