#include "html.h"
#include "web_color.h"
#include "css_parser.h"
namespace litehtml
{
const web_color web_color::transparent = web_color(0, 0, 0, 0);
const web_color web_color::black = web_color(0, 0, 0, 255);
const web_color web_color::white = web_color(255, 255, 255, 255);
const web_color web_color::current_color = web_color(true);
gradient gradient::transparent;
struct def_color
{
const char* name;
const char* rgb;
};
def_color g_def_colors[] =
{
{"transparent","rgba(0, 0, 0, 0)"},
{"AliceBlue","#F0F8FF"},
{"AntiqueWhite","#FAEBD7"},
{"Aqua","#00FFFF"},
{"Aquamarine","#7FFFD4"},
{"Azure","#F0FFFF"},
{"Beige","#F5F5DC"},
{"Bisque","#FFE4C4"},
{"Black","#000000"},
{"BlanchedAlmond","#FFEBCD"},
{"Blue","#0000FF"},
{"BlueViolet","#8A2BE2"},
{"Brown","#A52A2A"},
{"BurlyWood","#DEB887"},
{"CadetBlue","#5F9EA0"},
{"Chartreuse","#7FFF00"},
{"Chocolate","#D2691E"},
{"Coral","#FF7F50"},
{"CornflowerBlue","#6495ED"},
{"Cornsilk","#FFF8DC"},
{"Crimson","#DC143C"},
{"Cyan","#00FFFF"},
{"DarkBlue","#00008B"},
{"DarkCyan","#008B8B"},
{"DarkGoldenRod","#B8860B"},
{"DarkGray","#A9A9A9"},
{"DarkGrey","#A9A9A9"},
{"DarkGreen","#006400"},
{"DarkKhaki","#BDB76B"},
{"DarkMagenta","#8B008B"},
{"DarkOliveGreen","#556B2F"},
{"Darkorange","#FF8C00"},
{"DarkOrchid","#9932CC"},
{"DarkRed","#8B0000"},
{"DarkSalmon","#E9967A"},
{"DarkSeaGreen","#8FBC8F"},
{"DarkSlateBlue","#483D8B"},
{"DarkSlateGray","#2F4F4F"},
{"DarkSlateGrey","#2F4F4F"},
{"DarkTurquoise","#00CED1"},
{"DarkViolet","#9400D3"},
{"DeepPink","#FF1493"},
{"DeepSkyBlue","#00BFFF"},
{"DimGray","#696969"},
{"DimGrey","#696969"},
{"DodgerBlue","#1E90FF"},
{"FireBrick","#B22222"},
{"FloralWhite","#FFFAF0"},
{"ForestGreen","#228B22"},
{"Fuchsia","#FF00FF"},
{"Gainsboro","#DCDCDC"},
{"GhostWhite","#F8F8FF"},
{"Gold","#FFD700"},
{"GoldenRod","#DAA520"},
{"Gray","#808080"},
{"Grey","#808080"},
{"Green","#008000"},
{"GreenYellow","#ADFF2F"},
{"HoneyDew","#F0FFF0"},
{"HotPink","#FF69B4"},
{"Ivory","#FFFFF0"},
{"Khaki","#F0E68C"},
{"Lavender","#E6E6FA"},
{"LavenderBlush","#FFF0F5"},
{"LawnGreen","#7CFC00"},
{"LemonChiffon","#FFFACD"},
{"LightBlue","#ADD8E6"},
{"LightCoral","#F08080"},
{"LightCyan","#E0FFFF"},
{"LightGoldenRodYellow","#FAFAD2"},
{"LightGray","#D3D3D3"},
{"LightGrey","#D3D3D3"},
{"LightGreen","#90EE90"},
{"LightPink","#FFB6C1"},
{"LightSalmon","#FFA07A"},
{"LightSeaGreen","#20B2AA"},
{"LightSkyBlue","#87CEFA"},
{"LightSlateGray","#778899"},
{"LightSlateGrey","#778899"},
{"LightSteelBlue","#B0C4DE"},
{"LightYellow","#FFFFE0"},
{"Lime","#00FF00"},
{"LimeGreen","#32CD32"},
{"Linen","#FAF0E6"},
{"Magenta","#FF00FF"},
{"Maroon","#800000"},
{"MediumAquaMarine","#66CDAA"},
{"MediumBlue","#0000CD"},
{"MediumOrchid","#BA55D3"},
{"MediumPurple","#9370D8"},
{"MediumSeaGreen","#3CB371"},
{"MediumSlateBlue","#7B68EE"},
{"MediumSpringGreen","#00FA9A"},
{"MediumTurquoise","#48D1CC"},
{"MediumVioletRed","#C71585"},
{"MidnightBlue","#191970"},
{"MintCream","#F5FFFA"},
{"MistyRose","#FFE4E1"},
{"Moccasin","#FFE4B5"},
{"NavajoWhite","#FFDEAD"},
{"Navy","#000080"},
{"OldLace","#FDF5E6"},
{"Olive","#808000"},
{"OliveDrab","#6B8E23"},
{"Orange","#FFA500"},
{"OrangeRed","#FF4500"},
{"Orchid","#DA70D6"},
{"PaleGoldenRod","#EEE8AA"},
{"PaleGreen","#98FB98"},
{"PaleTurquoise","#AFEEEE"},
{"PaleVioletRed","#D87093"},
{"PapayaWhip","#FFEFD5"},
{"PeachPuff","#FFDAB9"},
{"Peru","#CD853F"},
{"Pink","#FFC0CB"},
{"Plum","#DDA0DD"},
{"PowderBlue","#B0E0E6"},
{"Purple","#800080"},
{"Red","#FF0000"},
{"RosyBrown","#BC8F8F"},
{"RoyalBlue","#4169E1"},
{"SaddleBrown","#8B4513"},
{"Salmon","#FA8072"},
{"SandyBrown","#F4A460"},
{"SeaGreen","#2E8B57"},
{"SeaShell","#FFF5EE"},
{"Sienna","#A0522D"},
{"Silver","#C0C0C0"},
{"SkyBlue","#87CEEB"},
{"SlateBlue","#6A5ACD"},
{"SlateGray","#708090"},
{"SlateGrey","#708090"},
{"Snow","#FFFAFA"},
{"SpringGreen","#00FF7F"},
{"SteelBlue","#4682B4"},
{"Tan","#D2B48C"},
{"Teal","#008080"},
{"Thistle","#D8BFD8"},
{"Tomato","#FF6347"},
{"Turquoise","#40E0D0"},
{"Violet","#EE82EE"},
{"Wheat","#F5DEB3"},
{"White","#FFFFFF"},
{"WhiteSmoke","#F5F5F5"},
{"Yellow","#FFFF00"},
{"YellowGreen","#9ACD32"},
};
// https://drafts.csswg.org/css-color-4/#typedef-hex-color
bool parse_hash_color(const css_token& tok, web_color& color)
{
if (tok.type != HASH) return false;
string s = tok.str;
int len = (int)s.size();
if (!is_one_of(len, 3, 4, 6, 8)) return false;
for (auto ch : s) if (!is_hex_digit(ch)) return false;
string r, g, b, a = "ff";
if (len == 3 || len == 4)
{
r = {s[0], s[0]};
g = {s[1], s[1]};
b = {s[2], s[2]};
if (len == 4)
a = {s[3], s[3]};
}
else // 6 or 8
{
r = {s[0], s[1]};
g = {s[2], s[3]};
b = {s[4], s[5]};
if (len == 8)
a = {s[6], s[7]};
}
auto read_two_hex_digits = [](auto str)
{
return byte(16 * digit_value(str[0]) + digit_value(str[1]));
};
color = web_color(
read_two_hex_digits(r),
read_two_hex_digits(g),
read_two_hex_digits(b),
read_two_hex_digits(a));
return true;
}
float clamp(float x, float min, float max)
{
if (x < min) return min;
if (x > max) return max;
return x;
}
// [ | | none ]{3} [ / [ | none] ]?
bool parse_modern_syntax(const css_token_vector& tokens, bool is_hsl,
css_length& x, css_length& y, css_length& z, css_length& a)
{
auto n = tokens.size();
if (!(n == 3 || n == 5)) return false;
if (is_hsl)
{
// [ | none] [ | | none]{2} [ / [ | none] ]?
// = |
if (!x.from_token(tokens[0], f_number, "none"))
{
float hue;
if (!parse_angle(tokens[0], hue)) return false;
x.set_value(hue, css_units_none);
}
}
else if (!x.from_token(tokens[0], f_number | f_percentage, "none")) return false;
if (!y.from_token(tokens[1], f_number | f_percentage, "none")) return false;
if (!z.from_token(tokens[2], f_number | f_percentage, "none")) return false;
if (n == 5)
{
if (tokens[3].ch != '/') return false;
// = |
if (!a.from_token(tokens[4], f_number | f_percentage, "none")) return false;
}
// convert nones to zeros
// https://drafts.csswg.org/css-color-4/#missing
// For all other purposes, a missing component behaves as a zero value...
for (auto t : {&x,&y,&z,&a}) if (t->is_predefined()) t->set_value(0, css_units_none);
return true;
}
// https://drafts.csswg.org/css-color-4/#rgb-functions
// Values outside these ranges are not invalid, but are clamped to the ranges defined here at parsed-value time.
byte calc_percent_and_clamp(const css_length& val, float max = 255)
{
float x = val.val();
if (val.units() == css_units_percentage) x = (x / 100) * max;
x = clamp(x, 0, max);
return (byte)round(max == 1 ? x * 255 : x);
}
// https://drafts.csswg.org/css-color-4/#rgb-functions
bool parse_rgb_func(const css_token& tok, web_color& color)
{
if (tok.type != CV_FUNCTION || !is_one_of(lowcase(tok.name), "rgb", "rgba"))
return false;
auto list = parse_comma_separated_list(tok.value);
int n = (int)list.size();
if (!is_one_of(n, 1, 3, 4))
return false;
css_length r, g, b, a(1, css_units_none);
// legacy syntax: #{3} , ? | #{3} , ?
if (n != 1)
{
for (const auto& item : list) if (item.size() != 1) return false;
auto type = list[0][0].type;
if (!is_one_of(type, PERCENTAGE, NUMBER)) return false;
int options = type == PERCENTAGE ? f_percentage : f_number;
if (!r.from_token(list[0][0], options)) return false;
if (!g.from_token(list[1][0], options)) return false;
if (!b.from_token(list[2][0], options)) return false;
// = |
if (n == 4 && !a.from_token(list[3][0], f_number | f_percentage)) return false;
}
// modern syntax: [ | | none ]{3} [ / [ | none] ]?
else if (!parse_modern_syntax(tok.value, false, r, g, b, a)) return false;
color = web_color(
calc_percent_and_clamp(r),
calc_percent_and_clamp(g),
calc_percent_and_clamp(b),
calc_percent_and_clamp(a, 1));
return true;
}
// https://drafts.csswg.org/css-color-4/#hsl-to-rgb
void hsl_to_rgb(float hue, float sat, float light, float& r, float& g, float& b)
{
hue = fmod(hue, 360.f);
if (hue < 0)
hue += 360;
sat /= 100;
light /= 100;
auto f = [=](float n)
{
float k = fmod(n + hue / 30, 12.f);
float a = sat * min(light, 1 - light);
return light - a * max(-1.f, min({k - 3, 9 - k, 1.f}));
};
r = f(0);
g = f(8);
b = f(4);
}
// https://drafts.csswg.org/css-color-4/#the-hsl-notation
bool parse_hsl_func(const css_token& tok, web_color& color)
{
if (tok.type != CV_FUNCTION || !is_one_of(lowcase(tok.name), "hsl", "hsla"))
return false;
auto list = parse_comma_separated_list(tok.value);
int n = (int)list.size();
if (!is_one_of(n, 1, 3, 4))
return false;
css_length h, s, l, a(1, css_units_none);
// legacy syntax: , , , ?
if (n != 1)
{
for (const auto& item : list) if (item.size() != 1) return false;
const auto& tok0 = list[0][0];
float hue;
// = |
// number is interpreted as a number of degrees https://drafts.csswg.org/css-color-4/#typedef-hue
if (tok0.type == NUMBER) hue = tok0.n.number;
else if (!parse_angle(tok0, hue)) return false;
h.set_value(hue, css_units_none);
if (!s.from_token(list[1][0], f_percentage)) return false;
if (!l.from_token(list[2][0], f_percentage)) return false;
if (n == 4 && !a.from_token(list[3][0], f_number | f_percentage)) return false;
}
// modern syntax: [ | none] [ | | none]{2} [ / [ | none] ]?
else if (!parse_modern_syntax(tok.value, true, h, s, l, a)) return false;
float hue = h.val();
// no percent calculation needed for sat and lit because 0% ~ 0, 100% ~ 100
float sat = s.val();
float lit = l.val();
// For historical reasons, if the saturation is less than 0% it is clamped to 0% at parsed-value time, before being converted to an sRGB color.
if (sat < 0) sat = 0;
// Note: Chrome and Firefox treat invalid hsl values differently.
//
// Note: at this point, sat is not clamped at 100, and lit is not clamped at all. The standard
// mentions only clamping sat at 0. As a result, returning rgb values may not be inside [0,1].
float r, g, b;
hsl_to_rgb(hue, sat, lit, r, g, b);
r = clamp(r, 0, 1);
g = clamp(g, 0, 1);
b = clamp(b, 0, 1);
color = web_color(
(byte)round(r * 255),
(byte)round(g * 255),
(byte)round(b * 255),
calc_percent_and_clamp(a, 1));
return true;
}
// https://drafts.csswg.org/css-color-5/#typedef-color-function
bool parse_func_color(const css_token& tok, web_color& color)
{
return parse_rgb_func(tok, color) || parse_hsl_func(tok, color);
}
string resolve_name(const string& name, document_container* container)
{
for (auto clr : g_def_colors)
{
if (equal_i(name, clr.name))
return clr.rgb;
}
if (container)
return container->resolve_color(name);
return "";
}
bool parse_name_color(const css_token& tok, web_color& color, document_container* container)
{
if (tok.type != IDENT) return false;
if (tok.ident() == "currentcolor")
{
color = web_color::current_color;
return true;
}
string str = resolve_name(tok.name, container);
auto tokens = normalize(str, f_componentize | f_remove_whitespace);
if (tokens.size() != 1) return false;
return parse_color(tokens[0], color, container);
}
// https://drafts.csswg.org/css-color-5/#typedef-color
bool parse_color(const css_token& tok, web_color& color, document_container* container)
{
return
parse_hash_color(tok, color) ||
parse_func_color(tok, color) ||
parse_name_color(tok, color, container);
}
web_color web_color::darken(double fraction) const
{
int v_red = (int)red;
int v_blue = (int)blue;
int v_green = (int)green;
v_red = (int)max(v_red - (v_red * fraction), 0.0);
v_blue = (int)max(v_blue - (v_blue * fraction), 0.0);
v_green = (int)max(v_green - (v_green * fraction), 0.0);
return {(byte)v_red, (byte)v_green, (byte)v_blue, alpha};
}
string web_color::to_string() const
{
char str[9];
if(alpha)
{
t_snprintf(str, 9, "%02X%02X%02X%02X", red, green, blue, alpha);
} else
{
t_snprintf(str, 9, "%02X%02X%02X", red, green, blue);
}
return str;
}
} // namespace litehtml