/*
* File: colors.c
*
* Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "colors.h"
#include "msg.h"
/*
* If EXTENDED_COLOR is defined, the extended set of named colors is supported.
* These colors're not standard but they're supported in most browsers.
* NOTE: The colors MUST be in alphabetical order and lower case because the
* code uses a binary search.
*/
#define EXTENDED_COLOR
static const struct key {
char *key;
int32_t val;
} color_keyword [] = {
#ifdef EXTENDED_COLOR
{ "aliceblue", 0xf0f8ff},
{ "antiquewhite", 0xfaebd7},
#endif
{ "aqua", 0x00ffff},
#ifdef EXTENDED_COLOR
{ "aquamarine", 0x7fffd4},
{ "azure", 0xf0ffff},
{ "beige", 0xf5f5dc},
{ "bisque", 0xffe4c4},
#endif
{ "black", 0x000000},
#ifdef EXTENDED_COLOR
{ "blanchedalmond", 0xffebcd},
#endif
{"blue", 0x0000ff},
#ifdef EXTENDED_COLOR
{ "blueviolet", 0x8a2be2},
{ "brown", 0xa52a2a},
{ "burlywood", 0xdeb887},
{ "cadetblue", 0x5f9ea0},
{ "chartreuse", 0x7fff00},
{ "chocolate", 0xd2691e},
{ "coral", 0xff7f50},
{ "cornflowerblue", 0x6495ed},
{ "cornsilk", 0xfff8dc},
{ "crimson", 0xdc1436},
{ "cyan", 0x00ffff},
{ "darkblue", 0x00008b},
{ "darkcyan", 0x008b8b},
{ "darkgoldenrod", 0xb8860b},
{ "darkgray", 0xa9a9a9},
{ "darkgreen", 0x006400},
{ "darkkhaki", 0xbdb76b},
{ "darkmagenta", 0x8b008b},
{ "darkolivegreen", 0x556b2f},
{ "darkorange", 0xff8c00},
{ "darkorchid", 0x9932cc},
{ "darkred", 0x8b0000},
{ "darksalmon", 0xe9967a},
{ "darkseagreen", 0x8fbc8f},
{ "darkslateblue", 0x483d8b},
{ "darkslategray", 0x2f4f4f},
{ "darkturquoise", 0x00ced1},
{ "darkviolet", 0x9400d3},
{ "deeppink", 0xff1493},
{ "deepskyblue", 0x00bfff},
{ "dimgray", 0x696969},
{ "dodgerblue", 0x1e90ff},
{ "firebrick", 0xb22222},
{ "floralwhite", 0xfffaf0},
{ "forestgreen", 0x228b22},
#endif
{ "fuchsia", 0xff00ff},
#ifdef EXTENDED_COLOR
{ "gainsboro", 0xdcdcdc},
{ "ghostwhite", 0xf8f8ff},
{ "gold", 0xffd700},
{ "goldenrod", 0xdaa520},
#endif
{ "gray", 0x808080},
{ "green", 0x008000},
#ifdef EXTENDED_COLOR
{ "greenyellow", 0xadff2f},
{ "honeydew", 0xf0fff0},
{ "hotpink", 0xff69b4},
{ "indianred", 0xcd5c5c},
{ "indigo", 0x4b0082},
{ "ivory", 0xfffff0},
{ "khaki", 0xf0e68c},
{ "lavender", 0xe6e6fa},
{ "lavenderblush", 0xfff0f5},
{ "lawngreen", 0x7cfc00},
{ "lemonchiffon", 0xfffacd},
{ "lightblue", 0xadd8e6},
{ "lightcoral", 0xf08080},
{ "lightcyan", 0xe0ffff},
{ "lightgoldenrodyellow", 0xfafad2},
{ "lightgreen", 0x90ee90},
{ "lightgrey", 0xd3d3d3},
{ "lightpink", 0xffb6c1},
{ "lightsalmon", 0xffa07a},
{ "lightseagreen", 0x20b2aa},
{ "lightskyblue", 0x87cefa},
{ "lightslategray", 0x778899},
{ "lightsteelblue", 0xb0c4de},
{ "lightyellow", 0xffffe0},
#endif
{ "lime", 0x00ff00},
#ifdef EXTENDED_COLOR
{ "limegreen", 0x32cd32},
{ "linen", 0xfaf0e6},
{ "magenta", 0xff00ff},
#endif
{ "maroon", 0x800000},
#ifdef EXTENDED_COLOR
{ "mediumaquamarine", 0x66cdaa},
{ "mediumblue", 0x0000cd},
{ "mediumorchid", 0xba55d3},
{ "mediumpurple", 0x9370db},
{ "mediumseagreen", 0x3cb371},
{ "mediumslateblue", 0x7b68ee},
{ "mediumspringgreen", 0x00fa9a},
{ "mediumturquoise", 0x48d1cc},
{ "mediumvioletred", 0xc71585},
{ "midnightblue", 0x191970},
{ "mintcream", 0xf5fffa},
{ "mistyrose", 0xffe4e1},
{ "moccasin", 0xffe4b5},
{ "navajowhite", 0xffdead},
#endif
{ "navy", 0x000080},
#ifdef EXTENDED_COLOR
{ "oldlace", 0xfdf5e6},
#endif
{ "olive", 0x808000},
#ifdef EXTENDED_COLOR
{ "olivedrab", 0x6b8e23},
{ "orange", 0xffa500},
{ "orangered", 0xff4500},
{ "orchid", 0xda70d6},
{ "palegoldenrod", 0xeee8aa},
{ "palegreen", 0x98fb98},
{ "paleturquoise", 0xafeeee},
{ "palevioletred", 0xdb7093},
{ "papayawhip", 0xffefd5},
{ "peachpuff", 0xffdab9},
{ "peru", 0xcd853f},
{ "pink", 0xffc0cb},
{ "plum", 0xdda0dd},
{ "powderblue", 0xb0e0e6},
#endif
{ "purple", 0x800080},
{ "red", 0xff0000},
#ifdef EXTENDED_COLOR
{ "rosybrown", 0xbc8f8f},
{ "royalblue", 0x4169e1},
{ "saddlebrown", 0x8b4513},
{ "salmon", 0xfa8072},
{ "sandybrown", 0xf4a460},
{ "seagreen", 0x2e8b57},
{ "seashell", 0xfff5ee},
{ "sienna", 0xa0522d},
#endif
{ "silver", 0xc0c0c0},
#ifdef EXTENDED_COLOR
{ "skyblue", 0x87ceeb},
{ "slateblue", 0x6a5acd},
{ "slategray", 0x708090},
{ "snow", 0xfffafa},
{ "springgreen", 0x00ff7f},
{ "steelblue", 0x4682b4},
{ "tan", 0xd2b48c},
#endif
{ "teal", 0x008080},
#ifdef EXTENDED_COLOR
{ "thistle", 0xd8bfd8},
{ "tomato", 0xff6347},
{ "turquoise", 0x40e0d0},
{ "violet", 0xee82ee},
{ "wheat", 0xf5deb3},
#endif
{ "white", 0xffffff},
#ifdef EXTENDED_COLOR
{ "whitesmoke", 0xf5f5f5},
#endif
{ "yellow", 0xffff00},
#ifdef EXTENDED_COLOR
{ "yellowgreen", 0x9acd32},
#endif
};
#define NCOLORS (sizeof(color_keyword) / sizeof(color_keyword[0]))
/*
* Parse a color in hex (RRGGBB) or (RGB)
*
* Return Value:
* parsed color if successful (err = 0),
* default_color on error (err = 1).
*/
static int32_t Color_parse_hex (const char *s, int32_t default_color, int *err)
{
int32_t ret_color;
char *tail;
*err = 1;
ret_color = strtol(s, &tail, 16);
if (tail - s == 6)
*err = 0;
else if (tail - s == 3) { /* #RGB as allowed by CSS */
*err = 0;
ret_color = ((ret_color & 0xf00) << 12) | ((ret_color & 0xf00) << 8) |
((ret_color & 0x0f0) << 8) | ((ret_color & 0x0f0) << 4) |
((ret_color & 0x00f) << 4) | ((ret_color & 0x00f) << 0);
} else
ret_color = default_color;
return ret_color;
}
/*
* Parse a color string.
*
* - If the string begins with # or with 0x, return the color number
* (with 'RGB' expanded to 'RRGGBB').
* - Else search the set of named colors.
* - As a last resort, treat it as bare hex as in the first case.
*
* Return Value:
* Parsed color if successful,
* default_color on error.
*
* "err" argument:
* 0 if a color beginning with '#' is successfully parsed
* or the color is a recognized word.
* 1 if the color is bare hex or can't be parsed at all.
* 2 if a color beginning with 0[xX] is successfully parsed.
*/
int32_t a_Color_parse (const char *str, int32_t default_color, int *err)
{
const char *cp;
int32_t ret_color;
int ret, low, mid, high, st = 1;
/* skip leading spaces */
for (cp = str; dIsspace(*cp); cp++);
ret_color = default_color;
if (*cp == '#') {
ret_color = Color_parse_hex(cp + 1, default_color, &st);
} else if (*cp == '0' && (cp[1] == 'x' || cp[1] == 'X') ) {
ret_color = Color_parse_hex(cp + 2, default_color, &st);
if (!st)
st = 2;
} else {
/* Binary search */
low = 0;
high = NCOLORS - 1;
while (low <= high) {
mid = (low + high) / 2;
if ((ret = dStrAsciiCasecmp(cp, color_keyword[mid].key)) < 0)
high = mid - 1;
else if (ret > 0)
low = mid + 1;
else {
ret_color = color_keyword[mid].val;
st = 0;
break;
}
}
if (low > high) {
/* try for RRGGBB lacking the leading '#' */
ret_color = Color_parse_hex(cp, default_color, &st);
st = 1;
}
}
_MSG("color string: %s\n", str);
_MSG("color : %X\n", ret_color);
*err = st;
return ret_color;
}
#if 0
/*
* Return a "distance" measure (between [0, 10])
*/
static int Color_distance(long c1, long c2)
{
return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) +
labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) +
labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75;
}
#endif
/*
* Return: [0-3]
*/
static int Color_distance2(long c1, long c2)
{
return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000060) +
(labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x006000) +
(labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x600000);
}
/*
* Return: [0-3] (requires less contrast than distance2)
*/
static int Color_distance3(long c1, long c2)
{
return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000040) +
(labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x004000) +
(labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x400000);
}
/*
* Return a suitable "visited link" color
* Return value:
* if candidate has good contrast with C_txt, C_lnk and C_bg -> candidate
* else another color (from the internal list)
*/
int32_t a_Color_vc(int32_t candidate,
int32_t C_txt, int32_t C_lnk, int32_t C_bg)
{
/* candidate purple darkcyan darkmagenta olive */
static int32_t v[] = {0x000000, 0x800080, 0x008b8b, 0x8b008b, 0x808000,
/* darkred coral black */
0x8b0000, 0xff7f50, 0x000000};
int v_size = sizeof(v) / sizeof(v[0]);
int i, max_i, score, max_score, d_bg, d_txt, d_lnk;
/* set candidate in the list */
v[0] = candidate;
/* Try to get good overall and individual contrast */
max_i = max_score = 0;
for (i = 0; i < v_size; ++i) {
_MSG("a_Color_vc: [%d]%.6x: %d %d %d\n", i, v[i],
Color_distance2(C_txt, v[i]),
Color_distance2(C_lnk, v[i]),
Color_distance2(C_bg, v[i]));
/* Tuned with: slashdot.org, paulgraham.com, newsforge.com,
* linuxjournal.com
*/
d_txt = Color_distance2(C_txt, v[i]);
d_lnk = Color_distance2(C_lnk, v[i]);
d_bg = Color_distance2(C_bg, v[i]);
score = (d_bg >= 2 ? 4 : 2 * d_bg) +
(d_txt + d_lnk >= 2 ? 2 : d_txt + d_lnk) +
(Color_distance3(C_lnk, v[i]) >= 1 ? 1 : 0);
if (score >= 7) {
/* enough distance, use this color */
max_i = i;
break;
} else if (score > max_score) {
/* keep track of the best candidate so far */
max_score = score;
max_i = i;
}
}
return v[max_i];
}