1 /* gEDA - GPL Electronic Design Automation
2  * libgeda - gEDA's library
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 #include <config.h>
21 #include <missing.h>
22 
23 #include <stdio.h>
24 #include <math.h>
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #endif
28 
29 #include "libgeda_priv.h"
30 
31 #ifdef HAVE_LIBDMALLOC
32 #include <dmalloc.h>
33 #endif
34 
35 COLOR print_colors[MAX_COLORS];
36 
37 #define NOCOLOR {0xff, 0xff, 0xff, 0xff, FALSE}
38 #define WHITE   {0xff, 0xff, 0xff, 0xff, TRUE}
39 #define GRAY    {0x88, 0x88, 0x88, 0xff, TRUE}
40 #define BLACK   {0x00, 0x00, 0x00, 0xff, TRUE}
41 #define ENDMAP  {0x00, 0x00, 0x00, 0x00, FALSE}
42 
43 static COLOR default_colors[] = {
44   WHITE,           /*  0: background         */
45   BLACK,           /*  1: pin                */
46   BLACK,           /*  2: net-endpoint       */
47   BLACK,           /*  3: graphic            */
48   BLACK,           /*  4: net                */
49   BLACK,           /*  5: attribute          */
50   BLACK,           /*  6: logic-bubble       */
51   BLACK,           /*  7: dots-grid          */
52   BLACK,           /*  8: detached-attribute */
53   BLACK,           /*  9: text               */
54   BLACK,           /* 10: bus                */
55   GRAY,            /* 11: select             */
56   GRAY,            /* 12: bounding-box       */
57   GRAY,            /* 13: zoom-box           */
58   GRAY,            /* 14: stroke             */
59   BLACK,           /* 15: lock               */
60   NOCOLOR,         /* 16: output-background  */
61   NOCOLOR,         /* 17: freestyle1         */
62   NOCOLOR,         /* 18: freestyle2         */
63   NOCOLOR,         /* 19: freestyle3         */
64   NOCOLOR,         /* 20: freestyle4         */
65   BLACK,           /* 21: junction           */
66   GRAY,            /* 22: mesh-grid-major    */
67   NOCOLOR,         /* 23: mesh-grid-minor    */
68   ENDMAP
69 };
70 
71 /*! \brief Initialises the color subsystem
72  *  \par Function Description
73  *  At the moment, just initialises the print color map.
74  */
75 void
s_color_init(void)76 s_color_init(void)
77 {
78   s_color_map_defaults (print_colors);
79 }
80 
81 /*! \brief Initialise a color map to B&W
82  *  \par Function Description
83  *  Initialises a color map to a simple default: black features on a
84  *  white background, with "special" colors as gray.
85  *
86  *  \warning \a map must be have length of at least #MAX_COLORS.
87  *
88  *  \param map Color map to initialise.
89  */
90 void
s_color_map_defaults(COLOR * map)91 s_color_map_defaults (COLOR *map)
92 {
93   int i;
94   gboolean reached_end = FALSE;
95   COLOR c;
96   for (i = 0; i < MAX_COLORS; i++) {
97     if (reached_end) {
98       map[i].enabled = FALSE;
99       continue;
100     }
101     c = default_colors[i];
102     if (c.a == 0) { /* Check for end of default map */
103       reached_end = TRUE;
104       i--;
105       continue;
106     }
107     map[i] = c;
108   }
109 }
110 
111 /* \brief Decode a hexadecimal RGB or RGBA color code.
112  * \par Function Description
113  * Accepts a hexadecimal color code \a rgba of either the form #RRGGBB
114  * or #RRGGBBAA, and parses it to extract the numerical color values,
115  * placing them in the the #guchar pointers passed as arguments. If
116  * the six-digit form is used, the alpha channel is set to full
117  * opacity. If an error occurs during parsing, the return values are
118  * set to solid white.
119  *
120  * Note that this function implements similar functionality to
121  * gdk_color_parse(). However, for consistency, <em>only</em> this
122  * function should be used to parse color strings from gEDA
123  * configuration files, as gdk_color_parse() does not support the
124  * alpha channel.
125  *
126  * \todo Use GError mechanism to give more specific error messages.
127  *
128  * \param [in]  rgba Colour code to parse.
129  * \param [out] r    Location to store red value.
130  * \param [out] g    Location to store green value.
131  * \param [out] b    Location to store blue value.
132  *
133  *  \returns #TRUE on success, #FALSE on failure.
134  */
135 gboolean
s_color_rgba_decode(const gchar * rgba,guint8 * r,guint8 * g,guint8 * b,guint8 * a)136 s_color_rgba_decode (const gchar *rgba,
137                      guint8 *r, guint8 *g, guint8 *b, guint8 *a)
138 {
139   gint len, i, ri, gi, bi, ai;
140   gchar c;
141 
142   /* Default to solid white */
143   *r = 0xff; *g = 0xff; *b = 0xff; *a = 0xff;
144 
145   /* Check that the string is a valid length and starts with a '#' */
146   len = strlen (rgba);
147   if ((len != 9 && len != 7) || rgba[0] != '#')
148     return FALSE;
149 
150   /* Check we only have [0-9a-fA-F] */
151   for (i = 1; i < len; i++) {
152     c = rgba[i];
153     if ((c < '0' || c > '9')
154         && (c < 'a' || c > 'f')
155         && (c < 'A' || c > 'F'))
156       return FALSE;
157   }
158 
159   /* Use sscanf to extract values */
160   c = sscanf (rgba + 1, "%2x%2x%2x", &ri, &gi, &bi);
161   if (c != 3)
162     return FALSE;
163   *r = (guint8) ri; *g = (guint8) gi; *b = (guint8) bi;
164 
165   if (len == 9) {
166     c = sscanf (rgba + 7, "%2x", &ai);
167     if (c != 1)
168       return FALSE;
169     *a = (guint8) ai;
170   }
171 
172   return TRUE;
173 }
174 
175 /* \brief Encode a hexadecimal RGB or RGBA color code.
176  * \par Function Description
177  * Encodes four colour components into either the form #RRGGBB or
178  * #RRGGBBAA. The shorter form is used when the alpha component is
179  * 0xff.
180  *
181  * \param [in] r Red component.
182  * \param [in] g Green component.
183  * \param [in] b Blue component.
184  * \returns A newly allocated string containing the encoded string.
185  */
186 gchar *
s_color_rgba_encode(guint8 r,guint8 g,guint8 b,guint8 a)187 s_color_rgba_encode (guint8 r, guint8 g, guint8 b, guint8 a)
188 {
189   if (a < 0xff)
190     return g_strdup_printf("#%02x%02x%02x%02x",
191                            (gint) r, (gint) g, (gint) b, (gint) a);
192   else
193     return g_strdup_printf("#%02x%02x%02x",
194                            (gint) r, (gint) g, (gint) b);
195 }
196 
197 /*! \todo Finish function documentation!!!
198  *  \brief
199  *  \par Function Description
200  *
201  */
s_color_ps_string(gint color)202 gchar *s_color_ps_string(gint color)
203 {
204   COLOR c;
205 
206   if (color >= MAX_COLORS) {
207     g_warning (_("Color index out of range"));
208     return NULL;
209   }
210 
211   c = print_colors[color];
212 
213   if ((c.a == 0) || !c.enabled) {
214     return NULL;
215   } else {
216     return g_strdup_printf ("%.3f %.3f %.3f",
217                             (gdouble) c.r/255.0,
218                             (gdouble) c.g/255.0,
219                             (gdouble) c.b/255.0);
220   }
221 }
222 
223 SCM
s_color_map_to_scm(const COLOR * map)224 s_color_map_to_scm (const COLOR *map)
225 {
226   SCM result = SCM_EOL;
227   int i;
228   for (i = MAX_COLORS - 1; i >= 0; i--) {
229     SCM color_val = SCM_BOOL_F;
230     if (map[i].enabled) {
231       COLOR c = map[i];
232       gchar *rgba = s_color_rgba_encode (c.r, c.g, c.b, c.a);
233       color_val = scm_from_utf8_string (rgba);
234       g_free (rgba);
235     }
236     result = scm_cons (scm_list_2 (scm_from_int (i), color_val), result);
237   }
238   return result;
239 }
240 
241 /*!
242  * \warning This function should ONLY be called from Scheme procedures.
243  */
244 void
s_color_map_from_scm(COLOR * map,SCM lst,const char * scheme_proc_name)245 s_color_map_from_scm (COLOR *map, SCM lst, const char *scheme_proc_name)
246 {
247   SCM curr = lst;
248   SCM wrong_type_arg_sym = scm_from_utf8_symbol ("wrong-type-arg");
249   SCM proc_name = scm_from_utf8_string (scheme_proc_name);
250   while (curr != SCM_EOL) {
251     int i;
252     char *rgba;
253     SCM s;
254     COLOR c = {0x00, 0x00, 0x00, FALSE};
255     gboolean result;
256     SCM entry = scm_car (curr);
257 
258     /* Check map entry has correct type */
259     if (!scm_is_true (scm_list_p (entry))
260         || (scm_to_int (scm_length (entry)) != 2)) {
261       scm_error_scm (wrong_type_arg_sym, proc_name,
262                      scm_from_utf8_string (_("Color map entry must be a two-element list")),
263                      SCM_EOL, scm_list_1 (entry));
264     }
265 
266     /* Check color index has correct type, and extract it */
267     s = scm_car (entry);
268     if (!scm_is_integer (s)) {
269       scm_error_scm (wrong_type_arg_sym, proc_name,
270                      scm_from_utf8_string (_("Index in color map entry must be an integer")),
271                      SCM_EOL, scm_list_1 (s));
272     }
273     i = scm_to_int (s);
274 
275     /* Check color index is within bounds. If it's out of bounds, it's
276      * legal, but warn & ignore it.
277      *
278      * FIXME one day we will have dynamically-expanding colorspace.
279      * One day. */
280     if ((i < 0) || (i >= MAX_COLORS)) {
281       g_critical ("Color map index out of bounds: %i\n", i);
282       goto color_map_next;
283     }
284 
285     /* If color value is #F, disable color */
286     s = scm_cadr (entry);
287     if (scm_is_false (s)) {
288       map[i].enabled = FALSE;
289       goto color_map_next;
290     }
291 
292     /* Otherwise, we require a string */
293     s = scm_cadr (entry);
294     if (!scm_is_string (s)) {
295       scm_error_scm (wrong_type_arg_sym, proc_name,
296                      scm_from_utf8_string (_("Value in color map entry must be #f or a string")),
297                      SCM_EOL, scm_list_1 (s));
298     }
299     rgba = scm_to_utf8_string (s);
300 
301     result = s_color_rgba_decode (rgba, &c.r, &c.g, &c.b, &c.a);
302 
303     /* FIXME should we generate a Guile error if there's a problem here? */
304     if (!result) {
305       g_critical ("Invalid color map value: %s\n", rgba);
306     } else {
307       map[i] = c;
308       map[i].enabled = TRUE;
309     }
310 
311   color_map_next:
312     /* Go to next element in map */
313     curr = scm_cdr (curr);
314   }
315   scm_remember_upto_here_2 (wrong_type_arg_sym, proc_name);
316 }
317