1 /* gEDA - GPL Electronic Design Automation
2  * gschem - gEDA Schematic Capture
3  * Copyright (C) 1998-2010 Ales Hvezda
4  * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
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 Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 
22 #include <config.h>
23 
24 #include <cairo.h>
25 #include <math.h>
26 
27 #include "gschem.h"
28 
29 #ifdef HAVE_LIBDMALLOC
30 #include <dmalloc.h>
31 #endif
32 
33 
screen_width(GSCHEM_TOPLEVEL * w_current,int w_width)34 static inline int screen_width (GSCHEM_TOPLEVEL *w_current, int w_width)
35 {
36   int width = SCREENabs (w_current, w_width);
37   if (width < 1)
38     width = 1;
39 
40   return width;
41 }
42 
gschem_cairo_line(GSCHEM_TOPLEVEL * w_current,int line_end,int w_line_width,int w_x1,int w_y1,int w_x2,int w_y2)43 void gschem_cairo_line (GSCHEM_TOPLEVEL *w_current, int line_end,
44                         int w_line_width,
45                         int w_x1, int w_y1, int w_x2, int w_y2)
46 {
47   int x1, y1, x2, y2, line_width;
48   double offset;
49   double xoffset = 0;
50   double yoffset = 0;
51   int horizontal = 0;
52   int vertical = 0;
53 
54   WORLDtoSCREEN (w_current, w_x1, w_y1, &x1, &y1);
55   WORLDtoSCREEN (w_current, w_x2, w_y2, &x2, &y2);
56   line_width = screen_width (w_current, w_line_width);
57   offset = ((line_width % 2) == 0) ? 0 : 0.5;
58 
59   if (y1 == y2) horizontal = 1;
60   if (x1 == x2) vertical = 1;
61 
62   /* Hint so the length of the line runs along a pixel boundary */
63 
64   if (horizontal)
65     yoffset = offset;
66   else if (vertical)
67     xoffset = offset;
68   else
69     xoffset = yoffset = offset;
70 
71   /* Now hint the ends of the lines */
72 
73   switch (line_end) {
74     case END_NONE:
75       /* Line terminates at the passed coordinate */
76 
77       /* Add an extra pixel to give an inclusive span */
78       if (horizontal) {
79         if (x1 > x2) x1 += 1; else x2 += 1;
80       } else if (vertical) {
81         if (y1 > y2) y1 += 1; else y2 += 1;
82       }
83       break;
84 
85     case END_SQUARE:
86     case END_ROUND:
87       /* Line terminates half a width away from the passed coordinate */
88       if (horizontal) {
89         xoffset = offset;
90       } else if (vertical) {
91         yoffset = offset;
92       }
93       break;
94   }
95 
96   cairo_move_to (w_current->cr, x1 + xoffset, y1 + yoffset);
97   cairo_line_to (w_current->cr, x2 + xoffset, y2 + yoffset);
98 }
99 
100 
gschem_cairo_box(GSCHEM_TOPLEVEL * w_current,int line_width,int x1,int y1,int x2,int y2)101 void gschem_cairo_box (GSCHEM_TOPLEVEL *w_current, int line_width,
102                        int x1, int y1, int x2, int y2)
103 {
104   int s_line_width;
105   int s_x1, s_y1, s_x2, s_y2;
106   double offset;
107 
108   WORLDtoSCREEN (w_current, x1, y1, &s_x1, &s_y1);
109   WORLDtoSCREEN (w_current, x2, y2, &s_x2, &s_y2);
110   s_line_width = screen_width (w_current, line_width);
111   offset = (line_width == -1 || (s_line_width % 2) == 0) ? 0 : 0.5;
112 
113   /* Allow filled boxes (inferred from line_width == -1)
114    * to touch an extra pixel, so the filled span is inclusive */
115   if (line_width == -1) {
116     if (x1 > x2) x1 += 1; else x2 += 1;
117     if (y1 > y2) y1 += 1; else y2 += 1;
118   }
119 
120   cairo_move_to (w_current->cr, s_x2 + offset, s_y2 + offset);
121   cairo_line_to (w_current->cr, s_x1 + offset, s_y2 + offset);
122   cairo_line_to (w_current->cr, s_x1 + offset, s_y1 + offset);
123   cairo_line_to (w_current->cr, s_x2 + offset, s_y1 + offset);
124   cairo_close_path (w_current->cr);
125 }
126 
127 
gschem_cairo_center_box(GSCHEM_TOPLEVEL * w_current,int center_width,int line_width,int x,int y,int half_width,int half_height)128 void gschem_cairo_center_box (GSCHEM_TOPLEVEL *w_current,
129                               int center_width,
130                               int line_width, int x, int y,
131                               int half_width, int half_height)
132 {
133   int s_center_width, s_line_width;
134   int s_width, s_height;
135   double s_half_width, s_half_height;
136   int s_x, s_y;
137   int even_center_width;
138   int even_line_width;
139   int even_width, even_height;
140   double x1, y1, x2, y2;
141   double center_offset;
142   int do_width_hint = TRUE;
143   int do_height_hint = TRUE;
144 
145   WORLDtoSCREEN (w_current, x, y, &s_x, &s_y);
146   s_width  = SCREENabs (w_current, 2 * half_width);
147   s_height = SCREENabs (w_current, 2 * half_height);
148   even_width  = (s_width % 2 == 0);
149   even_height = (s_width % 2 == 0);
150   s_half_width  = (double) s_width  / 2.;
151   s_half_height = (double) s_height / 2.;
152 
153 #if 0 /* Not as nice an effect as with arcs */
154   /* Switch off radius hinting for small radii. If we don't, then we get
155    * a very abrupt transition once the box reaches a single pixel size. */
156   if (s_half_width  <= 1.)  do_width_hint  = FALSE;
157   if (s_half_height <= 1.)  do_height_hint = FALSE;
158 #endif
159 
160   /* Hint the center of the box based on where a line
161    * of thickness center_width (world) would drawn */
162   s_center_width = screen_width (w_current, center_width);
163   even_center_width = (center_width == -1 || (s_center_width % 2) == 0);
164   center_offset = even_center_width ? 0. : 0.5;
165 
166   /* Hint the half-widths to land the stroke on the pixel grid */
167   s_line_width = screen_width (w_current, line_width);
168   even_line_width = (line_width == -1 || (s_line_width % 2) == 0);
169   if (do_width_hint)
170     s_half_width  += ((even_center_width ==
171                              even_line_width) == even_width ) ? 0. : 0.5;
172   if (do_height_hint)
173     s_half_height += ((even_center_width ==
174                              even_line_width) == even_height) ? 0. : 0.5;
175 
176   x1 = (double) s_x + center_offset - s_half_width;
177   y1 = (double) s_y + center_offset - s_half_height;
178   x2 = (double) s_x + center_offset + s_half_width;
179   y2 = (double) s_y + center_offset + s_half_height;
180 
181   /* Allow filled boxes (inferred from line_width == -1)
182    * to touch an extra pixel, so the filled span is inclusive */
183   if (line_width == -1) {
184     x2 += 1;  y2 += 1;
185   }
186 
187   cairo_move_to (w_current->cr, x2, y2);
188   cairo_line_to (w_current->cr, x1, y2);
189   cairo_line_to (w_current->cr, x1, y1);
190   cairo_line_to (w_current->cr, x2, y1);
191   cairo_close_path (w_current->cr);
192 }
193 
194 
do_arc(cairo_t * cr,double x,double y,double radius,int start_angle,int end_angle)195 static inline void do_arc (cairo_t *cr, double x, double y, double radius,
196                                         int start_angle, int end_angle)
197 {
198   cairo_new_sub_path (cr);
199   if (start_angle > start_angle + end_angle) {
200     cairo_arc (cr, x, y, radius, -start_angle * (M_PI / 180.),
201                    (-start_angle - end_angle) * (M_PI / 180.));
202   } else {
203     cairo_arc_negative (cr, x, y, radius, -start_angle * (M_PI / 180.),
204                             (-start_angle - end_angle) * (M_PI / 180.));
205   }
206 }
207 
208 
gschem_cairo_arc(GSCHEM_TOPLEVEL * w_current,int width,int x,int y,int radius,int start_angle,int end_angle)209 void gschem_cairo_arc (GSCHEM_TOPLEVEL *w_current,
210                        int width, int x, int y,
211                        int radius, int start_angle, int end_angle)
212 {
213   int s_width;
214   int x1, y1, x2, y2;
215   double s_x, s_y, s_radius;
216   double offset;
217 
218   WORLDtoSCREEN (w_current, x - radius, y + radius, &x1, &y1);
219   WORLDtoSCREEN (w_current, x + radius, y - radius, &x2, &y2);
220   s_width = screen_width (w_current, width);
221   offset = ((s_width % 2) == 0) ? 0 : 0.5;
222 
223   s_x = (double)(x1 + x2) / 2.;
224   s_y = (double)(y1 + y2) / 2.;
225   s_radius = (double)(y2 - y1) / 2.;
226 
227   cairo_save (w_current->cr);
228   cairo_translate (w_current->cr, s_x + offset, s_y + offset);
229 
230   /* Adjust for non-uniform X/Y scale factor. Note that the + 1
231      allows for the case where x2 == x1 or y2 == y1 */
232   cairo_scale (w_current->cr, (double)(x2 - x1 + 1) /
233                               (double)(y2 - y1 + 1), 1.);
234 
235   do_arc (w_current->cr, 0., 0., (double) s_radius, start_angle, end_angle);
236 
237   cairo_restore (w_current->cr);
238 }
239 
240 
gschem_cairo_center_arc(GSCHEM_TOPLEVEL * w_current,int center_width,int line_width,int x,int y,int radius,int start_angle,int end_angle)241 void gschem_cairo_center_arc (GSCHEM_TOPLEVEL *w_current,
242                               int center_width,
243                               int line_width, int x, int y,
244                               int radius, int start_angle, int end_angle)
245 {
246   int s_center_width, s_line_width;
247   int s_x, s_y, s_diameter;
248   int even_center_width;
249   int even_line_width;
250   int even_diameter;
251   double center_offset;
252   double s_radius;
253   int do_radius_hint = TRUE;
254 
255   WORLDtoSCREEN (w_current, x, y, &s_x, &s_y);
256   s_diameter = SCREENabs (w_current, 2 * radius);
257   even_diameter = ((s_diameter % 2) == 0);
258   s_radius = (double) s_diameter / 2.;
259 
260   /* Switch off radius hinting for small radii. If we don't, then we get
261    * a very abrupt transition once the arc reaches a single pixel size. */
262   if (s_radius <= 1.) do_radius_hint = FALSE;
263 
264   /* Hint the center of the arc based on where a line
265    * of thickness center_width (world) would drawn */
266   s_center_width = screen_width (w_current, center_width);
267   even_center_width = (center_width == -1 || (s_center_width % 2) == 0);
268   center_offset = even_center_width ? 0. : 0.5;
269 
270   /* Hint the radius to land its extermity on the pixel grid */
271   s_line_width = screen_width (w_current, line_width);
272   even_line_width = (line_width == -1 || (s_line_width % 2) == 0);
273   if (do_radius_hint)
274     s_radius += ((even_center_width ==
275                         even_line_width) == even_diameter) ? 0. : 0.5;
276 
277   do_arc (w_current->cr, (double) s_x + center_offset,
278                          (double) s_y + center_offset,
279                          (double) s_radius,
280                          start_angle, end_angle);
281 }
282 
283 
gschem_cairo_stroke(GSCHEM_TOPLEVEL * w_current,int line_type,int line_end,int wwidth,int wlength,int wspace)284 void gschem_cairo_stroke (GSCHEM_TOPLEVEL *w_current, int line_type, int line_end,
285                           int wwidth, int wlength, int wspace)
286 {
287   double offset;
288   double dashes[4];
289   cairo_line_cap_t cap;
290   cairo_line_cap_t round_cap_if_legible;
291   int num_dashes;
292   int width, length, space;
293 
294   width  = screen_width (w_current, wwidth);
295   length = screen_width (w_current, wlength);
296   space  = screen_width (w_current, wspace);
297   offset = ((width % 2) == 0) ? 0 : 0.5;
298 
299   cairo_set_line_width (w_current->cr, width);
300   cairo_set_line_join (w_current->cr, CAIRO_LINE_JOIN_MITER);
301 
302   round_cap_if_legible = (width <= 1) ? CAIRO_LINE_CAP_SQUARE :
303                                         CAIRO_LINE_CAP_ROUND;
304 
305   switch (line_end) {
306     case END_NONE:   cap = CAIRO_LINE_CAP_BUTT;   break;
307     case END_SQUARE: cap = CAIRO_LINE_CAP_SQUARE; break;
308     case END_ROUND:  cap = round_cap_if_legible;  break;
309     default:
310       fprintf(stderr, _("Unknown end for line (%d)\n"), line_end);
311       cap = CAIRO_LINE_CAP_BUTT;
312     break;
313   }
314 
315   switch (line_type) {
316 
317     default:
318       fprintf(stderr, _("Unknown type for stroke (%d) !\n"), line_type);
319       /* Fall through */
320 
321     case TYPE_SOLID:
322       num_dashes = 0;
323 
324       cairo_set_dash (w_current->cr, dashes, num_dashes, 0.);
325       cairo_set_line_cap (w_current->cr, cap);
326       cairo_stroke (w_current->cr);
327       break;
328 
329     case TYPE_DOTTED:
330       dashes[0] = 0;                    /* DOT */
331       dashes[1] = space;
332       num_dashes = 2;
333 
334       cairo_set_dash (w_current->cr, dashes, num_dashes, offset);
335       cairo_set_line_cap (w_current->cr, round_cap_if_legible);
336       cairo_stroke (w_current->cr);
337       break;
338 
339     case TYPE_DASHED:
340       dashes[0] = length;               /* DASH */
341       dashes[1] = space;
342       num_dashes = 2;
343 
344       cairo_set_dash (w_current->cr, dashes, num_dashes, 0.);
345       cairo_set_line_cap (w_current->cr, CAIRO_LINE_CAP_BUTT);
346       cairo_stroke (w_current->cr);
347       break;
348 
349     case TYPE_CENTER:
350       dashes[0] = length;               /* DASH */
351       dashes[1] = 2 * space;
352       num_dashes = 2;
353 
354       cairo_set_dash (w_current->cr, dashes, num_dashes, 0.);
355       cairo_set_line_cap (w_current->cr, CAIRO_LINE_CAP_BUTT);
356       cairo_stroke_preserve (w_current->cr);
357 
358       dashes[0] = 0;                    /* DOT */
359       dashes[1] = 2 * space + length;
360       num_dashes = 2;
361 
362       cairo_set_dash (w_current->cr, dashes, num_dashes, -length - space + offset);
363       cairo_set_line_cap (w_current->cr, round_cap_if_legible);
364       cairo_stroke (w_current->cr);
365       break;
366 
367     case TYPE_PHANTOM:
368       dashes[0] = length;               /* DASH */
369       dashes[1] = 3 * space;
370       num_dashes = 2;
371 
372       cairo_set_dash (w_current->cr, dashes, num_dashes, 0.);
373       cairo_set_line_cap (w_current->cr, CAIRO_LINE_CAP_BUTT);
374       cairo_stroke_preserve (w_current->cr);
375 
376       dashes[0] = 0;                    /* DOT */
377       dashes[1] = space;
378       dashes[2] = 0;                    /* DOT */
379       dashes[3] = 2 * space + length;
380       num_dashes = 4;
381 
382       cairo_set_dash (w_current->cr, dashes, num_dashes, -length - space + offset);
383       cairo_set_line_cap (w_current->cr, round_cap_if_legible);
384       cairo_stroke (w_current->cr);
385       break;
386   }
387 
388   cairo_set_dash (w_current->cr, NULL, 0, 0.);
389 }
390 
391 
gschem_cairo_set_source_color(GSCHEM_TOPLEVEL * w_current,COLOR * color)392 void gschem_cairo_set_source_color (GSCHEM_TOPLEVEL *w_current, COLOR *color)
393 {
394   cairo_set_source_rgba (w_current->cr, (double)color->r / 255.0,
395                                         (double)color->g / 255.0,
396                                         (double)color->b / 255.0,
397                                         (double)color->a / 255.0);
398 }
399