Migrate to Guile 2.2
[geda-gaf.git] / libgedacairo / edacairo.c
blob9c861b2c5e815aa1be68b459793d86a6a3ce8d84
1 /* gEDA - GPL Electronic Design Automation
2 * libgedacairo - Rendering gEDA schematics with Cairo
3 * Copyright (C) 1998-2019 gEDA Contributors (see ChangeLog for details)
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
20 #include <config.h>
22 #include <cairo.h>
23 #include <math.h>
25 #include <libgeda/libgeda.h>
26 #include "edacairo.h"
28 /* We don't use gettext */
29 #define _(x) (x)
31 static inline int
32 screen_width (cairo_t *cr, double width)
34 double dummy = 0;
35 cairo_user_to_device_distance (cr, &width, &dummy);
36 if (width < 1)
37 width = 1;
39 return rint (width);
42 static inline int
43 SCREENabs (cairo_t *cr, double dist)
45 double dummy = 0;
46 cairo_user_to_device_distance (cr, &dist, &dummy);
47 return rint (dist);
50 static inline void
51 WORLDtoSCREEN (cairo_t *cr, double wx, double wy, double *sx, double *sy)
53 cairo_user_to_device (cr, &wx, &wy);
54 *sx = round (wx); *sy = round (wy);
57 void
58 eda_cairo_set_source_color (cairo_t *cr, int color, GArray *map)
60 COLOR c;
62 g_return_if_fail (color >= 0);
63 g_return_if_fail (map != NULL);
64 g_return_if_fail ((color >= 0) && (map->len > color));
66 c = g_array_index (map, COLOR, color);
68 cairo_set_source_rgba (cr, (double)c.r / 255.0,
69 (double)c.g / 255.0,
70 (double)c.b / 255.0,
71 (double)c.a / 255.0);
74 void
75 eda_cairo_line (cairo_t *cr, int flags, int line_end,
76 double w_line_width,
77 double w_x1, double w_y1, double w_x2, double w_y2)
79 double x1, y1, x2, y2;
80 int line_width;
81 double offset;
82 double xoffset = 0;
83 double yoffset = 0;
84 double horizontal = 0;
85 double vertical = 0;
87 if (!(flags & EDA_CAIRO_ENABLE_HINTS)) {
88 cairo_move_to (cr, w_x1, w_y1);
89 cairo_line_to (cr, w_x2, w_y2);
90 return;
93 WORLDtoSCREEN (cr, w_x1, w_y1, &x1, &y1);
94 WORLDtoSCREEN (cr, w_x2, w_y2, &x2, &y2);
95 line_width = screen_width (cr, w_line_width);
96 offset = ((line_width % 2) == 0) ? 0 : 0.5;
98 if (y1 == y2) horizontal = 1;
99 if (x1 == x2) vertical = 1;
101 /* Hint so the length of the line runs along a pixel boundary */
103 if (horizontal)
104 yoffset = offset;
105 else if (vertical)
106 xoffset = offset;
107 else
108 xoffset = yoffset = offset;
110 /* Now hint the ends of the lines */
112 switch (line_end) {
113 case END_NONE:
114 /* Line terminates at the passed coordinate */
116 /* Add an extra pixel to give an inclusive span */
117 if (horizontal) {
118 if (x1 > x2) x1 += 1; else x2 += 1;
119 } else if (vertical) {
120 if (y1 > y2) y1 += 1; else y2 += 1;
122 break;
124 case END_SQUARE:
125 case END_ROUND:
126 /* Line terminates half a width away from the passed coordinate */
127 if (horizontal) {
128 xoffset = offset;
129 } else if (vertical) {
130 yoffset = offset;
132 break;
135 x1 += xoffset; y1 += yoffset;
136 x2 += xoffset; y2 += yoffset;
137 cairo_device_to_user (cr, &x1, &y1);
138 cairo_device_to_user (cr, &x2, &y2);
139 cairo_move_to (cr, x1, y1);
140 cairo_line_to (cr, x2, y2);
144 void
145 eda_cairo_box (cairo_t *cr, int flags, double line_width,
146 double x1, double y1, double x2, double y2)
148 int s_line_width;
149 double s_x1, s_y1, s_x2, s_y2;
150 double offset;
152 if (!(flags & EDA_CAIRO_ENABLE_HINTS)) {
153 cairo_rectangle (cr, x1, y1, (x2 - x1), (y2 - y1));
154 return;
157 WORLDtoSCREEN (cr, x1, y1, &s_x1, &s_y1);
158 WORLDtoSCREEN (cr, x2, y2, &s_x2, &s_y2);
159 s_line_width = screen_width (cr, line_width);
160 offset = (line_width == -1 || (s_line_width % 2) == 0) ? 0 : 0.5;
162 /* Allow filled boxes (inferred from line_width == -1)
163 * to touch an extra pixel, so the filled span is inclusive */
164 if (line_width == -1) {
165 if (s_x1 > s_x2) s_x1 += 1; else s_x2 += 1;
166 if (s_y1 > s_y2) s_y1 += 1; else s_y2 += 1;
169 s_x1 += offset; s_y1 += offset;
170 s_x2 += offset; s_y2 += offset;
171 cairo_device_to_user (cr, &s_x1, &s_y1);
172 cairo_device_to_user (cr, &s_x2, &s_y2);
173 cairo_move_to (cr, s_x2, s_y2);
174 cairo_line_to (cr, s_x1, s_y2);
175 cairo_line_to (cr, s_x1, s_y1);
176 cairo_line_to (cr, s_x2, s_y1);
177 cairo_close_path (cr);
181 void
182 eda_cairo_center_box (cairo_t *cr, int flags,
183 double center_width,
184 double line_width, double x, double y,
185 double half_width, double half_height)
187 int s_center_width, s_line_width;
188 int s_width, s_height;
189 double s_half_width, s_half_height;
190 double s_x, s_y;
191 double even_center_width;
192 double even_line_width;
193 double even_width, even_height;
194 double x1, y1, x2, y2;
195 double center_offset;
196 int do_width_hint = TRUE;
197 int do_height_hint = TRUE;
199 if (!(flags & EDA_CAIRO_ENABLE_HINTS)) {
200 cairo_rectangle (cr, (x - half_width), (y - half_height),
201 2*half_width, 2*half_height);
202 return;
205 WORLDtoSCREEN (cr, x, y, &s_x, &s_y);
206 s_width = SCREENabs (cr, 2 * half_width);
207 s_height = SCREENabs (cr, 2 * half_height);
208 even_width = (s_width % 2 == 0);
209 even_height = (s_width % 2 == 0);
210 s_half_width = (double) s_width / 2.;
211 s_half_height = (double) s_height / 2.;
213 #if 0 /* Not as nice an effect as with arcs */
214 /* Switch off radius hinting for small radii. If we don't, then we get
215 * a very abrupt transition once the box reaches a single pixel size. */
216 if (s_half_width <= 1.) do_width_hint = FALSE;
217 if (s_half_height <= 1.) do_height_hint = FALSE;
218 #endif
220 /* Hint the center of the box based on where a line
221 * of thickness center_width (world) would drawn */
222 s_center_width = screen_width (cr, center_width);
223 even_center_width = (center_width == -1 || (s_center_width % 2) == 0);
224 center_offset = even_center_width ? 0. : 0.5;
226 /* Hint the half-widths to land the stroke on the pixel grid */
227 s_line_width = screen_width (cr, line_width);
228 even_line_width = (line_width == -1 || (s_line_width % 2) == 0);
229 if (do_width_hint)
230 s_half_width += ((even_center_width ==
231 even_line_width) == even_width ) ? 0. : 0.5;
232 if (do_height_hint)
233 s_half_height += ((even_center_width ==
234 even_line_width) == even_height) ? 0. : 0.5;
236 x1 = (double) s_x + center_offset - s_half_width;
237 y1 = (double) s_y + center_offset - s_half_height;
238 x2 = (double) s_x + center_offset + s_half_width;
239 y2 = (double) s_y + center_offset + s_half_height;
241 /* Allow filled boxes (inferred from line_width == -1)
242 * to touch an extra pixel, so the filled span is inclusive */
243 if (line_width == -1) {
244 x2 += 1; y2 += 1;
247 cairo_device_to_user (cr, &x1, &y1);
248 cairo_device_to_user (cr, &x2, &y2);
249 cairo_move_to (cr, x2, y2);
250 cairo_line_to (cr, x1, y2);
251 cairo_line_to (cr, x1, y1);
252 cairo_line_to (cr, x2, y1);
253 cairo_close_path (cr);
257 static inline void
258 do_arc (cairo_t *cr, double x, double y, double radius,
259 double start_angle, double sweep_angle)
261 cairo_new_sub_path (cr);
262 if (sweep_angle > 0) {
263 cairo_arc (cr, x, y, radius, start_angle * (M_PI / 180.),
264 (start_angle + sweep_angle) * (M_PI / 180.));
265 } else {
266 cairo_arc_negative (cr, x, y, radius, start_angle * (M_PI / 180.),
267 (start_angle + sweep_angle) * (M_PI / 180.));
272 void
273 eda_cairo_arc (cairo_t *cr, int flags,
274 double width, double x, double y,
275 double radius, double start_angle, double sweep_angle)
277 int s_width;
278 double x1, y1, x2, y2;
279 double s_x, s_y, s_radius;
280 double offset, dummy = 0;
282 if (!(flags & EDA_CAIRO_ENABLE_HINTS)) {
283 do_arc (cr, x, y, radius, start_angle, sweep_angle);
284 return;
287 WORLDtoSCREEN (cr, x - radius, y + radius, &x1, &y1);
288 WORLDtoSCREEN (cr, x + radius, y - radius, &x2, &y2);
289 s_width = screen_width (cr, width);
290 offset = ((s_width % 2) == 0) ? 0 : 0.5;
292 s_x = (double)(x1 + x2) / 2.;
293 s_y = (double)(y1 + y2) / 2.;
294 s_radius = (double)(y2 - y1) / 2.;
296 cairo_device_to_user (cr, &s_x, &s_y);
297 cairo_device_to_user_distance (cr, &offset, &dummy);
298 cairo_device_to_user_distance (cr, &s_radius, &dummy);
300 do_arc (cr, s_x + offset, s_y + offset,
301 s_radius, start_angle, sweep_angle);
305 void
306 eda_cairo_center_arc (cairo_t *cr, int flags,
307 double center_width,
308 double line_width, double x, double y,
309 double radius, double start_angle, double sweep_angle)
311 int s_center_width, s_line_width;
312 double s_x, s_y, dummy = 0;
313 int s_diameter;
314 double even_center_width;
315 double even_line_width;
316 double even_diameter;
317 double center_offset;
318 double s_radius;
319 int do_radius_hint = TRUE;
321 if (!(flags & EDA_CAIRO_ENABLE_HINTS)) {
322 do_arc (cr, x, y, radius, start_angle, sweep_angle);
323 return;
326 WORLDtoSCREEN (cr, x, y, &s_x, &s_y);
327 s_diameter = SCREENabs (cr, 2 * radius);
328 even_diameter = ((s_diameter % 2) == 0);
329 s_radius = (double) s_diameter / 2.;
331 /* Switch off radius hinting for small radii. If we don't, then we get
332 * a very abrupt transition once the arc reaches a single pixel size. */
333 if (s_radius <= 1.) do_radius_hint = FALSE;
335 /* Hint the center of the arc based on where a line
336 * of thickness center_width (world) would drawn */
337 s_center_width = screen_width (cr, center_width);
338 even_center_width = (center_width == -1 || (s_center_width % 2) == 0);
339 center_offset = even_center_width ? 0. : 0.5;
341 /* Hint the radius to land its extermity on the pixel grid */
342 s_line_width = screen_width (cr, line_width);
343 even_line_width = (line_width == -1 || (s_line_width % 2) == 0);
344 if (do_radius_hint)
345 s_radius += ((even_center_width ==
346 even_line_width) == even_diameter) ? 0. : 0.5;
348 s_x += center_offset;
349 s_y += center_offset;
350 cairo_device_to_user (cr, &s_x, &s_y);
351 cairo_device_to_user_distance (cr, &s_radius, &dummy);
353 do_arc (cr, s_x, s_y, s_radius, start_angle, sweep_angle);
357 void
358 eda_cairo_stroke (cairo_t *cr, int flags, int line_type, int line_end,
359 double wwidth, double wlength, double wspace)
361 double offset = 0;
362 double dashes[4];
363 double dummy = 0;
364 cairo_line_cap_t cap;
365 cairo_line_cap_t round_cap_if_legible = CAIRO_LINE_CAP_ROUND;
366 int num_dashes;
367 int iwidth;
368 double width = wwidth, length = wlength, space = wspace;
370 if (flags & EDA_CAIRO_ENABLE_HINTS) {
371 width = iwidth = screen_width (cr, wwidth);
372 length = screen_width (cr, wlength);
373 space = screen_width (cr, wspace);
374 cairo_device_to_user_distance (cr, &width, &dummy);
375 cairo_device_to_user_distance (cr, &length, &dummy);
376 cairo_device_to_user_distance (cr, &space, &dummy);
378 offset = ((iwidth % 2) == 0) ? 0 : 0.5;
380 round_cap_if_legible =
381 (iwidth <= 1) ? CAIRO_LINE_CAP_SQUARE : CAIRO_LINE_CAP_ROUND;
384 cairo_set_line_width (cr, width);
385 cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
387 switch (line_end) {
388 case END_NONE: cap = CAIRO_LINE_CAP_BUTT; break;
389 case END_SQUARE: cap = CAIRO_LINE_CAP_SQUARE; break;
390 case END_ROUND: cap = round_cap_if_legible; break;
391 default:
392 g_warn_if_reached ();
393 cap = CAIRO_LINE_CAP_BUTT;
394 break;
397 switch (line_type) {
399 default:
400 g_warn_if_reached ();
401 /* Fall through */
403 case TYPE_SOLID:
404 num_dashes = 0;
406 cairo_set_dash (cr, dashes, num_dashes, 0.);
407 cairo_set_line_cap (cr, cap);
408 cairo_stroke (cr);
409 break;
411 case TYPE_DOTTED:
412 dashes[0] = 0; /* DOT */
413 dashes[1] = space;
414 num_dashes = 2;
416 cairo_set_dash (cr, dashes, num_dashes, offset);
417 cairo_set_line_cap (cr, round_cap_if_legible);
418 cairo_stroke (cr);
419 break;
421 case TYPE_DASHED:
422 dashes[0] = length; /* DASH */
423 dashes[1] = space;
424 num_dashes = 2;
426 cairo_set_dash (cr, dashes, num_dashes, 0.);
427 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
428 cairo_stroke (cr);
429 break;
431 case TYPE_CENTER:
432 dashes[0] = length; /* DASH */
433 dashes[1] = 2 * space;
434 num_dashes = 2;
436 cairo_set_dash (cr, dashes, num_dashes, 0.);
437 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
438 cairo_stroke_preserve (cr);
440 dashes[0] = 0; /* DOT */
441 dashes[1] = 2 * space + length;
442 num_dashes = 2;
444 cairo_set_dash (cr, dashes, num_dashes, -length - space + offset);
445 cairo_set_line_cap (cr, round_cap_if_legible);
446 cairo_stroke (cr);
447 break;
449 case TYPE_PHANTOM:
450 dashes[0] = length; /* DASH */
451 dashes[1] = 3 * space;
452 num_dashes = 2;
454 cairo_set_dash (cr, dashes, num_dashes, 0.);
455 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
456 cairo_stroke_preserve (cr);
458 dashes[0] = 0; /* DOT */
459 dashes[1] = space;
460 dashes[2] = 0; /* DOT */
461 dashes[3] = 2 * space + length;
462 num_dashes = 4;
464 cairo_set_dash (cr, dashes, num_dashes, -length - space + offset);
465 cairo_set_line_cap (cr, round_cap_if_legible);
466 cairo_stroke (cr);
467 break;
470 cairo_set_dash (cr, NULL, 0, 0.);
473 static inline void
474 eda_cairo_path_hint (cairo_t *cr, int flags,
475 double *x, double *y, int width)
477 double offset;
478 if (flags & EDA_CAIRO_ENABLE_HINTS) {
479 cairo_user_to_device (cr, x, y);
480 offset = ((width % 2) == 0) ? 0 : 0.5;
481 *x += offset; *y += offset;
482 cairo_device_to_user (cr, x, y);
486 void
487 eda_cairo_path (cairo_t *cr, int flags, double line_width,
488 int nsections, PATH_SECTION *sections)
490 int i;
491 int s_line_width;
492 double dummy = 0;
494 if (flags & EDA_CAIRO_ENABLE_HINTS) {
495 s_line_width = screen_width (cr, line_width);
496 } else {
497 cairo_user_to_device (cr, &line_width, &dummy);
498 s_line_width = line_width;
501 for (i = 0; i < nsections; i++) {
502 PATH_SECTION *section = sections + i;
503 double x1 = section->x1;
504 double x2 = section->x2;
505 double x3 = section->x3;
506 double y1 = section->y1;
507 double y2 = section->y2;
508 double y3 = section->y3;
510 switch (section->code) {
511 case PATH_CURVETO:
512 /* Two control point grips */
513 eda_cairo_path_hint (cr, flags, &x1, &y1, s_line_width);
514 eda_cairo_path_hint (cr, flags, &x2, &y2, s_line_width);
515 /* Fall through */
516 case PATH_MOVETO:
517 case PATH_MOVETO_OPEN:
518 case PATH_LINETO:
519 /* Destination point grip */
520 eda_cairo_path_hint (cr, flags, &x3, &y3, s_line_width);
521 case PATH_END:
522 break;
525 switch (section->code) {
526 case PATH_MOVETO:
527 cairo_close_path (cr);
528 /* fall-through */
529 case PATH_MOVETO_OPEN:
530 cairo_move_to (cr, x3, y3);
531 break;
532 case PATH_CURVETO:
533 cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
534 break;
535 case PATH_LINETO:
536 cairo_line_to (cr, x3, y3);
537 break;
538 case PATH_END:
539 cairo_close_path (cr);
540 break;