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