1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2015 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.
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.
43 * All fields are private and should not be used directly.
44 * Use its public methods instead.
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
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
76 #include "adg-internal.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
)
98 static void _adg_finalize (GObject
*object
);
99 static void _adg_get_property (GObject
*object
,
103 static void _adg_set_property (GObject
*object
,
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
,
115 adg_trail_class_init(AdgTrailClass
*klass
)
117 GObjectClass
*gobject_class
;
118 AdgModelClass
*model_class
;
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",
136 P_("Max arc angle to approximate with a single Bezier curve: check adg_trail_set_max_angle() for details"),
139 g_object_class_install_property(gobject_class
, PROP_MAX_ANGLE
, param
);
143 adg_trail_init(AdgTrail
*trail
)
145 AdgTrailPrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(trail
, ADG_TYPE_TRAIL
,
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
;
161 _adg_finalize(GObject
*object
)
163 _adg_clear((AdgModel
*) object
);
165 if (_ADG_OLD_OBJECT_CLASS
->finalize
)
166 _ADG_OLD_OBJECT_CLASS
->finalize(object
);
170 _adg_get_property(GObject
*object
, guint prop_id
,
171 GValue
*value
, GParamSpec
*pspec
)
174 AdgTrailPrivate
*data
;
176 trail
= (AdgTrail
*) object
;
181 g_value_set_double(value
, data
->max_angle
);
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
190 _adg_set_property(GObject
*object
, guint prop_id
,
191 const GValue
*value
, GParamSpec
*pspec
)
194 AdgTrailPrivate
*data
;
196 trail
= (AdgTrail
*) object
;
201 data
->max_angle
= g_value_get_double(value
);
204 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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.
224 adg_trail_new(AdgTrailCallback callback
, gpointer user_data
)
227 AdgTrailPrivate
*data
;
229 trail
= g_object_new(ADG_TYPE_TRAIL
, NULL
);
232 data
->callback
= callback
;
233 data
->user_data
= user_data
;
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.
257 adg_trail_get_cairo_path(AdgTrail
*trail
)
259 AdgTrailPrivate
*data
;
260 cairo_path_t
*cairo_path
;
262 const cairo_path_data_t
*p_src
;
265 g_return_val_if_fail(ADG_IS_TRAIL(trail
), NULL
);
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
))
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
);
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
);
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.
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
)
336 if (data
->in_construction
) {
337 g_warning(_("%s: you cannot access the path from the callback you provided to build it"),
342 data
->in_construction
= TRUE
;
343 cairo_path
= klass
->get_cairo_path(trail
);
344 data
->in_construction
= FALSE
;
350 * adg_trail_put_segment:
351 * @trail: an #AdgTrail
352 * @n_segment: the segment to retrieve, where 1 is the first segment
353 * @segment: the destination #CpmlSegment
355 * Convenient function to get a segment from @trail. The segment is
356 * got from the cairo path: check out adg_trail_cairo_path() for
357 * further information.
359 * When the segment is not found, either because @n_segment is out
360 * of range or because there is still no path bound to @trail, this
361 * function will return <constant>FALSE</constant> leaving @segment
362 * untouched. If the segment is found and @segment is
363 * not <constant>NULL</constant>, the resulting segment is copied in @segment.
365 * Returns: <constant>TRUE</constant> on success or <constant>FALSE</constant> on errors.
370 adg_trail_put_segment(AdgTrail
*trail
, guint n_segment
, CpmlSegment
*segment
)
372 cairo_path_t
*cairo_path
;
374 CpmlSegment iterator
;
377 g_return_val_if_fail(ADG_IS_TRAIL(trail
), FALSE
);
379 if (n_segment
== 0) {
380 g_warning(_("%s: requested undefined segment for type '%s'"),
381 G_STRLOC
, g_type_name(G_OBJECT_TYPE(trail
)));
385 cairo_path
= adg_trail_cairo_path(trail
);
386 found
= ! EMPTY_PATH(cairo_path
) &&
387 cpml_segment_from_cairo(&iterator
, cairo_path
);
389 for (cnt
= 1; found
&& cnt
< n_segment
; ++cnt
)
390 found
= cpml_segment_next(&iterator
);
392 if (found
&& segment
)
393 cpml_segment_copy(segment
, &iterator
);
396 g_warning(_("%s: segment %u is out of range for type '%s'"),
397 G_STRLOC
, n_segment
, g_type_name(G_OBJECT_TYPE(trail
)));
403 * adg_trail_get_extents:
404 * @trail: an #AdgTrail
406 * Gets the extents of @trail. The returned pointer is owned by
407 * @trail and should not be freed nor modified.
409 * Returns: the requested extents or <constant>NULL</constant> on errors.
414 adg_trail_get_extents(AdgTrail
*trail
)
416 AdgTrailPrivate
*data
;
418 g_return_val_if_fail(ADG_IS_TRAIL(trail
), NULL
);
422 if (!data
->extents
.is_defined
) {
423 cairo_path_t
*cairo_path
;
427 cairo_path
= adg_trail_cairo_path(trail
);
428 if (! EMPTY_PATH(cairo_path
) &&
429 cpml_segment_from_cairo(&segment
, cairo_path
)) {
431 cpml_segment_put_extents(&segment
, &extents
);
432 cpml_extents_add(&data
->extents
, &extents
);
433 } while (cpml_segment_next(&segment
));
437 return &data
->extents
;
442 * @trail: an #AdgTrail
444 * Dumps the data content of @trail to stdout in a human readable format.
449 adg_trail_dump(AdgTrail
*trail
)
452 cairo_path_t
*cairo_path
;
454 g_return_if_fail(ADG_IS_TRAIL(trail
));
456 cairo_path
= (cairo_path_t
*) adg_trail_get_cairo_path(trail
);
458 g_return_if_fail(cairo_path
!= NULL
);
460 if (!cpml_segment_from_cairo(&segment
, cairo_path
)) {
461 g_warning(_("%s: invalid path data"), G_STRLOC
);
464 cpml_segment_dump(&segment
);
465 } while (cpml_segment_next(&segment
));
470 * adg_trail_set_max_angle:
471 * @trail: an #AdgTrail
472 * @angle: the new angle (in radians)
474 * Sets the max angle of @trail to @angle, basically setting
475 * the #AdgTrail:max-angle property.
477 * This property is used to specify the maximum ciruclar arc
478 * that will be approximated by a single Bézier curve in the
479 * adg_trail_get_cairo_path() method. Basically this can be
480 * used to fine tune the fitting algorithm: lower values mean
481 * an arc will be approximated with more curves, lowering the
482 * error but incrementing time and memory needed. The default
483 * value of %G_PI_2 is usually good in most cases.
485 * Check the cairo-arc.c source file (part of the cairo project)
486 * for mathematical details. A copy can probably be consulted
487 * online at the cairo repository on freedesktop. Here is a link
488 * to the 1.10.2 version:
490 * http://cgit.freedesktop.org/cairo/tree/src/cairo-arc.c?id=1.10.2
495 adg_trail_set_max_angle(AdgTrail
*trail
, gdouble angle
)
497 g_return_if_fail(ADG_IS_TRAIL(trail
));
498 g_object_set(trail
, "max-angle", angle
, NULL
);
502 * adg_trail_get_max_angle:
503 * @trail: an #AdgTrail
505 * Gets the #AdgTrail:max-angle property value of @trail.
506 * Refer to adg_trail_set_max_angle() for details of what
507 * this parameter is used for.
509 * Returns: the value (in radians) of the max angle
514 adg_trail_get_max_angle(AdgTrail
*trail
)
516 AdgTrailPrivate
*data
;
518 g_return_val_if_fail(ADG_IS_TRAIL(trail
), 0);
521 return data
->max_angle
;
526 _adg_clear(AdgModel
*model
)
528 AdgTrailPrivate
*data
= ((AdgTrail
*) model
)->data
;
530 g_free(data
->cairo_path
.data
);
532 data
->cairo_path
.status
= CAIRO_STATUS_INVALID_PATH_DATA
;
533 data
->cairo_path
.data
= NULL
;
534 data
->cairo_path
.num_data
= 0;
535 data
->extents
.is_defined
= FALSE
;
537 if (_ADG_OLD_MODEL_CLASS
->clear
)
538 _ADG_OLD_MODEL_CLASS
->clear(model
);
541 static cairo_path_t
*
542 _adg_get_cairo_path(AdgTrail
*trail
)
544 AdgTrailPrivate
*data
= trail
->data
;
546 if (data
->callback
== NULL
) {
547 g_warning(_("%s: callback not defined for instance of type '%s'"),
548 G_STRLOC
, g_type_name(G_OBJECT_TYPE(trail
)));
552 return data
->callback(trail
, data
->user_data
);
556 _adg_arc_to_curves(GArray
*array
, const cairo_path_data_t
*src
,
562 /* Build the arc primitive: the arc origin is supposed to be the previous
563 * point (src-1): this means a primitive must exist before the arc */
565 arc
.org
= (cairo_path_data_t
*) (src
-1);
566 arc
.data
= (cairo_path_data_t
*) src
;
568 if (cpml_arc_info(&arc
, NULL
, NULL
, &start
, &end
)) {
571 cairo_path_data_t
*curves
;
573 n_curves
= ceil(fabs(end
-start
) / max_angle
);
574 curves
= g_new(cairo_path_data_t
, n_curves
* 4);
575 segment
.data
= curves
;
576 cpml_arc_to_curves(&arc
, &segment
, n_curves
);
578 array
= g_array_append_vals(array
, curves
, n_curves
* 4);