[AdgRuledFill] Improved ruled creation algorithm
[adg.git] / adg / adg-ruled-fill.c
blob95ff776b2cc2b5e3936ef392a0ba80846223b5a5
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 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-ruled-fill.h"
39 #include "adg-ruled-fill-private.h"
40 #include "adg-intl.h"
41 #include <math.h>
43 #define PARENT_STYLE_CLASS ((AdgStyleClass *) adg_ruled_fill_parent_class)
44 #define PARENT_FILL_STYLE_CLASS ((AdgFillStyleClass *) adg_ruled_fill_parent_class)
47 enum {
48 PROP_0,
49 PROP_SPACING,
50 PROP_ANGLE
54 static void get_property (GObject *object,
55 guint prop_id,
56 GValue *value,
57 GParamSpec *pspec);
58 static void set_property (GObject *object,
59 guint prop_id,
60 const GValue *value,
61 GParamSpec *pspec);
62 static void apply (AdgStyle *style,
63 cairo_t *cr);
64 static void set_extents (AdgFillStyle *fill_style,
65 const CpmlExtents *extents);
66 static gboolean set_spacing (AdgRuledFill *ruled_fill,
67 gdouble spacing);
68 static gboolean set_angle (AdgRuledFill *ruled_fill,
69 gdouble angle);
70 static cairo_pattern_t *create_pattern (AdgRuledFill *ruled_fill,
71 cairo_t *cr);
72 static void draw_lines (const CpmlPair *spacing,
73 const CpmlPair *size,
74 cairo_t *cr);
77 G_DEFINE_TYPE(AdgRuledFill, adg_ruled_fill, ADG_TYPE_FILL_STYLE);
80 static void
81 adg_ruled_fill_class_init(AdgRuledFillClass *klass)
83 GObjectClass *gobject_class;
84 AdgStyleClass *style_class;
85 AdgFillStyleClass *fill_style_class;
86 GParamSpec *param;
88 gobject_class = (GObjectClass *) klass;
89 style_class = (AdgStyleClass *) klass;
90 fill_style_class = (AdgFillStyleClass *) klass;
92 g_type_class_add_private(klass, sizeof(AdgRuledFillPrivate));
94 gobject_class->get_property = get_property;
95 gobject_class->set_property = set_property;
97 style_class->apply = apply;
99 fill_style_class->set_extents = set_extents;
101 param = g_param_spec_double("spacing",
102 P_("Spacing"),
103 P_("The spacing in global spaces between the lines"),
104 0, G_MAXDOUBLE, 16,
105 G_PARAM_READWRITE);
106 g_object_class_install_property(gobject_class, PROP_SPACING, param);
108 param = g_param_spec_double("angle",
109 P_("Angle"),
110 P_("The angle (in radians) of the lines"),
111 0, G_PI, G_PI_4,
112 G_PARAM_READWRITE);
113 g_object_class_install_property(gobject_class, PROP_ANGLE, param);
116 static void
117 adg_ruled_fill_init(AdgRuledFill *ruled_fill)
119 AdgRuledFillPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(ruled_fill,
120 ADG_TYPE_RULED_FILL,
121 AdgRuledFillPrivate);
123 data->angle = G_PI_4;
124 data->spacing = 16;
126 ruled_fill->data = data;
129 static void
130 get_property(GObject *object,
131 guint prop_id, GValue *value, GParamSpec *pspec)
133 AdgRuledFillPrivate *data = ((AdgRuledFill *) object)->data;
135 switch (prop_id) {
136 case PROP_SPACING:
137 g_value_set_double(value, data->spacing);
138 break;
139 case PROP_ANGLE:
140 g_value_set_double(value, data->angle);
141 break;
142 default:
143 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
144 break;
148 static void
149 set_property(GObject *object,
150 guint prop_id, const GValue *value, GParamSpec *pspec)
152 AdgRuledFill *ruled_fill = (AdgRuledFill *) object;
154 switch (prop_id) {
155 case PROP_SPACING:
156 set_spacing(ruled_fill, g_value_get_double(value));
157 break;
158 case PROP_ANGLE:
159 set_angle(ruled_fill, g_value_get_double(value));
160 break;
161 default:
162 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
163 break;
169 * adg_ruled_fill_get_spacing:
170 * @ruled_fill: an #AdgRuledFill
172 * Gets the current spacing of @ruled_fill.
174 * Returns: the spacing (in global space)
176 gdouble
177 adg_ruled_fill_get_spacing(AdgRuledFill *ruled_fill)
179 AdgRuledFillPrivate *data;
181 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0);
183 data = ruled_fill->data;
185 return data->spacing;
189 * adg_ruled_fill_set_spacing:
190 * @ruled_fill: an #AdgRuledFill
191 * @spacing: the new spacing
193 * Sets a new spacing on @ruled_fill.
195 void
196 adg_ruled_fill_set_spacing(AdgRuledFill *ruled_fill, gdouble spacing)
198 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
200 if (set_spacing(ruled_fill, spacing))
201 g_object_notify((GObject *) ruled_fill, "spacing");
205 * adg_ruled_fill_get_angle:
206 * @ruled_fill: an #AdgRuledFill
208 * Gets the current angle of @ruled_fill.
210 * Returns: the angle (in radians)
212 gdouble
213 adg_ruled_fill_get_angle(AdgRuledFill *ruled_fill)
215 AdgRuledFillPrivate *data;
217 g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0);
219 data = ruled_fill->data;
221 return data->angle;
225 * adg_ruled_fill_set_angle:
226 * @ruled_fill: an #AdgRuledFill
227 * @angle: the new angle
229 * Sets a new angle on @ruled_fill.
231 void
232 adg_ruled_fill_set_angle(AdgRuledFill *ruled_fill, gdouble angle)
234 g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill));
236 if (set_angle(ruled_fill, angle))
237 g_object_notify((GObject *) ruled_fill, "angle");
241 static void
242 apply(AdgStyle *style, cairo_t *cr)
244 AdgRuledFill *ruled_fill;
245 AdgFillStyle *fill_style;
246 cairo_pattern_t *pattern;
247 CpmlExtents extents;
248 cairo_matrix_t matrix;
250 ruled_fill = (AdgRuledFill *) style;
251 fill_style = (AdgFillStyle *) style;
252 pattern = adg_fill_style_get_pattern(fill_style);
254 if (pattern == NULL) {
255 pattern = create_pattern(ruled_fill, cr);
256 if (pattern == NULL)
257 return;
259 adg_fill_style_set_pattern(fill_style, pattern);
260 cairo_pattern_destroy(pattern);
263 adg_fill_style_get_extents(fill_style, &extents);
264 cairo_matrix_init_translate(&matrix, -extents.org.x, -extents.org.y);
265 cairo_pattern_set_matrix(pattern, &matrix);
267 if (PARENT_STYLE_CLASS->apply != NULL)
268 PARENT_STYLE_CLASS->apply(style, cr);
271 static void
272 set_extents(AdgFillStyle *fill_style, const CpmlExtents *extents)
274 CpmlExtents old, new;
276 adg_fill_style_get_extents(fill_style, &old);
278 /* The pattern is invalidated (and thus regenerated) only
279 * when the new extents are wider than the old ones */
280 if (old.size.x >= extents->size.x && old.size.y >= extents->size.y) {
281 new.org = extents->org;
282 new.size = old.size;
283 } else {
284 cpml_extents_copy(&new, extents);
285 adg_fill_style_set_pattern(fill_style, NULL);
288 if (PARENT_FILL_STYLE_CLASS->set_extents != NULL)
289 PARENT_FILL_STYLE_CLASS->set_extents(fill_style, &new);
292 static gboolean
293 set_spacing(AdgRuledFill *ruled_fill, gdouble spacing)
295 AdgRuledFillPrivate *data = ruled_fill->data;
297 if (spacing == data->spacing)
298 return FALSE;
300 data->spacing = spacing;
301 adg_fill_style_set_pattern((AdgFillStyle *) ruled_fill, NULL);
303 return TRUE;
306 static gboolean
307 set_angle(AdgRuledFill *ruled_fill, gdouble angle)
309 AdgRuledFillPrivate *data = ruled_fill->data;
311 if (angle == data->angle)
312 return FALSE;
314 data->angle = angle;
315 adg_fill_style_set_pattern((AdgFillStyle *) ruled_fill, NULL);
317 return TRUE;
320 static cairo_pattern_t *
321 create_pattern(AdgRuledFill *ruled_fill, cairo_t *cr)
323 AdgFillStyle *fill_style;
324 CpmlExtents extents;
325 AdgRuledFillPrivate *data;
326 cairo_pattern_t *pattern;
327 cairo_surface_t *surface;
328 CpmlPair spacing;
329 cairo_t *context;
331 fill_style = (AdgFillStyle *) ruled_fill;
332 adg_fill_style_get_extents(fill_style, &extents);
334 /* Check for valid extents */
335 if (!extents.is_defined)
336 return NULL;
338 data = ruled_fill->data;
340 surface = cairo_surface_create_similar(cairo_get_target(cr),
341 CAIRO_CONTENT_COLOR_ALPHA,
342 extents.size.x, extents.size.y);
343 pattern = cairo_pattern_create_for_surface(surface);
344 /* The pattern holds a reference to the surface */
345 cairo_surface_destroy(surface);
347 spacing.x = cos(data->angle) * data->spacing;
348 spacing.y = sin(data->angle) * data->spacing;
350 context = cairo_create(surface);
351 draw_lines(&spacing, &extents.size, context);
352 cairo_destroy(context);
354 return pattern;
357 static void
358 draw_lines(const CpmlPair *spacing, const CpmlPair *size, cairo_t *cr)
360 CpmlPair step, step1, step2;
361 CpmlPair p1, p2;
363 /* There should be some sort of spacing and a destination area */
364 if ((spacing->x == 0 && spacing->y == 0) ||
365 (size->x <= 0 && size->y <= 0))
366 return;
368 /* Revert spacings if needed to inspect only the x >= 0 cases */
369 cpml_pair_copy(&step, spacing);
370 if (spacing->x < 0 || (spacing->x == 0 && spacing->y < 0))
371 cpml_pair_negate(&step);
373 p1.x = step.x / 2;
374 p2.y = step.y / 2;
375 p1.y = step.y == 0 ? p2.y : 0;
376 p2.x = step.x == 0 ? p1.x : 0;
377 if (step.y < 0) {
378 p1.y += size->y;
379 p2.y += size->y;
382 step2.x = 0;
383 step2.y = step.y;
385 if (step.x != 0) {
386 step1.x = step.x;
387 step1.y = 0;
389 while (p1.x < size->x) {
390 if (p2.y <= 0 || p2.y >= size->y) {
391 step2.x = step.x;
392 step2.y = 0;
394 cairo_move_to(cr, p1.x, p1.y);
395 cairo_line_to(cr, p2.x, p2.y);
396 cpml_pair_add(&p1, &step1);
397 cpml_pair_add(&p2, &step2);
401 if (step.y != 0) {
402 step1.x = 0;
403 step1.y = step.y;
405 while (p1.y >= 0 && p1.y <= size->y) {
406 if (p2.y <= 0 || p2.y >= size->y) {
407 step2.x = step.x;
408 step2.y = 0;
410 cairo_move_to(cr, p1.x, p1.y);
411 cairo_line_to(cr, p2.x, p2.y);
412 cpml_pair_add(&p1, &step1);
413 cpml_pair_add(&p2, &step2);
417 cairo_stroke(cr);