1 /*
2  * go-marker.c :
3  *
4  * Copyright (C) 2003-2007 Emmanuel Pacaud <emmanuel.pacaud@lapp.in2p3.fr>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 
25 #include <glib/gi18n-lib.h>
26 #include <gsf/gsf-impl-utils.h>
27 
28 /**
29  * GOMarkerShape:
30  * @GO_MARKER_NONE: no mark.
31  * @GO_MARKER_SQUARE: square.
32  * @GO_MARKER_DIAMOND: diamond.
33  * @GO_MARKER_TRIANGLE_DOWN: triangle down.
34  * @GO_MARKER_TRIANGLE_UP: triangle up.
35  * @GO_MARKER_TRIANGLE_RIGHT: triangle right.
36  * @GO_MARKER_TRIANGLE_LEFT: triangle left.
37  * @GO_MARKER_CIRCLE: circle.
38  * @GO_MARKER_X: X.
39  * @GO_MARKER_CROSS: cross.
40  * @GO_MARKER_ASTERISK: asterisk.
41  * @GO_MARKER_BAR: horizontal bar.
42  * @GO_MARKER_HALF_BAR: right half bar.
43  * @GO_MARKER_BUTTERFLY: butterfly.
44  * @GO_MARKER_HOURGLASS: hourglass.
45  * @GO_MARKER_LEFT_HALF_BAR: left half bar.
46  * @GO_MARKER_MAX: maximum value, should not occur.
47  **/
48 
49 #define MARKER_DEFAULT_SIZE 5
50 #define MARKER_OUTLINE_WIDTH 0.1
51 
52 struct _GOMarker {
53 	GObject 	base;
54 
55 	int		size;
56 	GOMarkerShape	shape;
57 	GOColor		outline_color;
58 	GOColor		fill_color;
59 };
60 
61 typedef struct {
62 	GObjectClass	base;
63 } GOMarkerClass;
64 
65 #define GO_MARKER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS((o),  GO_TYPE_MARKER, GOMarkerClass))
66 
67 static char const square_path[] = 		"M-1,-1 L-1,1 1,1 1,-1 z";
68 static char const diamond_path[] =		"M0,-1 L1,0 0,1 -1,0 z";
69 static char const triangle_down_path[] =	"M-1,-1 L1,-1 0,1 z";
70 static char const triangle_up_path[] =		"M0,-1 L1,1 -1,1 z";
71 static char const triangle_right_path[] =	"M-1,-1 L1,0 -1,1 z";
72 static char const triangle_left_path[] =	"M1,-1 L-1,0 1,1 z";
73 static char const circle_path[] =		"M1,0 C1,0.56 0.56,1 0,1 C-0.56,1 -1,0.56 -1,0 "
74 						"C-1,-0.56 -0.56,-1 0,-1 C0.56,-1 1,-0.56 1,0 L1,0 z";
75 static char const x_path[] =			"M1,1 L-1,-1 M1,-1 L-1,1";
76 static char const cross_path[] =		"M1,0 L-1,0 M0,1 L0,-1";
77 static char const asterisk_path[] =		"M0.7,0.7 L-0.7,-0.7 M0.7,-0.7 L-0.7,0.7 M1,0 L-1,0 M0,1 L0,-1";
78 static char const bar_path[] =			"M-1 -0.2 L 1 -0.2 L 1 0.2 L -1 0.2 z";
79 static char const half_bar_path[] = 		"M0,-0.2 L1,-0.2 1,0.2 0,0.2 z";
80 static char const butterfly_path[] =		"M-1,-1 L-1,1 0,0 1,1 1,-1 0,0 z";
81 static char const hourglass_path[] =		"M-1,-1 L1,-1 0,0 1,1 -1,1 0,0 z";
82 static char const left_half_bar_path[] =	"M0,-0.2 L-1,-0.2 -1,0.2 0,0.2 z";
83 
84 typedef struct
85 {
86 	char const *name;
87 	char const *str;
88 	char const *outline_path;
89 	char const *fill_path;
90 } MarkerShape;
91 
92 #define MAKE_MARKER_SHAPE(name, str, path)	{name, str, path, path}
93 #define MAKE_MARKER_SQUARED(name, str, path)	{name, str, path, square_path}
94 
95 static MarkerShape const marker_shapes[GO_MARKER_MAX] = {
96     MAKE_MARKER_SHAPE   ( N_("none"),		"none",           NULL),
97     MAKE_MARKER_SHAPE   ( N_("square"),		"square",         square_path),
98     MAKE_MARKER_SHAPE   ( N_("diamond"),	"diamond",        diamond_path),
99     MAKE_MARKER_SHAPE   ( N_("triangle down"),	"triangle-down",  triangle_down_path),
100     MAKE_MARKER_SHAPE   ( N_("triangle up"),	"triangle-up",    triangle_up_path),
101     MAKE_MARKER_SHAPE   ( N_("triangle right"),	"triangle-right", triangle_right_path),
102     MAKE_MARKER_SHAPE   ( N_("triangle left"),	"triangle-left",  triangle_left_path),
103     MAKE_MARKER_SHAPE   ( N_("circle"),		"circle",         circle_path),
104     MAKE_MARKER_SQUARED ( N_("x"),		"x",              x_path),
105     MAKE_MARKER_SQUARED ( N_("cross"),		"cross",          cross_path),
106     MAKE_MARKER_SQUARED ( N_("asterisk"),	"asterisk",       asterisk_path),
107     MAKE_MARKER_SHAPE   ( N_("bar"), 		"bar",            bar_path),
108     MAKE_MARKER_SHAPE   ( N_("half bar"),	"half-bar",       half_bar_path),
109     MAKE_MARKER_SHAPE   ( N_("butterfly"),	"butterfly",      butterfly_path),
110     MAKE_MARKER_SHAPE   ( N_("hourglass"),	"hourglass",      hourglass_path),
111     MAKE_MARKER_SHAPE   ( N_("left half bar"),	"lefthalf-bar",	  left_half_bar_path)
112 };
113 
114 static GObjectClass *marker_parent_klass;
115 
116 static void
go_marker_init(GOMarker * marker)117 go_marker_init (GOMarker * marker)
118 {
119 	marker->shape		= GO_MARKER_NONE;
120 	marker->outline_color	= GO_COLOR_BLACK;
121 	marker->fill_color	= GO_COLOR_WHITE;
122 	marker->size		= MARKER_DEFAULT_SIZE;
123 }
124 
125 static void
go_marker_class_init(GObjectClass * gobject_klass)126 go_marker_class_init (GObjectClass *gobject_klass)
127 {
128 	marker_parent_klass = g_type_class_peek_parent (gobject_klass);
129 }
130 
131 GOMarkerShape
go_marker_shape_from_str(char const * str)132 go_marker_shape_from_str (char const *str)
133 {
134 	unsigned i;
135 	for (i = 0; i < GO_MARKER_MAX; i++)
136 		if (g_ascii_strcasecmp (marker_shapes[i].str, str) == 0)
137 			return (GOMarkerShape)i;
138 	return GO_MARKER_NONE;
139 }
140 
141 char const *
go_marker_shape_as_str(GOMarkerShape shape)142 go_marker_shape_as_str (GOMarkerShape shape)
143 {
144 	return (unsigned)shape >= GO_MARKER_MAX
145 		? "pattern"
146 		: marker_shapes[shape].str;
147 }
148 
149 static void
go_marker_get_paths(GOMarker const * marker,char const ** outline_path,char const ** fill_path)150 go_marker_get_paths (GOMarker const *marker,
151 		     char const **outline_path,
152 		     char const **fill_path)
153 {
154 	*outline_path = marker_shapes[marker->shape].outline_path;
155 	*fill_path = marker_shapes[marker->shape].fill_path;
156 }
157 
158 GOMarkerShape
go_marker_get_shape(GOMarker const * marker)159 go_marker_get_shape (GOMarker const *marker)
160 {
161 	return marker->shape;
162 }
163 
164 void
go_marker_set_shape(GOMarker * marker,GOMarkerShape shape)165 go_marker_set_shape (GOMarker *marker, GOMarkerShape shape)
166 {
167 	g_return_if_fail (GO_IS_MARKER (marker));
168 
169 	if (marker->shape == shape)
170 		return;
171 	marker->shape = shape;
172 }
173 
174 gboolean
go_marker_is_closed_shape(GOMarker const * m)175 go_marker_is_closed_shape (GOMarker const *m)
176 {
177 	g_return_val_if_fail (GO_IS_MARKER (m), FALSE);
178 	switch (m->shape) {
179 	case GO_MARKER_X:
180 	case GO_MARKER_CROSS:
181 	case GO_MARKER_ASTERISK:
182 		return FALSE;
183 	default:
184 		return TRUE;
185 	}
186 }
187 
188 GOColor
go_marker_get_outline_color(GOMarker const * marker)189 go_marker_get_outline_color (GOMarker const *marker)
190 {
191 	return marker->outline_color;
192 }
193 
194 void
go_marker_set_outline_color(GOMarker * marker,GOColor color)195 go_marker_set_outline_color (GOMarker *marker, GOColor color)
196 {
197 	g_return_if_fail (GO_IS_MARKER (marker));
198 	if (marker->outline_color == color)
199 		return;
200 	marker->outline_color = color;
201 }
202 
203 GOColor
go_marker_get_fill_color(GOMarker const * marker)204 go_marker_get_fill_color (GOMarker const *marker)
205 {
206 	return marker->fill_color;
207 }
208 
209 void
go_marker_set_fill_color(GOMarker * marker,GOColor color)210 go_marker_set_fill_color (GOMarker *marker, GOColor color)
211 {
212 	g_return_if_fail (GO_IS_MARKER (marker));
213 	if (marker->fill_color == color)
214 		return;
215 	marker->fill_color = color;
216 }
217 
218 int
go_marker_get_size(GOMarker const * marker)219 go_marker_get_size (GOMarker const *marker)
220 {
221 	return marker->size;
222 }
223 
224 double
go_marker_get_outline_width(GOMarker const * marker)225 go_marker_get_outline_width (GOMarker const *marker)
226 {
227 	return (double)marker->size * MARKER_OUTLINE_WIDTH;
228 }
229 
230 void
go_marker_set_size(GOMarker * marker,int size)231 go_marker_set_size (GOMarker *marker, int size)
232 {
233 	g_return_if_fail (GO_IS_MARKER (marker));
234 	g_return_if_fail (size >= 0);
235 	if (marker->size == size)
236 		return;
237 	marker->size = size;
238 }
239 
240 void
go_marker_assign(GOMarker * dst,GOMarker const * src)241 go_marker_assign (GOMarker *dst, GOMarker const *src)
242 {
243 	if (src == dst)
244 		return;
245 
246 	g_return_if_fail (GO_MARKER (src) != NULL);
247 	g_return_if_fail (GO_MARKER (dst) != NULL);
248 
249 	dst->size		= src->size;
250 	dst->shape		= src->shape;
251 	dst->outline_color	= src->outline_color;
252 	dst->fill_color		= src->fill_color;
253 
254 }
255 
256 /**
257  * go_marker_dup:
258  * @src: the #GOMarker to duplicate
259  *
260  * Duplicates @src.
261  * Returns: (transfer full): the duplicated marker.
262  **/
263 GOMarker *
go_marker_dup(GOMarker const * src)264 go_marker_dup (GOMarker const *src)
265 {
266 	GOMarker *dst = go_marker_new ();
267 	go_marker_assign (dst, src);
268 	return dst;
269 }
270 
271 GOMarker *
go_marker_new(void)272 go_marker_new (void)
273 {
274 	return g_object_new (GO_TYPE_MARKER, NULL);
275 }
276 
277 /**
278  * go_marker_render:
279  * @marker: a #GOMarker
280  * @cr: a cairo context
281  * @x: x position
282  * @y: y position
283  * @scale: current scale
284  *
285  * Renders @marker onto the @cairo target, using @x and @y for the position.
286  **/
287 void
go_marker_render(GOMarker const * marker,cairo_t * cr,double x,double y,double scale)288 go_marker_render (GOMarker const *marker, cairo_t *cr, double x, double y, double scale)
289 {
290 	char const *outline_path_raw, *fill_path_raw;
291 	double half_size;
292 
293 	go_marker_get_paths (marker, &outline_path_raw, &fill_path_raw);
294 
295 	if ((outline_path_raw == NULL) ||
296 	    (fill_path_raw == NULL))
297 		return;
298 
299 	cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
300 	cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
301 
302 	half_size = 0.5 *  scale * go_marker_get_size (marker);
303 
304 	cairo_save (cr);
305 	cairo_translate (cr, x, y);
306 	cairo_scale (cr, half_size, half_size);
307 
308 	cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (go_marker_get_fill_color (marker)));
309 	go_cairo_emit_svg_path (cr, fill_path_raw);
310 	cairo_fill (cr);
311 
312 	cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (go_marker_get_outline_color (marker)));
313 	cairo_set_line_width (cr, 2.0 * MARKER_OUTLINE_WIDTH);
314 	cairo_set_dash (cr, NULL, 0, 0.);
315 	go_cairo_emit_svg_path (cr, outline_path_raw);
316 	cairo_stroke (cr);
317 	cairo_restore (cr);
318 }
319 
320 /**
321  * go_marker_create_cairo_surface:
322  * @marker: a #GOMarker
323  * @cr: a cairo context
324  * @scale: current context scale
325  * @width: a placeholder for the surface width
326  * @height: a placeholder for the surface height
327  *
328  * Creates a new cairo surface similar to the current target of @cr, and render
329  * @marker on it. @center will contain the coordinate of the center of the surface.
330  *
331  * Returns:  a newly created #cairo_surface_t. This surface should be destroyed
332  * 	using cairo_surface_destroy after use.
333  **/
334 cairo_surface_t *
go_marker_create_cairo_surface(GOMarker const * marker,cairo_t * cr,double scale,double * width,double * height)335 go_marker_create_cairo_surface (GOMarker const *marker, cairo_t *cr, double scale,
336 				double *width, double *height)
337 {
338 	cairo_t *cr_tmp;
339 	cairo_surface_t *cr_surface;
340 	cairo_surface_t *current_cr_surface;
341 	double half_size, offset;
342 
343 	g_return_val_if_fail (GO_IS_MARKER (marker), NULL);
344 	g_return_val_if_fail (cr != NULL, NULL);
345 
346 	current_cr_surface = cairo_get_target (cr);
347 
348 	if (go_cairo_surface_is_vector (current_cr_surface)) {
349 		half_size = scale * go_marker_get_size (marker) * 0.5;
350 		offset = half_size + scale * go_marker_get_outline_width (marker);
351 	} else {
352 		half_size = rint (scale * go_marker_get_size (marker)) * 0.5;
353 		offset = ceil (scale * go_marker_get_outline_width (marker) * 0.5) +
354 			half_size + .5;
355 	}
356 
357 	cr_surface = cairo_surface_create_similar (current_cr_surface,
358 						   CAIRO_CONTENT_COLOR_ALPHA,
359 						   ceil (2.0 * offset),
360 						   ceil (2.0 * offset));
361 	cr_tmp = cairo_create (cr_surface);
362 
363 	go_marker_render (marker, cr_tmp, offset, offset, scale);
364 
365 	cairo_destroy (cr_tmp);
366 
367 	if (width != NULL)
368 		*width = offset * 2.0;
369 	if (height != NULL)
370 		*height = offset * 2.0;
371 
372 	return cr_surface;
373 }
374 
375 GSF_CLASS (GOMarker, go_marker,
376 	   go_marker_class_init, go_marker_init,
377 	   G_TYPE_OBJECT)
378