summaryrefslogtreecommitdiff
path: root/libs/litehtml/src/web_color.cpp
diff options
context:
space:
mode:
authorGeorge Hazan <george.hazan@gmail.com>2024-10-09 18:13:40 +0300
committerGeorge Hazan <george.hazan@gmail.com>2024-10-09 18:13:40 +0300
commit0e86b853be3b5f809ed1decbf636221c1144a386 (patch)
tree4ce84d9849646a559d5afece33fc6e64d39e4a50 /libs/litehtml/src/web_color.cpp
parent834cbb58d74215980165eab257538ba918a378cd (diff)
litehtml update
Diffstat (limited to 'libs/litehtml/src/web_color.cpp')
-rw-r--r--libs/litehtml/src/web_color.cpp349
1 files changed, 259 insertions, 90 deletions
diff --git a/libs/litehtml/src/web_color.cpp b/libs/litehtml/src/web_color.cpp
index a2e74ff58d..51c6e762f1 100644
--- a/libs/litehtml/src/web_color.cpp
+++ b/libs/litehtml/src/web_color.cpp
@@ -1,14 +1,24 @@
#include "html.h"
#include "web_color.h"
-#include <cstring>
+#include "css_parser.h"
-const litehtml::web_color litehtml::web_color::transparent = web_color(0, 0, 0, 0);
-const litehtml::web_color litehtml::web_color::black = web_color(0, 0, 0, 255);
-const litehtml::web_color litehtml::web_color::white = web_color(255, 255, 255, 255);
+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;
-litehtml::background_gradient litehtml::background_gradient::transparent;
+struct def_color
+{
+ const char* name;
+ const char* rgb;
+};
-litehtml::def_color litehtml::g_def_colors[] =
+def_color g_def_colors[] =
{
{"transparent","rgba(0, 0, 0, 0)"},
{"AliceBlue","#F0F8FF"},
@@ -156,121 +166,280 @@ litehtml::def_color litehtml::g_def_colors[] =
{"WhiteSmoke","#F5F5F5"},
{"Yellow","#FFFF00"},
{"YellowGreen","#9ACD32"},
- {nullptr,nullptr}
};
-
-litehtml::web_color litehtml::web_color::from_string(const string& _str, document_container* callback)
+// <hex-color> https://drafts.csswg.org/css-color-4/#typedef-hex-color
+bool parse_hash_color(const css_token& tok, web_color& color)
{
- auto str = _str.c_str();
- if(!str[0])
+ 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)
{
- return web_color(0, 0, 0);
+ r = {s[0], s[0]};
+ g = {s[1], s[1]};
+ b = {s[2], s[2]};
+ if (len == 4)
+ a = {s[3], s[3]};
}
- if(str[0] == '#')
+ else // 6 or 8
{
- string red;
- string green;
- string blue;
- if(strlen(str + 1) == 3)
- {
- red += str[1];
- red += str[1];
- green += str[2];
- green += str[2];
- blue += str[3];
- blue += str[3];
- } else if(strlen(str + 1) == 6)
- {
- red += str[1];
- red += str[2];
- green += str[3];
- green += str[4];
- blue += str[5];
- blue += str[6];
- }
- char* sss = nullptr;
- web_color clr;
- clr.red = (byte) strtol(red.c_str(), &sss, 16);
- clr.green = (byte) strtol(green.c_str(), &sss, 16);
- clr.blue = (byte) strtol(blue.c_str(), &sss, 16);
- return clr;
- } else if(!strncmp(str, "rgb", 3))
+ 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)
{
- string s = str;
+ return byte(16 * digit_value(str[0]) + digit_value(str[1]));
+ };
- string::size_type pos = s.find_first_of('(');
- if(pos != string::npos)
- {
- s.erase(s.begin(), s.begin() + pos + 1);
- }
- pos = s.find_last_of(')');
- if(pos != string::npos)
+ 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;
+}
+
+// [ <number> | <percentage> | none ]{3} [ / [<alpha-value> | 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)
+ {
+ // [<hue> | none] [<number> | <percentage> | none]{2} [ / [<alpha-value> | none] ]?
+ // <hue> = <number> | <angle>
+ if (!x.from_token(tokens[0], f_number, "none"))
{
- s.erase(s.begin() + pos, s.end());
+ 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;
+ // <alpha-value> = <number> | <percentage>
+ 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;
+}
- std::vector<string> tokens;
- split_string(s, tokens, ", \t");
-
- web_color clr;
+// 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);
+}
- if(tokens.size() >= 1) clr.red = (byte) atoi(tokens[0].c_str());
- if(tokens.size() >= 2) clr.green = (byte) atoi(tokens[1].c_str());
- if(tokens.size() >= 3) clr.blue = (byte) atoi(tokens[2].c_str());
- if(tokens.size() >= 4) clr.alpha = (byte) (t_strtod(tokens[3].c_str(), nullptr) * 255.0);
+// 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;
- return clr;
- } else
+ 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: <percentage>#{3} , <alpha-value>? | <number>#{3} , <alpha-value>?
+ if (n != 1)
{
- string rgb = resolve_name(str, callback);
- if(!rgb.empty())
- {
- return from_string(rgb.c_str(), callback);
- }
+ 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;
+ // <alpha-value> = <number> | <percentage>
+ if (n == 4 && !a.from_token(list[3][0], f_number | f_percentage)) return false;
}
- return web_color(0, 0, 0);
+ // modern syntax: [ <number> | <percentage> | none ]{3} [ / [<alpha-value> | 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);
}
-litehtml::string litehtml::web_color::resolve_name(const string& name, document_container* callback)
+// https://drafts.csswg.org/css-color-4/#the-hsl-notation
+bool parse_hsl_func(const css_token& tok, web_color& color)
{
- for(int i=0; g_def_colors[i].name; i++)
+ 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: <hue>, <percentage>, <percentage>, <alpha-value>?
+ if (n != 1)
{
- if(!t_strcasecmp(name.c_str(), g_def_colors[i].name))
- {
- return g_def_colors[i].rgb;
- }
+ for (const auto& item : list) if (item.size() != 1) return false;
+ const auto& tok0 = list[0][0];
+ float hue;
+ // <hue> = <number> | <angle>
+ // 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;
}
- if (callback)
- {
- string clr = callback->resolve_color(name);
- return clr;
- }
- return "";
+ // modern syntax: [<hue> | none] [<percentage> | <number> | none]{2} [ / [<alpha-value> | 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;
}
-bool litehtml::web_color::is_color(const string& str, document_container* callback)
+// https://drafts.csswg.org/css-color-5/#typedef-color-function
+bool parse_func_color(const css_token& tok, web_color& color)
{
- if (!t_strncasecmp(str.c_str(), "rgb", 3) || str[0] == '#')
+ 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)
{
- return true;
+ if (equal_i(name, clr.name))
+ return clr.rgb;
}
- if (t_isalpha(str[0]) && resolve_name(str, callback) != "")
+
+ 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;
}
- return false;
+ 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);
}
-litehtml::string litehtml::web_color::to_string() const
+// https://drafts.csswg.org/css-color-5/#typedef-color
+bool parse_color(const css_token& tok, web_color& color, document_container* container)
{
- char str[9];
- if(alpha)
- {
+ 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
- {
+ } else
+ {
t_snprintf(str, 9, "%02X%02X%02X", red, green, blue);
- }
- return str;
+ }
+ return str;
}
+
+} // namespace litehtml \ No newline at end of file