[AdgRuledFill] Do not apply local global maps twice
[adg.git] / src / adg / adg-ruled-fill.c
blobe85f34032ad01d4b24ccc5f2214650a6a2ac8bd0
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 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-ruled-fill
23 * @short_description: A style composed of evenly spaced lines.
25 * The spacing between the lines could be changed using the
26 * adg_ruled_fill_set_spacing() method. The angle of the lines should
27 * be changed with adg_ruled_fill_set_angle().
28 **/
30 /**
31 * AdgRuledFill:
33 * All fields are private and should not be used directly.
34 * Use its public methods instead.
35 **/
38 #include "adg-internal.h"
39 #include "adg-ruled-fill.h"
40 #include "adg-ruled-fill-private.h"
41 #include "adg-dress-builtins.h"
42 #include <math.h>
44 #define PARENT_STYLE_CLASS ((AdgStyleClass *) adg_ruled_fill_parent_class)
45 #define PARENT_FILL_STYLE_CLASS ((AdgFillStyleClass *) adg_ruled_fill_parent_class)
48 enum {
49 PROP_0,
50 PROP_LINE_DRESS,
51 PROP_SPACING,
52 PROP_ANGLE
56 static void get_property (GObject *object,
57 guint prop_id,
58 GValue *value,
59 GParamSpec *pspec);
60 static void set_property (GObject *object,
61 guint prop_id,
62 const GValue *value,
63 GParamSpec *pspec);
64 static void apply (AdgStyle *style,
65 AdgEntity *entity,
66 cairo_t *cr);
67 static void set_extents (AdgFillStyle *fill_style,
68 const CpmlExtents *extents);
69 static gboolean set_spacing (AdgRuledFill *ruled_fill,
70 gdouble spacing);
71 static gboolean set_angle (AdgRuledFill *ruled_fill,
72 gdouble angle);
73 static cairo_pattern_t *create_pattern (AdgRuledFill *ruled_fill,
74 AdgEntity *entity,
75 cairo_t *cr);
76 static void draw_lines (const CpmlPair *spacing,
77 const CpmlPair *size,
78 cairo_t *cr);
81 G_DEFINE_TYPE(AdgRuledFill, adg_ruled_fill, ADG_TYPE_FILL_STYLE);
84 static void
85 adg_ruled_fill_class_init(AdgRuledFillClass *klass)
87 GObjectClass *gobject_class;
88 AdgStyleClass *style_class;
89 AdgFillStyleClass *fill_style_class;
90 GParamSpec *param;
92 gobject_class = (GObjectClass *) klass;
93 style_class = (AdgStyleClass *) klass;
94 fill_style_class = (AdgFillStyleClass *) klass;
96 g_type_class_add_private(klass, sizeof(AdgRuledFillPrivate));
98 gobject_class->get_property = get_property;
99 gobject_class->set_property = set_property;
101 style_class->apply = apply;
103 fill_style_class->set_extents = set_extents;
105 param = adg_param_spec_dress("line-dress",
106 P_("Line Dress"),
107 P_("Dress to be used for rendering the lines"),
108 ADG_DRESS_LINE_HATCH,
109 G_PARAM_READWRITE);
110 g_object_class_install_property(gobject_class, PROP_LINE_DRESS, param);
112 param = g_param_spec_double("spacing",
113 P_("Spacing"),
114 P_("The spacing in global spaces between the lines"),
115 0, G_MAXDOUBLE, 16,
116 G_PARAM_READWRITE);
117 g_object_class_install_property(gobject_class, PROP_SPACING, param);
119 param = g_param_spec_double("angle",
120 P_("Angle"),
121 P_("The angle (in radians) of the lines"),
122 0, G_PI, G_PI_4,
123 G_PARAM_READWRITE);
124 g_object_class_install_property(gobject_class, PROP_ANGLE, param);
127 static void
128 adg_ruled_fill_init(AdgRuledFill *ruled_fill)
130 AdgRuledFillPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(ruled_fill,
131 ADG_TYPE_RULED_FILL,
132 AdgRuledFillPrivate);
134 data->line_dress = ADG_DRESS_LINE_HATCH;
135 data->angle = G_PI_4;
136 data->spacing = 16;
138 ruled_fill->data = data;
141 static void
142 get_property(GObject *object,
143 guint prop_id, GValue *value, GParamSpec *pspec)
145 AdgRuledFillPrivate *data = ((AdgRuledFill *) object)->data;
147 switch (prop_id) {
148 case PROP_LINE_DRESS:
149 g_value_set_int(value, data->line_dress);
150 break;
151 case PROP_SPACING:
152 g_value_set_double(value, data->spacing);
153 break;
154 case PROP_ANGLE:
155 g_value_set_double(value, data->angle);
156 break;
157 default:
158 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
159 break;
163 static void
164 set_property(GObject *object,
165 guint prop_id, const GValue *value, GParamSpec *pspec)
167 AdgRuledFill *ruled_fill;
168 AdgRuledFillPrivate *data;
170 ruled_fill = (AdgRuledFill *) object;
171 data = ruled_fill->data;
173 switch (prop_id) {
174 case PROP_LINE_DRESS:
175 data->line_dress = g_value_get_int(value);
176 break;
177 case PROP_SPACING:
178 set_spacing(ruled_fill, g_value_get_double(value));
179 break;
180 case PROP_ANGLE:
181 set_angle(ruled_fill, g_value_get_double(value));
182 break;
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
185 break;
191 * adg_ruled_fill_new:
193 * Constructs a new empty ruled fill style initialized with default params.
195 * Returns: a newly created ruled fill style
197 AdgRuledFill *
198 adg_ruled_fill_new(void)
200 return g_object_new(ADG_TYPE_RULED_FILL, NULL);
204 * adg_ruled_fill_set_line_dress:
205 * @ruled_fill: an #AdgRuledFill object
206 * @dress: the new line dress
208 * Sets a new line dress on @ruled_fill.
210 void
211 adg_ruled_fill_set_line_dress(AdgRuledFill *ruled_fill, AdgDress dress)
213 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
214 g_object_set((GObject *) ruled_fill, "line-dress", dress, NULL);
218 * adg_ruled_fill_get_line_dress:
219 * @ruled_fill: an #AdgRuledFill object
221 * Gets the @ruled_fill dress to be used for rendering the lines.
223 * Returns: the line dress
225 AdgDress
226 adg_ruled_fill_get_line_dress(AdgRuledFill *ruled_fill)
228 AdgRuledFillPrivate *data;
230 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), ADG_DRESS_UNDEFINED);
232 data = ruled_fill->data;
234 return data->line_dress;
238 * adg_ruled_fill_set_spacing:
239 * @ruled_fill: an #AdgRuledFill
240 * @spacing: the new spacing
242 * Sets a new spacing on @ruled_fill.
244 void
245 adg_ruled_fill_set_spacing(AdgRuledFill *ruled_fill, gdouble spacing)
247 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
249 if (set_spacing(ruled_fill, spacing))
250 g_object_notify((GObject *) ruled_fill, "spacing");
254 * adg_ruled_fill_get_spacing:
255 * @ruled_fill: an #AdgRuledFill
257 * Gets the current spacing of @ruled_fill.
259 * Returns: the spacing (in global space)
261 gdouble
262 adg_ruled_fill_get_spacing(AdgRuledFill *ruled_fill)
264 AdgRuledFillPrivate *data;
266 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0);
268 data = ruled_fill->data;
270 return data->spacing;
274 * adg_ruled_fill_set_angle:
275 * @ruled_fill: an #AdgRuledFill
276 * @angle: the new angle
278 * Sets a new angle on @ruled_fill.
280 void
281 adg_ruled_fill_set_angle(AdgRuledFill *ruled_fill, gdouble angle)
283 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
285 if (set_angle(ruled_fill, angle))
286 g_object_notify((GObject *) ruled_fill, "angle");
290 * adg_ruled_fill_get_angle:
291 * @ruled_fill: an #AdgRuledFill
293 * Gets the current angle of @ruled_fill.
295 * Returns: the angle (in radians)
297 gdouble
298 adg_ruled_fill_get_angle(AdgRuledFill *ruled_fill)
300 AdgRuledFillPrivate *data;
302 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0);
304 data = ruled_fill->data;
306 return data->angle;
310 static void
311 apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr)
313 AdgFillStyle *fill_style;
314 AdgPattern *pattern;
315 const CpmlExtents *extents;
316 cairo_matrix_t matrix;
318 fill_style = (AdgFillStyle *) style;
319 pattern = adg_fill_style_get_pattern(fill_style);
320 extents = adg_fill_style_get_extents(fill_style);
322 if (pattern == NULL) {
323 pattern = create_pattern((AdgRuledFill *) style, entity, cr);
324 if (pattern == NULL)
325 return;
327 adg_fill_style_set_pattern(fill_style, pattern);
328 cairo_pattern_destroy(pattern);
331 /* The extents are yet in identity space, so the ctm should be
332 * reset to avoid using local and global maps twice: only the
333 * translation (also in identity space) is applied */
334 cairo_matrix_init_translate(&matrix, extents->org.x, extents->org.y);
335 cairo_set_matrix(cr, &matrix);
337 if (PARENT_STYLE_CLASS->apply)
338 PARENT_STYLE_CLASS->apply(style, entity, cr);
341 static void
342 set_extents(AdgFillStyle *fill_style, const CpmlExtents *extents)
344 CpmlExtents old, new;
346 cpml_extents_copy(&old, adg_fill_style_get_extents(fill_style));
348 /* The pattern is invalidated (and thus regenerated) only
349 * when the new extents are wider than the old ones */
350 if (old.size.x >= extents->size.x && old.size.y >= extents->size.y) {
351 new.org = extents->org;
352 new.size = old.size;
353 } else {
354 cpml_extents_copy(&new, extents);
355 adg_fill_style_set_pattern(fill_style, NULL);
358 if (PARENT_FILL_STYLE_CLASS->set_extents)
359 PARENT_FILL_STYLE_CLASS->set_extents(fill_style, &new);
362 static gboolean
363 set_spacing(AdgRuledFill *ruled_fill, gdouble spacing)
365 AdgRuledFillPrivate *data = ruled_fill->data;
367 if (spacing == data->spacing)
368 return FALSE;
370 data->spacing = spacing;
371 adg_fill_style_set_pattern((AdgFillStyle *) ruled_fill, NULL);
373 return TRUE;
376 static gboolean
377 set_angle(AdgRuledFill *ruled_fill, gdouble angle)
379 AdgRuledFillPrivate *data = ruled_fill->data;
381 if (angle == data->angle)
382 return FALSE;
384 data->angle = angle;
385 adg_fill_style_set_pattern((AdgFillStyle *) ruled_fill, NULL);
387 return TRUE;
390 static cairo_pattern_t *
391 create_pattern(AdgRuledFill *ruled_fill, AdgEntity *entity, cairo_t *cr)
393 AdgFillStyle *fill_style;
394 const CpmlExtents *extents;
395 AdgRuledFillPrivate *data;
396 AdgStyle *line_style;
397 cairo_pattern_t *pattern;
398 cairo_surface_t *surface;
399 CpmlPair spacing;
400 cairo_t *context;
402 fill_style = (AdgFillStyle *) ruled_fill;
403 extents = adg_fill_style_get_extents(fill_style);
405 /* Check for valid extents */
406 if (!extents->is_defined)
407 return NULL;
409 data = ruled_fill->data;
410 line_style = adg_entity_style(entity, data->line_dress);
411 surface = cairo_surface_create_similar(cairo_get_target(cr),
412 CAIRO_CONTENT_COLOR_ALPHA,
413 extents->size.x, extents->size.y);
414 pattern = cairo_pattern_create_for_surface(surface);
416 /* The pattern holds a reference to the surface, so
417 * there is no need to hold another reference here */
418 cairo_surface_destroy(surface);
420 spacing.x = cos(data->angle) * data->spacing;
421 spacing.y = sin(data->angle) * data->spacing;
423 context = cairo_create(surface);
424 adg_style_apply(line_style, entity, context);
425 draw_lines(&spacing, &extents->size, context);
426 cairo_destroy(context);
428 return pattern;
431 static void
432 draw_lines(const CpmlPair *spacing, const CpmlPair *size, cairo_t *cr)
434 CpmlPair step, step1, step2;
435 CpmlPair p1, p2;
437 /* There should be some sort of spacing and a destination area */
438 if ((spacing->x == 0 && spacing->y == 0) ||
439 (size->x <= 0 && size->y <= 0))
440 return;
442 /* Revert spacings if needed to inspect only the x >= 0 cases */
443 cpml_pair_copy(&step, spacing);
444 if (spacing->x < 0 || (spacing->x == 0 && spacing->y < 0)) {
445 step.x = -step.x;
446 step.y = -step.y;
449 p1.x = step.x / 2;
450 p2.y = step.y / 2;
451 p1.y = step.y == 0 ? p2.y : 0;
452 p2.x = step.x == 0 ? p1.x : 0;
453 if (step.y < 0) {
454 p1.y += size->y;
455 p2.y += size->y;
458 step2.x = 0;
459 step2.y = step.y;
461 if (step.x != 0) {
462 step1.x = step.x;
463 step1.y = 0;
465 while (p1.x < size->x) {
466 if (p2.y <= 0 || p2.y >= size->y) {
467 step2.x = step.x;
468 step2.y = 0;
470 cairo_move_to(cr, p1.x, p1.y);
471 cairo_line_to(cr, p2.x, p2.y);
472 p1.x += step1.x;
473 p1.y += step1.y;
474 p2.x += step2.x;
475 p2.y += step2.y;
479 if (step.y != 0) {
480 step1.x = 0;
481 step1.y = step.y;
483 while (p1.y >= 0 && p1.y <= size->y) {
484 if (p2.y <= 0 || p2.y >= size->y) {
485 step2.x = step.x;
486 step2.y = 0;
488 cairo_move_to(cr, p1.x, p1.y);
489 cairo_line_to(cr, p2.x, p2.y);
490 p1.x += step1.x;
491 p1.y += step1.y;
492 p2.x += step2.x;
493 p2.y += step2.y;
497 cairo_stroke(cr);