diff --git a/Source/Core/PropertyParserColour.cpp b/Source/Core/PropertyParserColour.cpp index c0bc8f7d8..95d5915d4 100644 --- a/Source/Core/PropertyParserColour.cpp +++ b/Source/Core/PropertyParserColour.cpp @@ -27,10 +27,34 @@ */ #include "PropertyParserColour.h" +#include +#include +#include #include namespace Rml { +// Helper function for hsl->rgb conversion. +static float HSL_f(float h, float s, float l, float n) { + float k = std::fmod((n + h * (1.0f / 30.0f)), 12.0f); + float a = s * std::min(l, 1.0f - l); + return l - a * std::max(-1.0f, std::min({k - 3.0f, 9.0f - k, 1.0f})); +} + +// Ref: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative +static void HSLAToRGBA(std::array& vals) { + if (vals[1] == 0.0f) { + vals[0] = vals[1] = vals[2]; + } + else + { + auto [h, s, l, _] = vals; + vals[0] = HSL_f(h, s, l, 0.0f); + vals[1] = HSL_f(h, s, l, 8.0f); + vals[2] = HSL_f(h, s, l, 4.0f); + } +} + const PropertyParserColour::ColourMap PropertyParserColour::html_colours = { {"black", Colourb(0, 0, 0)}, {"silver", Colourb(192, 192, 192)}, @@ -116,7 +140,8 @@ bool PropertyParserColour::ParseColour(Colourb& colour, const String& value) colour[i] = (byte)(tens * 16 + ones); } } - else if (value.substr(0, 3) == "rgb") + else if (auto prefix = value.substr(0, 3); + prefix == "rgb" || prefix == "hsl") { StringList values; values.reserve(4); @@ -129,33 +154,70 @@ bool PropertyParserColour::ParseColour(Colourb& colour, const String& value) StringUtilities::ExpandString(values, value.substr(begin_values, value.rfind(')') - begin_values), ','); - // Check if we're parsing an 'rgba' or 'rgb' colour declaration. - if (value.size() > 3 && value[3] == 'a') + if (prefix == "rgb") { - if (values.size() != 4) - return false; + // Check if we're parsing an 'rgba' or 'rgb' colour declaration. + if (value.size() > 3 && value[3] == 'a') + { + if (values.size() != 4) + return false; + } + else + { + if (values.size() != 3) + return false; + + values.push_back("255"); + } + + // Parse the RGBA values. + for (int i = 0; i < 4; ++i) + { + int component; + + // We're parsing a percentage value. + if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') + component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f)); + // We're parsing a 0 -> 255 integer value. + else + component = atoi(values[i].c_str()); + + colour[i] = (byte)(Math::Clamp(component, 0, 255)); + } } else { - if (values.size() != 3) - return false; - - values.push_back("255"); - } - - // Parse the three RGB values. - for (int i = 0; i < 4; ++i) - { - int component; - - // We're parsing a percentage value. - if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') - component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f)); - // We're parsing a 0 -> 255 integer value. + // Check if we're parsing an 'hsla' or 'hsl' colour declaration. + if (value.size() > 3 && value[3] == 'a') + { + if (values.size() != 4) + return false; + } else - component = atoi(values[i].c_str()); - - colour[i] = (byte)(Math::Clamp(component, 0, 255)); + { + if (values.size() != 3) + return false; + + values.push_back("1.0"); + } + + // Parse the HSLA values. + std::array vals; + // H is a number in degrees, A is a number between 0.0 and 1.0. + for (int i : {0, 3}) + vals[i] = (float)atof(values[i].c_str()); + // S and L are percentage values. + for (int i: {1, 2}) + if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') + vals[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (1.0f / 100.0f); + else + return false; + + HSLAToRGBA(vals); + for (int i = 0; i < 4; ++i) + { + colour[i] = (byte)(Math::Clamp((int)(vals[i] * 255.0f), 0, 255)); + } } } else diff --git a/Tests/Source/UnitTests/Properties.cpp b/Tests/Source/UnitTests/Properties.cpp index c563d0ce1..0b8a796fc 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -142,6 +142,15 @@ TEST_CASE("Properties") ColorStop{ColourbPremultiplied(0, 150, 0, 150), NumericValue{10.f, Unit::DP}}, }, }, + { + "hsl(233, 0%, 50%), hsl(240, 100%, 50%) 50%, hsla(120, 100%, 50%, 0.5) 10dp", + "#7f7f7f, #0000ff 50%, #00ff007f 10dp", + { + ColorStop{ColourbPremultiplied(127, 127, 127), NumericValue{}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, + ColorStop{ColourbPremultiplied(0, 127, 0, 127), NumericValue{10.f, Unit::DP}}, + }, + }, { "red 50px 20%, blue 10in", "#ff0000 50px, #ff0000 20%, #0000ff 10in",