1 /*
2  * go-line.c :
3  *
4  * Copyright (C) 2004-2006 Emmanuel Pacaud (emmanuel.pacaud@univ-poitiers.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 <string.h>
26 #include <glib/gi18n-lib.h>
27 
28 /**
29  * GOLineDashType:
30  * @GO_LINE_NONE: No line displayed.
31  * @GO_LINE_SOLID: Solid line.
32  * @GO_LINE_S_DOT:
33  * @GO_LINE_S_DASH_DOT:
34  * @GO_LINE_S_DASH_DOT_DOT:
35  * @GO_LINE_DASH_DOT_DOT_DOT:
36  * @GO_LINE_DOT:
37  * @GO_LINE_S_DASH:
38  * @GO_LINE_DASH:
39  * @GO_LINE_LONG_DASH:
40  * @GO_LINE_DASH_DOT:
41  * @GO_LINE_DASH_DOT_DOT:
42  * @GO_LINE_MAX:
43  **/
44 
45 /**
46  * GOLineInterpolation:
47  * @GO_LINE_INTERPOLATION_LINEAR: Linear interpolation.
48  * @GO_LINE_INTERPOLATION_SPLINE: Bezier cubic spline interpolation.
49  * @GO_LINE_INTERPOLATION_CLOSED_SPLINE: Closed Bezier cubic spline interpolation.
50  * @GO_LINE_INTERPOLATION_ODF_SPLINE: ODF compatible Bezier cubic spline interpolation, cyclic if first and last points are identical.
51  * @GO_LINE_INTERPOLATION_CUBIC_SPLINE: Cubic spline interpolation with natural limits.
52  * @GO_LINE_INTERPOLATION_PARABOLIC_CUBIC_SPLINE: Cubic spline interpolation with parabolic limits.
53  * @GO_LINE_INTERPOLATION_CUBIC_CUBIC_SPLINE: Cubic spline interpolation with cubic limits.
54  * @GO_LINE_INTERPOLATION_CLAMPED_CUBIC_SPLINE: Cubic spline interpolation with fixed derivatives at both ends.
55  * @GO_LINE_INTERPOLATION_STEP_START: Steps using first y value.
56  * @GO_LINE_INTERPOLATION_STEP_END: Steps using last y value.
57  * @GO_LINE_INTERPOLATION_STEP_CENTER_X: Steps centered around each point.
58  * @GO_LINE_INTERPOLATION_STEP_CENTER_Y: Steps using mean y value.
59  * @GO_LINE_INTERPOLATION_MAX: First invalid value.
60  **/
61 
62 /**
63  * GOLineDashSequence:
64  * @offset: offset from start.
65  * @n_dash: number of values in dash fields
66  * @dash: lengths of the dashes segments. See cairo_set_dash() for details.
67  **/
68 
69 /**
70  * GOArrowType:
71  * @GO_ARROW_NONE: no arrow head.
72  * @GO_ARROW_KITE: kite head.
73  * @GO_ARROW_OVAL: oval head.
74  **/
75 
76 /**
77  * GOArrow:
78  * @typ: #GOArrowType.
79  * @a: first arrow head size parameter.
80  * @b: second arrow head size parameter.
81  * @c: third arrow head size parameter.
82  **/
83 
84 static GOLineDashSequence *
go_line_dash_sequence_ref(GOLineDashSequence * sequence)85 go_line_dash_sequence_ref (GOLineDashSequence * sequence)
86 {
87 	sequence->ref_count++;
88 	return sequence;
89 }
90 
91 GType
go_line_dash_sequence_get_type(void)92 go_line_dash_sequence_get_type (void)
93 {
94 	static GType t = 0;
95 
96 	if (t == 0) {
97 		t = g_boxed_type_register_static ("GOLineDashSequence",
98 			 (GBoxedCopyFunc)go_line_dash_sequence_ref,
99 			 (GBoxedFreeFunc)go_line_dash_sequence_free);
100 	}
101 	return t;
102 }
103 
104 typedef struct {
105 	int 		 n_dash;
106 	double		 length;
107 	double 		 dash[8];
108 } GOLineDashDesc;
109 
110 static GOLineDashDesc const line_short_dot_desc = 		{2, 10,	{ 0, 2 } };
111 static GOLineDashDesc const line_dot_desc = 			{2, 12,	{ 3, 3 } };
112 static GOLineDashDesc const line_short_dash_desc =		{2, 9,	{ 6, 3 } };
113 static GOLineDashDesc const line_short_dash_dot_desc =		{4, 12,	{ 6, 3, 0, 3 } };
114 static GOLineDashDesc const line_short_dash_dot_dot_desc =    	{6, 15,	{ 6, 3, 0, 3, 0, 3 } };
115 static GOLineDashDesc const line_dash_dot_dot_dot_desc =    	{8, 21,	{ 9, 3, 0, 3, 0, 3, 0, 3 } };
116 static GOLineDashDesc const line_dash_dot_desc =		{4, 24,	{ 9, 6, 3, 6 } };
117 static GOLineDashDesc const line_dash_dot_dot_desc =    	{6, 24,	{ 9, 3, 3, 3, 3, 3 } };
118 static GOLineDashDesc const line_dash_desc =			{2, 16,	{ 12, 4 } };
119 static GOLineDashDesc const line_long_dash_desc =		{2, 22,	{ 18, 4 } };
120 
121 static struct {
122 	GOLineDashType type;
123 	char const *label;
124 	char const *name;
125 	GOLineDashDesc const *dash_desc;
126 } line_dashes[GO_LINE_MAX] = {
127 	{ GO_LINE_NONE,			N_("None"),
128 		"none",			NULL },
129 	{ GO_LINE_SOLID,		N_("Solid"),
130 		"solid",		NULL },
131 	{ GO_LINE_S_DOT,		N_("Dot"),
132 		"s-dot",		&line_short_dot_desc},
133 	{ GO_LINE_S_DASH_DOT,		N_("Dash dot"),
134 		"s-dash-dot",		&line_short_dash_dot_desc },
135 	{ GO_LINE_S_DASH_DOT_DOT,	N_("Dash dot dot"),
136 		"s-dash-dot-dot",	&line_short_dash_dot_dot_desc },
137 	{ GO_LINE_DASH_DOT_DOT_DOT,	N_("Dash dot dot dot"),
138 		"dash-dot-dot-dot",	&line_dash_dot_dot_dot_desc },
139 	{ GO_LINE_DOT,			N_("Short dash"),
140 		"dot",			&line_dot_desc},
141 	{ GO_LINE_S_DASH,		N_("Dash"),
142 		"s-dash",		&line_short_dash_desc },
143 	{ GO_LINE_DASH,			N_("Long dash"),
144 		"dash",			&line_dash_desc },
145 	{ GO_LINE_LONG_DASH,		N_("Very long dash"),
146 		"l-dash",		&line_long_dash_desc },
147 	{ GO_LINE_DASH_DOT,		N_("Long dash dash"),
148 		"dash-dot",		&line_dash_dot_desc },
149 	{ GO_LINE_DASH_DOT_DOT,		N_("Long dash dash dash"),
150 		"dash-dot-dot",		&line_dash_dot_dot_desc }
151 };
152 
153 static struct {
154 	GOLineInterpolation type;
155 	char const *label;
156 	char const *name;
157 	gboolean supports_radial;
158 	gboolean auto_skip;
159 } line_interpolations[GO_LINE_INTERPOLATION_MAX] =
160 {
161 	{ GO_LINE_INTERPOLATION_LINEAR,			N_("Linear"),
162 		"linear",		TRUE,  FALSE },
163 	{ GO_LINE_INTERPOLATION_SPLINE,			N_("Bezier cubic spline"),
164 		"spline",		TRUE,  FALSE },
165 	{ GO_LINE_INTERPOLATION_CLOSED_SPLINE,		N_("Closed Bezier cubic spline"),
166 		"closed-spline",	TRUE,  TRUE },
167 	{ GO_LINE_INTERPOLATION_ODF_SPLINE,		N_("ODF compatible Bezier cubic spline"),
168 		"odf-spline",		TRUE,  FALSE },
169 	{ GO_LINE_INTERPOLATION_CUBIC_SPLINE,		N_("Natural cubic spline"),
170 		"cspline",		FALSE, FALSE },
171 	{ GO_LINE_INTERPOLATION_PARABOLIC_CUBIC_SPLINE,	N_("Cubic spline with parabolic extrapolation"),
172 		"parabolic-cspline",	FALSE, FALSE },
173 	{ GO_LINE_INTERPOLATION_CUBIC_CUBIC_SPLINE,	N_("Cubic spline with cubic extrapolation"),
174 		"cubic-cspline",	FALSE, FALSE },
175 	{ GO_LINE_INTERPOLATION_CLAMPED_CUBIC_SPLINE,   N_("Clamped cubic spline"),
176 		"clamped-cspline",	FALSE, TRUE },
177 	{ GO_LINE_INTERPOLATION_STEP_START,		N_("Step at start"),
178 		"step-start",		TRUE,  FALSE },
179 	{ GO_LINE_INTERPOLATION_STEP_END,		N_("Step at end"),
180 		"step-end",		TRUE,  FALSE },
181 	{ GO_LINE_INTERPOLATION_STEP_CENTER_X,		N_("Step at center"),
182 		"step-center-x",	TRUE,  FALSE },
183 	{ GO_LINE_INTERPOLATION_STEP_CENTER_Y,		N_("Step to mean"),
184 		"step-center-y",	TRUE,  FALSE }
185 };
186 
187 /**
188  * go_line_dash_from_str:
189  * @name: Name of the dash type
190  *
191  * Returns: a #GOLineDashType corresponding to name, or %GO_LINE_NONE
192  * 	if not found.
193  **/
194 GOLineDashType
go_line_dash_from_str(char const * name)195 go_line_dash_from_str (char const *name)
196 {
197 	unsigned i;
198 	GOLineDashType ret = GO_LINE_NONE;
199 
200 	for (i = 0; i < GO_LINE_MAX; i++) {
201 		if (strcmp (line_dashes[i].name, name) == 0) {
202 			ret = line_dashes[i].type;
203 			break;
204 		}
205 	}
206 	return ret;
207 }
208 
209 /**
210  * go_line_dash_as_str:
211  * @type: a #GOLineDashType
212  *
213  * Returns: a pointer to the nickname of the dash type, or "none" if
214  * 	type is invalid. The returning string should not be freed.
215  **/
216 char const *
go_line_dash_as_str(GOLineDashType type)217 go_line_dash_as_str (GOLineDashType type)
218 {
219 	unsigned i;
220 	char const *ret = "none";
221 
222 	for (i = 0; i < GO_LINE_MAX; i++) {
223 		if (line_dashes[i].type == type) {
224 			ret = line_dashes[i].name;
225 			break;
226 		}
227 	}
228 	return ret;
229 }
230 
231 /**
232  * go_line_dash_as_label:
233  * @type: a #GOLineDashType
234  *
235  * Returns: a pointer to the user readable name of the dash type,
236  * 	or the name of %GO_LINE_NONE if type is invalid. The returned
237  * 	string should not be freed.
238  **/
239 char const *
go_line_dash_as_label(GOLineDashType type)240 go_line_dash_as_label (GOLineDashType type)
241 {
242 	unsigned i;
243 	char const *ret = line_dashes[0].label;
244 
245 	for (i = 0; i < GO_LINE_MAX; i++) {
246 		if (line_dashes[i].type == type) {
247 			ret = line_dashes[i].label;
248 			break;
249 		}
250 	}
251 	return _(ret);
252 }
253 
254 /**
255  * go_line_dash_get_length:
256  * @type: #GOLineDashType
257  *
258  * Returns: the unscaled length of the dash sequence.
259  **/
260 double
go_line_dash_get_length(GOLineDashType type)261 go_line_dash_get_length (GOLineDashType type)
262 {
263 	GOLineDashDesc const *dash_desc;
264 
265 	if ((unsigned)type >= G_N_ELEMENTS (line_dashes))
266 		return 1.0;
267 
268 	dash_desc = line_dashes[type].dash_desc;
269 	return dash_desc != NULL ? dash_desc->length : 1.0;
270 }
271 
272 /**
273  * go_line_dash_get_sequence:
274  * @type: a #GOLineDashType
275  * @scale: dash scale
276  *
277  * Returns: a struct containing the dash sequence corresponding to @type,
278  * 	or %NULL if type is invalid or equal to %GO_LINE_NONE.
279  * 	The lengths are scaled according to @scale.
280  **/
281 GOLineDashSequence *
go_line_dash_get_sequence(GOLineDashType type,double scale)282 go_line_dash_get_sequence (GOLineDashType type, double scale)
283 {
284 	unsigned int i;
285 	GOLineDashSequence *sequence = NULL;
286 	GOLineDashDesc const *dash_desc;
287 
288 	if ((unsigned)type >= G_N_ELEMENTS (line_dashes))
289 		return NULL;
290 
291 	dash_desc = line_dashes[type].dash_desc;
292 	if (dash_desc != NULL) {
293 		sequence = g_new (GOLineDashSequence, 1);
294 		sequence->offset = 0.0;
295 		sequence->n_dash = dash_desc->n_dash;
296 		sequence->dash = g_new (double, sequence->n_dash);
297 		for (i = 0; i < sequence->n_dash; i++)
298 			sequence->dash[i] = scale * dash_desc->dash[i];
299 		sequence->ref_count = 1;
300 	}
301 
302 	return sequence;
303 }
304 
305 /**
306  * go_line_dash_sequence_free:
307  * @sequence: a #GOLineDashSequence
308  *
309  * Frees the dash sequence struct.
310  **/
311 void
go_line_dash_sequence_free(GOLineDashSequence * sequence)312 go_line_dash_sequence_free (GOLineDashSequence *sequence)
313 {
314 	if (sequence == NULL || sequence->ref_count-- > 1)
315 		return;
316 	g_free (sequence->dash);
317 	g_free (sequence);
318 }
319 
320 /**
321  * go_line_interpolation_from_str:
322  * @name: an interpolation type nickname
323  *
324  * Returns: a #GOLineInterpolation corresponding to @name, or
325  * 	%GO_LINE_INTERPOLATION_LINEAR if not found.
326  **/
327 GOLineInterpolation
go_line_interpolation_from_str(char const * name)328 go_line_interpolation_from_str (char const *name)
329 {
330 	unsigned i;
331 	GOLineInterpolation ret = GO_LINE_INTERPOLATION_LINEAR;
332 
333 	for (i = 0; i < GO_LINE_INTERPOLATION_MAX; i++) {
334 		if (strcmp (line_interpolations[i].name, name) == 0) {
335 			ret = line_interpolations[i].type;
336 			break;
337 		}
338 	}
339 	return ret;
340 }
341 
342 /**
343  * go_line_interpolation_as_str:
344  * @type: an interpolation type
345  *
346  * Returns: a pointer to the nickname of @type, or "linear" if type
347  * 	is invalid. The returned string should not be freed.
348  **/
349 char const *
go_line_interpolation_as_str(GOLineInterpolation type)350 go_line_interpolation_as_str (GOLineInterpolation type)
351 {
352 	unsigned i;
353 	char const *ret = "linear";
354 
355 	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
356 		if (line_interpolations[i].type == type) {
357 			ret = line_interpolations[i].name;
358 			break;
359 		}
360 	}
361 	return ret;
362 }
363 
364 /**
365  * go_line_interpolation_as_label:
366  * @type: an interpolation type
367  *
368  * Returns: a pointer to the label of @type, or the name of
369  * %GO_LINE_INTERPOLATION_LINEAR if type is invalid.
370  * The returned string should not be freed.
371  **/
372 char const *
go_line_interpolation_as_label(GOLineInterpolation type)373 go_line_interpolation_as_label (GOLineInterpolation type)
374 {
375 	unsigned i;
376 	char const *ret = _("Linear");
377 
378 	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
379 		if (line_interpolations[i].type == type) {
380 			ret = _(line_interpolations[i].label);
381 			break;
382 		}
383 	}
384 	return ret;
385 }
386 
387 /**
388  * go_line_interpolation_supports_radial:
389  * @type: an interpolation type
390  *
391  * Returns: TRUE if the line interpolation type can be used with radial
392  * axes set, FALSE if it can't.
393  **/
394 gboolean
go_line_interpolation_supports_radial(GOLineInterpolation type)395 go_line_interpolation_supports_radial (GOLineInterpolation type)
396 {
397 	unsigned i;
398 
399 	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
400 		if (line_interpolations[i].type == type) {
401 			return line_interpolations[i].supports_radial;
402 		}
403 	}
404 	return FALSE;
405 }
406 
407 /**
408  * go_line_interpolation_auto_skip:
409  * @type: an interpolation type
410  *
411  * Returns: TRUE if the line interpolation type forces skipping invalid
412  * data, FALSE if it is only optional.
413  **/
414 gboolean
go_line_interpolation_auto_skip(GOLineInterpolation type)415 go_line_interpolation_auto_skip	(GOLineInterpolation type)
416 {
417 	unsigned i;
418 
419 	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
420 		if (line_interpolations[i].type == type) {
421 			return line_interpolations[i].auto_skip;
422 		}
423 	}
424 	return FALSE;
425 }
426 
427 /* ------------------------------------------------------------------------- */
428 
429 static struct {
430 	GOArrowType typ;
431 	char const *name;
432 } arrow_types[] = {
433 	{ GO_ARROW_NONE, "none" },
434 	{ GO_ARROW_KITE, "kite" },
435 	{ GO_ARROW_OVAL, "oval" }
436 };
437 
438 char const *
go_arrow_type_as_str(GOArrowType typ)439 go_arrow_type_as_str (GOArrowType typ)
440 {
441 	unsigned ui;
442 
443 	for (ui = 0; ui < G_N_ELEMENTS (arrow_types); ui++)
444 		if (typ == arrow_types[ui].typ)
445 			return arrow_types[ui].name;
446 
447 	return NULL;
448 }
449 
450 GOArrowType
go_arrow_type_from_str(const char * name)451 go_arrow_type_from_str (const char *name)
452 {
453 	unsigned ui;
454 	GOArrowType ret = GO_ARROW_NONE;
455 
456 	for (ui = 0; ui < G_N_ELEMENTS (arrow_types); ui++) {
457 		if (strcmp (arrow_types[ui].name, name) == 0) {
458 			ret = arrow_types[ui].typ;
459 			break;
460 		}
461 	}
462 
463 	return ret;
464 }
465 
466 GType
go_arrow_get_type(void)467 go_arrow_get_type (void)
468 {
469 	static GType t = 0;
470 
471 	if (t == 0) {
472 		t = g_boxed_type_register_static ("GOArrow",
473 			 (GBoxedCopyFunc)go_arrow_dup,
474 			 (GBoxedFreeFunc)g_free);
475 	}
476 	return t;
477 }
478 
479 void
go_arrow_init(GOArrow * res,GOArrowType typ,double a,double b,double c)480 go_arrow_init (GOArrow *res, GOArrowType typ,
481 	       double a, double b, double c)
482 {
483 	res->typ = typ;
484 	res->a = a;
485 	res->b = b;
486 	res->c = c;
487 }
488 
489 void
go_arrow_clear(GOArrow * dst)490 go_arrow_clear (GOArrow *dst)
491 {
492 	go_arrow_init (dst, GO_ARROW_NONE, 0, 0, 0);
493 }
494 
495 void
go_arrow_init_kite(GOArrow * dst,double a,double b,double c)496 go_arrow_init_kite (GOArrow *dst, double a, double b, double c)
497 {
498 	go_arrow_init (dst, GO_ARROW_KITE, a, b, c);
499 }
500 
501 void
go_arrow_init_oval(GOArrow * dst,double ra,double rb)502 go_arrow_init_oval (GOArrow *dst, double ra, double rb)
503 {
504 	go_arrow_init (dst, GO_ARROW_OVAL, ra, rb, 0);
505 }
506 
507 GOArrow *
go_arrow_dup(GOArrow * src)508 go_arrow_dup (GOArrow *src)
509 {
510 	return g_memdup (src, sizeof (*src));
511 }
512 
513 gboolean
go_arrow_equal(const GOArrow * a,const GOArrow * b)514 go_arrow_equal (const GOArrow *a, const GOArrow *b)
515 {
516 	g_return_val_if_fail (a != NULL, FALSE);
517 	g_return_val_if_fail (b != NULL, FALSE);
518 
519 	if (a->typ != b->typ)
520 		return FALSE;
521 
522 	switch (a->typ) {
523 	default:
524 		g_assert_not_reached ();
525 	case GO_ARROW_NONE:
526 		return TRUE;
527 
528 	case GO_ARROW_KITE:
529 		if (a->c != b->c)
530 			return FALSE;
531 		/* fall through */
532 	case GO_ARROW_OVAL:
533 		return (a->a == b->a && a->b == b->b);
534 	}
535 }
536 
537 
538 /**
539  * go_arrow_draw:
540  * @arrow: arrow to draw
541  * @cr: cairo surface to draw on
542  * @dx: (out): suggested change of line end-point
543  * @dy: (out): suggested change of line end-point
544  * @phi: angle to draw at
545  *
546  **/
547 void
go_arrow_draw(const GOArrow * arrow,cairo_t * cr,double * dx,double * dy,double phi)548 go_arrow_draw (const GOArrow *arrow, cairo_t *cr,
549 	       double *dx, double *dy, double phi)
550 {
551 	if (dx) *dx = 0;
552 	if (dy) *dy = 0;
553 
554 	switch (arrow->typ) {
555 	case GO_ARROW_NONE:
556 		return;
557 
558 	case GO_ARROW_KITE:
559 		cairo_rotate (cr, phi);
560 		cairo_set_line_width (cr, 1.0);
561 		cairo_new_path (cr);
562 		cairo_move_to (cr, 0.0, 0.0);
563 		cairo_line_to (cr, -arrow->c, -arrow->b);
564 		cairo_line_to (cr, 0.0, -arrow->a);
565 		cairo_line_to (cr, arrow->c, -arrow->b);
566 		cairo_close_path (cr);
567 		cairo_fill (cr);
568 
569 		/*
570 		 * Make the line shorter so that the arrow won't be on top
571 		 * of a (perhaps quite fat) line.
572 		 */
573 		if (dx) *dx = +arrow->a * sin (phi);
574 	        if (dy) *dy = -arrow->a * cos (phi);
575 		break;
576 
577 	case GO_ARROW_OVAL:
578 		if (arrow->a > 0 && arrow->b > 0) {
579 			cairo_rotate (cr, phi);
580 			cairo_scale (cr, arrow->a, arrow->b);
581 			cairo_arc (cr, 0., 0., 1., 0., 2 * M_PI);
582 			cairo_fill (cr);
583 		}
584 		break;
585 	}
586 }
587