1 #define __SP_SVG_COLOR_C__
3 /**
4 * \file
5 * Reading \& writing of SVG/CSS colors.
6 */
7 /*
8 * Authors:
9 * Lauris Kaplinski <lauris@kaplinski.com>
10 *
11 * Copyright (C) 1999-2002 Lauris Kaplinski
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
20 #include <cstdlib>
21 #include <cstdio> // sprintf
22 #include <cstring>
23 #include <string>
24 #include <cassert>
25 #include <math.h>
26 #include <glib/gmem.h>
27 #include <glib.h> // g_assert
28 #include <glib/gmessages.h>
29 #include <glib/gstrfuncs.h>
30 #include <glib/ghash.h>
31 #include <glib/gutils.h>
32 #include <errno.h>
34 #include "strneq.h"
35 #include "preferences.h"
36 #include "svg-color.h"
37 #include "svg-icc-color.h"
39 #if ENABLE_LCMS
40 #include <lcms.h>
41 #include "color.h"
42 #include "color-profile.h"
43 #include "document.h"
44 #include "inkscape.h"
45 #include "profile-manager.h"
46 #endif // ENABLE_LCMS
48 using std::sprintf;
50 struct SPSVGColor {
51 unsigned long rgb;
52 char const *name;
53 };
55 /*
56 * These are the colors defined in the SVG standard
57 */
58 static SPSVGColor const sp_svg_color_named[] = {
59 { 0xF0F8FF, "aliceblue" },
60 { 0xFAEBD7, "antiquewhite" },
61 { 0x00FFFF, "aqua" },
62 { 0x7FFFD4, "aquamarine" },
63 { 0xF0FFFF, "azure" },
64 { 0xF5F5DC, "beige" },
65 { 0xFFE4C4, "bisque" },
66 { 0x000000, "black" },
67 { 0xFFEBCD, "blanchedalmond" },
68 { 0x0000FF, "blue" },
69 { 0x8A2BE2, "blueviolet" },
70 { 0xA52A2A, "brown" },
71 { 0xDEB887, "burlywood" },
72 { 0x5F9EA0, "cadetblue" },
73 { 0x7FFF00, "chartreuse" },
74 { 0xD2691E, "chocolate" },
75 { 0xFF7F50, "coral" },
76 { 0x6495ED, "cornflowerblue" },
77 { 0xFFF8DC, "cornsilk" },
78 { 0xDC143C, "crimson" },
79 { 0x00FFFF, "cyan" },
80 { 0x00008B, "darkblue" },
81 { 0x008B8B, "darkcyan" },
82 { 0xB8860B, "darkgoldenrod" },
83 { 0xA9A9A9, "darkgray" },
84 { 0x006400, "darkgreen" },
85 { 0xA9A9A9, "darkgrey" },
86 { 0xBDB76B, "darkkhaki" },
87 { 0x8B008B, "darkmagenta" },
88 { 0x556B2F, "darkolivegreen" },
89 { 0xFF8C00, "darkorange" },
90 { 0x9932CC, "darkorchid" },
91 { 0x8B0000, "darkred" },
92 { 0xE9967A, "darksalmon" },
93 { 0x8FBC8F, "darkseagreen" },
94 { 0x483D8B, "darkslateblue" },
95 { 0x2F4F4F, "darkslategray" },
96 { 0x2F4F4F, "darkslategrey" },
97 { 0x00CED1, "darkturquoise" },
98 { 0x9400D3, "darkviolet" },
99 { 0xFF1493, "deeppink" },
100 { 0x00BFFF, "deepskyblue" },
101 { 0x696969, "dimgray" },
102 { 0x696969, "dimgrey" },
103 { 0x1E90FF, "dodgerblue" },
104 { 0xB22222, "firebrick" },
105 { 0xFFFAF0, "floralwhite" },
106 { 0x228B22, "forestgreen" },
107 { 0xFF00FF, "fuchsia" },
108 { 0xDCDCDC, "gainsboro" },
109 { 0xF8F8FF, "ghostwhite" },
110 { 0xFFD700, "gold" },
111 { 0xDAA520, "goldenrod" },
112 { 0x808080, "gray" },
113 { 0x808080, "grey" },
114 { 0x008000, "green" },
115 { 0xADFF2F, "greenyellow" },
116 { 0xF0FFF0, "honeydew" },
117 { 0xFF69B4, "hotpink" },
118 { 0xCD5C5C, "indianred" },
119 { 0x4B0082, "indigo" },
120 { 0xFFFFF0, "ivory" },
121 { 0xF0E68C, "khaki" },
122 { 0xE6E6FA, "lavender" },
123 { 0xFFF0F5, "lavenderblush" },
124 { 0x7CFC00, "lawngreen" },
125 { 0xFFFACD, "lemonchiffon" },
126 { 0xADD8E6, "lightblue" },
127 { 0xF08080, "lightcoral" },
128 { 0xE0FFFF, "lightcyan" },
129 { 0xFAFAD2, "lightgoldenrodyellow" },
130 { 0xD3D3D3, "lightgray" },
131 { 0x90EE90, "lightgreen" },
132 { 0xD3D3D3, "lightgrey" },
133 { 0xFFB6C1, "lightpink" },
134 { 0xFFA07A, "lightsalmon" },
135 { 0x20B2AA, "lightseagreen" },
136 { 0x87CEFA, "lightskyblue" },
137 { 0x778899, "lightslategray" },
138 { 0x778899, "lightslategrey" },
139 { 0xB0C4DE, "lightsteelblue" },
140 { 0xFFFFE0, "lightyellow" },
141 { 0x00FF00, "lime" },
142 { 0x32CD32, "limegreen" },
143 { 0xFAF0E6, "linen" },
144 { 0xFF00FF, "magenta" },
145 { 0x800000, "maroon" },
146 { 0x66CDAA, "mediumaquamarine" },
147 { 0x0000CD, "mediumblue" },
148 { 0xBA55D3, "mediumorchid" },
149 { 0x9370DB, "mediumpurple" },
150 { 0x3CB371, "mediumseagreen" },
151 { 0x7B68EE, "mediumslateblue" },
152 { 0x00FA9A, "mediumspringgreen" },
153 { 0x48D1CC, "mediumturquoise" },
154 { 0xC71585, "mediumvioletred" },
155 { 0x191970, "midnightblue" },
156 { 0xF5FFFA, "mintcream" },
157 { 0xFFE4E1, "mistyrose" },
158 { 0xFFE4B5, "moccasin" },
159 { 0xFFDEAD, "navajowhite" },
160 { 0x000080, "navy" },
161 { 0xFDF5E6, "oldlace" },
162 { 0x808000, "olive" },
163 { 0x6B8E23, "olivedrab" },
164 { 0xFFA500, "orange" },
165 { 0xFF4500, "orangered" },
166 { 0xDA70D6, "orchid" },
167 { 0xEEE8AA, "palegoldenrod" },
168 { 0x98FB98, "palegreen" },
169 { 0xAFEEEE, "paleturquoise" },
170 { 0xDB7093, "palevioletred" },
171 { 0xFFEFD5, "papayawhip" },
172 { 0xFFDAB9, "peachpuff" },
173 { 0xCD853F, "peru" },
174 { 0xFFC0CB, "pink" },
175 { 0xDDA0DD, "plum" },
176 { 0xB0E0E6, "powderblue" },
177 { 0x800080, "purple" },
178 { 0xFF0000, "red" },
179 { 0xBC8F8F, "rosybrown" },
180 { 0x4169E1, "royalblue" },
181 { 0x8B4513, "saddlebrown" },
182 { 0xFA8072, "salmon" },
183 { 0xF4A460, "sandybrown" },
184 { 0x2E8B57, "seagreen" },
185 { 0xFFF5EE, "seashell" },
186 { 0xA0522D, "sienna" },
187 { 0xC0C0C0, "silver" },
188 { 0x87CEEB, "skyblue" },
189 { 0x6A5ACD, "slateblue" },
190 { 0x708090, "slategray" },
191 { 0x708090, "slategrey" },
192 { 0xFFFAFA, "snow" },
193 { 0x00FF7F, "springgreen" },
194 { 0x4682B4, "steelblue" },
195 { 0xD2B48C, "tan" },
196 { 0x008080, "teal" },
197 { 0xD8BFD8, "thistle" },
198 { 0xFF6347, "tomato" },
199 { 0x40E0D0, "turquoise" },
200 { 0xEE82EE, "violet" },
201 { 0xF5DEB3, "wheat" },
202 { 0xFFFFFF, "white" },
203 { 0xF5F5F5, "whitesmoke" },
204 { 0xFFFF00, "yellow" },
205 { 0x9ACD32, "yellowgreen" }
206 };
208 static GHashTable *sp_svg_create_color_hash();
210 guint32
211 sp_svg_read_color(gchar const *str, guint32 const dfl)
212 {
213 return sp_svg_read_color(str, NULL, dfl);
214 }
216 static guint32
217 internal_sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 def)
218 {
219 static GHashTable *colors = NULL;
220 guint32 val = 0;
222 if (str == NULL) return def;
223 while ((*str <= ' ') && *str) str++;
224 if (!*str) return def;
226 if (str[0] == '#') {
227 gint i;
228 for (i = 1; str[i]; i++) {
229 int hexval;
230 if (str[i] >= '0' && str[i] <= '9')
231 hexval = str[i] - '0';
232 else if (str[i] >= 'A' && str[i] <= 'F')
233 hexval = str[i] - 'A' + 10;
234 else if (str[i] >= 'a' && str[i] <= 'f')
235 hexval = str[i] - 'a' + 10;
236 else
237 break;
238 val = (val << 4) + hexval;
239 }
240 /* handle #rgb case */
241 if (i == 1 + 3) {
242 val = ((val & 0xf00) << 8) |
243 ((val & 0x0f0) << 4) |
244 (val & 0x00f);
245 val |= val << 4;
246 } else if (i != 1 + 6) {
247 /* must be either 3 or 6 digits. */
248 return def;
249 }
250 if (end_ptr) {
251 *end_ptr = str + i;
252 }
253 } else if (strneq(str, "rgb(", 4)) {
254 bool hasp, hasd;
255 gchar *s, *e;
256 gdouble r, g, b;
258 s = (gchar *) str + 4;
259 hasp = false;
260 hasd = false;
262 r = g_ascii_strtod(s, &e);
263 if (s == e) return def;
264 s = e;
265 if (*s == '%') {
266 hasp = true;
267 s += 1;
268 } else {
269 hasd = true;
270 }
271 while (*s && g_ascii_isspace(*s)) s += 1;
272 if (*s != ',') return def;
273 s += 1;
274 while (*s && g_ascii_isspace(*s)) s += 1;
275 g = g_ascii_strtod(s, &e);
276 if (s == e) return def;
277 s = e;
278 if (*s == '%') {
279 hasp = true;
280 s += 1;
281 } else {
282 hasd = true;
283 }
284 while (*s && g_ascii_isspace(*s)) s += 1;
285 if (*s != ',') return def;
286 s += 1;
287 while (*s && g_ascii_isspace(*s)) s += 1;
288 b = g_ascii_strtod(s, &e);
289 if (s == e) return def;
290 s = e;
291 if (*s == '%') {
292 hasp = true;
293 s += 1;
294 } else {
295 hasd = true;
296 }
297 while(*s && g_ascii_isspace(*s)) s += 1;
298 if (*s != ')') {
299 return def;
300 }
301 ++s;
302 if (hasp && hasd) return def;
303 if (hasp) {
304 val = (guint) floor(CLAMP(r, 0.0, 100.0) * 2.559999) << 24;
305 val |= ((guint) floor(CLAMP(g, 0.0, 100.0) * 2.559999) << 16);
306 val |= ((guint) floor(CLAMP(b, 0.0, 100.0) * 2.559999) << 8);
307 } else {
308 val = (guint) CLAMP(r, 0, 255) << 24;
309 val |= ((guint) CLAMP(g, 0, 255) << 16);
310 val |= ((guint) CLAMP(b, 0, 255) << 8);
311 }
312 if (end_ptr) {
313 *end_ptr = s;
314 }
315 return val;
316 } else {
317 gint i;
318 if (!colors) {
319 colors = sp_svg_create_color_hash();
320 }
321 gchar c[32];
322 for (i = 0; i < 31; i++) {
323 if (str[i] == ';' || g_ascii_isspace(str[i])) {
324 c[i] = '\0';
325 break;
326 }
327 c[i] = g_ascii_tolower(str[i]);
328 if (!str[i]) break;
329 }
330 c[31] = '\0';
332 gpointer const rgb_ptr = g_hash_table_lookup(colors, c);
333 if (rgb_ptr) {
334 val = *(static_cast<unsigned long *>(rgb_ptr));
335 } else {
336 return def;
337 }
338 if (end_ptr) {
339 *end_ptr = str + i;
340 }
341 }
343 return (val << 8);
344 }
346 guint32
347 sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 dfl)
348 {
349 /* I've been rather hurried in editing the above to add support for end_ptr, so I'm adding
350 * this check wrapper. */
351 gchar const *end = str;
352 guint32 const ret = internal_sp_svg_read_color(str, &end, dfl);
353 assert(((ret == dfl) && (end == str))
354 || (((ret & 0xff) == 0)
355 && (str < end)));
356 if (str < end) {
357 gchar *buf = (gchar *) g_malloc(end + 1 - str);
358 memcpy(buf, str, end - str);
359 buf[end - str] = '\0';
360 gchar const *buf_end = buf;
361 guint32 const check = internal_sp_svg_read_color(buf, &buf_end, 1);
362 assert(check == ret
363 && buf_end - buf == end - str);
364 g_free(buf);
366 if ( end_ptr ) {
367 *end_ptr = end;
368 }
369 }
370 return ret;
371 }
374 /**
375 * Converts an RGB colour expressed in form 0x00rrggbb to a CSS/SVG representation of that colour.
376 * The result is valid even in SVG Tiny or non-SVG CSS.
377 */
378 static void
379 rgb24_to_css(char *const buf, unsigned const rgb24)
380 {
381 assert(rgb24 < (1u << 24));
383 /* SVG 1.1 Full allows additional colour names not supported by SVG Tiny, but we don't bother
384 * with them: it's good for these colours to be copyable to non-SVG CSS stylesheets and for
385 * documents to be more viewable in SVG Tiny/Basic viewers; and some of the SVG Full names are
386 * less meaningful than hex equivalents anyway. And it's easier for a person to map from the
387 * restricted set because the only component values are {00,80,ff}, other than silver 0xc0c0c0.
388 */
390 char const *src = NULL;
391 switch (rgb24) {
392 /* Extracted mechanically from the table at
393 * http://www.w3.org/TR/REC-html40/types.html#h-6.5 .*/
394 case 0x000000: src = "black"; break;
395 case 0xc0c0c0: src = "silver"; break;
396 case 0x808080: src = "gray"; break;
397 case 0xffffff: src = "white"; break;
398 case 0x800000: src = "maroon"; break;
399 case 0xff0000: src = "red"; break;
400 case 0x800080: src = "purple"; break;
401 case 0xff00ff: src = "fuchsia"; break;
402 case 0x008000: src = "green"; break;
403 case 0x00ff00: src = "lime"; break;
404 case 0x808000: src = "olive"; break;
405 case 0xffff00: src = "yellow"; break;
406 case 0x000080: src = "navy"; break;
407 case 0x0000ff: src = "blue"; break;
408 case 0x008080: src = "teal"; break;
409 case 0x00ffff: src = "aqua"; break;
411 default: {
412 if ((rgb24 & 0xf0f0f) * 0x11 == rgb24) {
413 /* Can use the shorter three-digit form #rgb instead of #rrggbb. */
414 sprintf(buf, "#%x%x%x",
415 (rgb24 >> 16) & 0xf,
416 (rgb24 >> 8) & 0xf,
417 rgb24 & 0xf);
418 } else {
419 sprintf(buf, "#%06x", rgb24);
420 }
421 break;
422 }
423 }
424 if (src) {
425 strcpy(buf, src);
426 }
428 // assert(sp_svg_read_color(buf, 0xff) == (rgb24 << 8));
429 }
431 /**
432 * Converts an RGBA32 colour to a CSS/SVG representation of the RGB portion of that colour. The
433 * result is valid even in SVG Tiny or non-SVG CSS.
434 *
435 * \param rgba32 Colour expressed in form 0xrrggbbaa.
436 * \pre buflen \>= 8.
437 */
438 void
439 sp_svg_write_color(gchar *buf, unsigned const buflen, guint32 const rgba32)
440 {
441 g_assert(8 <= buflen);
443 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
444 unsigned const rgb24 = rgba32 >> 8;
445 if (prefs->getBool("/options/svgoutput/usenamedcolors")) {
446 rgb24_to_css(buf, rgb24);
447 } else {
448 g_snprintf(buf, buflen, "#%06x", rgb24);
449 }
450 }
452 static GHashTable *
453 sp_svg_create_color_hash()
454 {
455 GHashTable *colors = g_hash_table_new(g_str_hash, g_str_equal);
457 for (unsigned i = 0 ; i < G_N_ELEMENTS(sp_svg_color_named) ; i++) {
458 g_hash_table_insert(colors,
459 (gpointer)(sp_svg_color_named[i].name),
460 (gpointer)(&sp_svg_color_named[i].rgb));
461 }
463 return colors;
464 }
466 #if ENABLE_LCMS
467 //helper function borrowed from src/widgets/sp-color-icc-selector.cpp:
468 void getThings( DWORD space, gchar const**& namers, gchar const**& tippies, guint const*& scalies );
470 void icc_color_to_sRGB(SVGICCColor* icc, guchar* r, guchar* g, guchar* b){
471 guchar color_out[4];
472 guchar color_in[4];
473 if (icc){
474 g_message("profile name: %s", icc->colorProfile.c_str());
475 Inkscape::ColorProfile* prof = SP_ACTIVE_DOCUMENT->profileManager->find(icc->colorProfile.c_str());
476 if ( prof ) {
477 cmsHTRANSFORM trans = prof->getTransfToSRGB8();
478 if ( trans ) {
479 gchar const** names = 0;
480 gchar const** tips = 0;
481 guint const* scales = 0;
482 getThings( prof->getColorSpace(), names, tips, scales );
484 guint count = _cmsChannelsOf( prof->getColorSpace() );
485 if (count>4) count=4; //do we need it? Should we allow an arbitrary number of color values? Or should we limit to a maximum? (max==4?)
486 for (guint i=0;i<count; i++){
487 color_in[i] = (guchar) ((((gdouble)icc->colors[i])*256.0) * (gdouble)scales[i]);
488 g_message("input[%d]: %d",i, color_in[i]);
489 }
491 cmsDoTransform( trans, color_in, color_out, 1 );
492 g_message("transform to sRGB done");
493 }
494 *r = color_out[0];
495 *g = color_out[1];
496 *b = color_out[2];
497 }
498 }
499 }
500 #endif //ENABLE_LCMS
502 /*
503 * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj
504 * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z'
505 * Allowed ASCII remaining chars add: '-', '.', '0'-'9',
506 */
507 bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor* dest )
508 {
509 bool good = true;
511 if ( end_ptr ) {
512 *end_ptr = str;
513 }
514 if ( dest ) {
515 dest->colorProfile.clear();
516 dest->colors.clear();
517 }
519 if ( !str ) {
520 // invalid input
521 good = false;
522 } else {
523 while ( g_ascii_isspace(*str) ) {
524 str++;
525 }
527 good = strneq( str, "icc-color(", 10 );
529 if ( good ) {
530 str += 10;
531 while ( g_ascii_isspace(*str) ) {
532 str++;
533 }
535 if ( !g_ascii_isalpha(*str)
536 && ( !(0x080 & *str) )
537 && (*str != '_')
538 && (*str != ':') ) {
539 // Name must start with a certain type of character
540 good = false;
541 } else {
542 while ( g_ascii_isdigit(*str) || g_ascii_isalpha(*str)
543 || (*str == '-') || (*str == ':') || (*str == '_') || (*str == '.') ) {
544 if ( dest ) {
545 dest->colorProfile += *str;
546 }
547 str++;
548 }
549 while ( g_ascii_isspace(*str) || *str == ',' ) {
550 str++;
551 }
552 }
553 }
555 if ( good ) {
556 while ( *str && *str != ')' ) {
557 if ( g_ascii_isdigit(*str) || *str == '.' || *str == '-' || *str == '+') {
558 gchar* endPtr = 0;
559 gdouble dbl = g_ascii_strtod( str, &endPtr );
560 if ( !errno ) {
561 if ( dest ) {
562 dest->colors.push_back( dbl );
563 }
564 str = endPtr;
565 } else {
566 good = false;
567 break;
568 }
570 while ( g_ascii_isspace(*str) || *str == ',' ) {
571 str++;
572 }
573 } else {
574 break;
575 }
576 }
577 }
579 // We need to have ended on a closing parenthesis
580 if ( good ) {
581 while ( g_ascii_isspace(*str) ) {
582 str++;
583 }
584 good &= (*str == ')');
585 }
586 }
588 if ( good ) {
589 if ( end_ptr ) {
590 *end_ptr = str;
591 }
592 } else {
593 if ( dest ) {
594 dest->colorProfile.clear();
595 dest->colors.clear();
596 }
597 }
599 return good;
600 }
603 bool sp_svg_read_icc_color( gchar const *str, SVGICCColor* dest )
604 {
605 return sp_svg_read_icc_color(str, NULL, dest);
606 }
609 /*
610 Local Variables:
611 mode:c++
612 c-file-style:"stroustrup"
613 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
614 indent-tabs-mode:nil
615 fill-column:99
616 End:
617 */
618 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :