|
| 1 | +// (c) Dean McNamee <dean@gmail.com>, 2012. |
| 2 | +// C++ port by Konstantin Käfer <mail@kkaefer.com>, 2014. |
| 3 | +// |
| 4 | +// https://github.com/deanm/css-color-parser-js |
| 5 | +// https://github.com/kkaefer/css-color-parser-cpp |
| 6 | +// |
| 7 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | +// of this software and associated documentation files (the "Software"), to |
| 9 | +// deal in the Software without restriction, including without limitation the |
| 10 | +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| 11 | +// sell copies of the Software, and to permit persons to whom the Software is |
| 12 | +// furnished to do so, subject to the following conditions: |
| 13 | +// |
| 14 | +// The above copyright notice and this permission notice shall be included in |
| 15 | +// all copies or substantial portions of the Software. |
| 16 | +// |
| 17 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 20 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 22 | +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 23 | +// IN THE SOFTWARE. |
| 24 | + |
| 25 | +#include "csscolorparser.hpp" |
| 26 | + |
| 27 | +#include <cstdint> |
| 28 | +#include <vector> |
| 29 | +#include <sstream> |
| 30 | +#include <cmath> |
| 31 | +#include <map> |
| 32 | + |
| 33 | +using namespace CSSColorParser; |
| 34 | + |
| 35 | +// http://www.w3.org/TR/css3-color/ |
| 36 | +const std::map<std::string, Color> kCSSColorTable = { |
| 37 | + { "transparent", { 0, 0, 0, 0 } }, { "aliceblue", { 240, 248, 255, 1 } }, |
| 38 | + { "antiquewhite", { 250, 235, 215, 1 } }, { "aqua", { 0, 255, 255, 1 } }, |
| 39 | + { "aquamarine", { 127, 255, 212, 1 } }, { "azure", { 240, 255, 255, 1 } }, |
| 40 | + { "beige", { 245, 245, 220, 1 } }, { "bisque", { 255, 228, 196, 1 } }, |
| 41 | + { "black", { 0, 0, 0, 1 } }, { "blanchedalmond", { 255, 235, 205, 1 } }, |
| 42 | + { "blue", { 0, 0, 255, 1 } }, { "blueviolet", { 138, 43, 226, 1 } }, |
| 43 | + { "brown", { 165, 42, 42, 1 } }, { "burlywood", { 222, 184, 135, 1 } }, |
| 44 | + { "cadetblue", { 95, 158, 160, 1 } }, { "chartreuse", { 127, 255, 0, 1 } }, |
| 45 | + { "chocolate", { 210, 105, 30, 1 } }, { "coral", { 255, 127, 80, 1 } }, |
| 46 | + { "cornflowerblue", { 100, 149, 237, 1 } }, { "cornsilk", { 255, 248, 220, 1 } }, |
| 47 | + { "crimson", { 220, 20, 60, 1 } }, { "cyan", { 0, 255, 255, 1 } }, |
| 48 | + { "darkblue", { 0, 0, 139, 1 } }, { "darkcyan", { 0, 139, 139, 1 } }, |
| 49 | + { "darkgoldenrod", { 184, 134, 11, 1 } }, { "darkgray", { 169, 169, 169, 1 } }, |
| 50 | + { "darkgreen", { 0, 100, 0, 1 } }, { "darkgrey", { 169, 169, 169, 1 } }, |
| 51 | + { "darkkhaki", { 189, 183, 107, 1 } }, { "darkmagenta", { 139, 0, 139, 1 } }, |
| 52 | + { "darkolivegreen", { 85, 107, 47, 1 } }, { "darkorange", { 255, 140, 0, 1 } }, |
| 53 | + { "darkorchid", { 153, 50, 204, 1 } }, { "darkred", { 139, 0, 0, 1 } }, |
| 54 | + { "darksalmon", { 233, 150, 122, 1 } }, { "darkseagreen", { 143, 188, 143, 1 } }, |
| 55 | + { "darkslateblue", { 72, 61, 139, 1 } }, { "darkslategray", { 47, 79, 79, 1 } }, |
| 56 | + { "darkslategrey", { 47, 79, 79, 1 } }, { "darkturquoise", { 0, 206, 209, 1 } }, |
| 57 | + { "darkviolet", { 148, 0, 211, 1 } }, { "deeppink", { 255, 20, 147, 1 } }, |
| 58 | + { "deepskyblue", { 0, 191, 255, 1 } }, { "dimgray", { 105, 105, 105, 1 } }, |
| 59 | + { "dimgrey", { 105, 105, 105, 1 } }, { "dodgerblue", { 30, 144, 255, 1 } }, |
| 60 | + { "firebrick", { 178, 34, 34, 1 } }, { "floralwhite", { 255, 250, 240, 1 } }, |
| 61 | + { "forestgreen", { 34, 139, 34, 1 } }, { "fuchsia", { 255, 0, 255, 1 } }, |
| 62 | + { "gainsboro", { 220, 220, 220, 1 } }, { "ghostwhite", { 248, 248, 255, 1 } }, |
| 63 | + { "gold", { 255, 215, 0, 1 } }, { "goldenrod", { 218, 165, 32, 1 } }, |
| 64 | + { "gray", { 128, 128, 128, 1 } }, { "green", { 0, 128, 0, 1 } }, |
| 65 | + { "greenyellow", { 173, 255, 47, 1 } }, { "grey", { 128, 128, 128, 1 } }, |
| 66 | + { "honeydew", { 240, 255, 240, 1 } }, { "hotpink", { 255, 105, 180, 1 } }, |
| 67 | + { "indianred", { 205, 92, 92, 1 } }, { "indigo", { 75, 0, 130, 1 } }, |
| 68 | + { "ivory", { 255, 255, 240, 1 } }, { "khaki", { 240, 230, 140, 1 } }, |
| 69 | + { "lavender", { 230, 230, 250, 1 } }, { "lavenderblush", { 255, 240, 245, 1 } }, |
| 70 | + { "lawngreen", { 124, 252, 0, 1 } }, { "lemonchiffon", { 255, 250, 205, 1 } }, |
| 71 | + { "lightblue", { 173, 216, 230, 1 } }, { "lightcoral", { 240, 128, 128, 1 } }, |
| 72 | + { "lightcyan", { 224, 255, 255, 1 } }, { "lightgoldenrodyellow", { 250, 250, 210, 1 } }, |
| 73 | + { "lightgray", { 211, 211, 211, 1 } }, { "lightgreen", { 144, 238, 144, 1 } }, |
| 74 | + { "lightgrey", { 211, 211, 211, 1 } }, { "lightpink", { 255, 182, 193, 1 } }, |
| 75 | + { "lightsalmon", { 255, 160, 122, 1 } }, { "lightseagreen", { 32, 178, 170, 1 } }, |
| 76 | + { "lightskyblue", { 135, 206, 250, 1 } }, { "lightslategray", { 119, 136, 153, 1 } }, |
| 77 | + { "lightslategrey", { 119, 136, 153, 1 } }, { "lightsteelblue", { 176, 196, 222, 1 } }, |
| 78 | + { "lightyellow", { 255, 255, 224, 1 } }, { "lime", { 0, 255, 0, 1 } }, |
| 79 | + { "limegreen", { 50, 205, 50, 1 } }, { "linen", { 250, 240, 230, 1 } }, |
| 80 | + { "magenta", { 255, 0, 255, 1 } }, { "maroon", { 128, 0, 0, 1 } }, |
| 81 | + { "mediumaquamarine", { 102, 205, 170, 1 } }, { "mediumblue", { 0, 0, 205, 1 } }, |
| 82 | + { "mediumorchid", { 186, 85, 211, 1 } }, { "mediumpurple", { 147, 112, 219, 1 } }, |
| 83 | + { "mediumseagreen", { 60, 179, 113, 1 } }, { "mediumslateblue", { 123, 104, 238, 1 } }, |
| 84 | + { "mediumspringgreen", { 0, 250, 154, 1 } }, { "mediumturquoise", { 72, 209, 204, 1 } }, |
| 85 | + { "mediumvioletred", { 199, 21, 133, 1 } }, { "midnightblue", { 25, 25, 112, 1 } }, |
| 86 | + { "mintcream", { 245, 255, 250, 1 } }, { "mistyrose", { 255, 228, 225, 1 } }, |
| 87 | + { "moccasin", { 255, 228, 181, 1 } }, { "navajowhite", { 255, 222, 173, 1 } }, |
| 88 | + { "navy", { 0, 0, 128, 1 } }, { "oldlace", { 253, 245, 230, 1 } }, |
| 89 | + { "olive", { 128, 128, 0, 1 } }, { "olivedrab", { 107, 142, 35, 1 } }, |
| 90 | + { "orange", { 255, 165, 0, 1 } }, { "orangered", { 255, 69, 0, 1 } }, |
| 91 | + { "orchid", { 218, 112, 214, 1 } }, { "palegoldenrod", { 238, 232, 170, 1 } }, |
| 92 | + { "palegreen", { 152, 251, 152, 1 } }, { "paleturquoise", { 175, 238, 238, 1 } }, |
| 93 | + { "palevioletred", { 219, 112, 147, 1 } }, { "papayawhip", { 255, 239, 213, 1 } }, |
| 94 | + { "peachpuff", { 255, 218, 185, 1 } }, { "peru", { 205, 133, 63, 1 } }, |
| 95 | + { "pink", { 255, 192, 203, 1 } }, { "plum", { 221, 160, 221, 1 } }, |
| 96 | + { "powderblue", { 176, 224, 230, 1 } }, { "purple", { 128, 0, 128, 1 } }, |
| 97 | + { "red", { 255, 0, 0, 1 } }, { "rosybrown", { 188, 143, 143, 1 } }, |
| 98 | + { "royalblue", { 65, 105, 225, 1 } }, { "saddlebrown", { 139, 69, 19, 1 } }, |
| 99 | + { "salmon", { 250, 128, 114, 1 } }, { "sandybrown", { 244, 164, 96, 1 } }, |
| 100 | + { "seagreen", { 46, 139, 87, 1 } }, { "seashell", { 255, 245, 238, 1 } }, |
| 101 | + { "sienna", { 160, 82, 45, 1 } }, { "silver", { 192, 192, 192, 1 } }, |
| 102 | + { "skyblue", { 135, 206, 235, 1 } }, { "slateblue", { 106, 90, 205, 1 } }, |
| 103 | + { "slategray", { 112, 128, 144, 1 } }, { "slategrey", { 112, 128, 144, 1 } }, |
| 104 | + { "snow", { 255, 250, 250, 1 } }, { "springgreen", { 0, 255, 127, 1 } }, |
| 105 | + { "steelblue", { 70, 130, 180, 1 } }, { "tan", { 210, 180, 140, 1 } }, |
| 106 | + { "teal", { 0, 128, 128, 1 } }, { "thistle", { 216, 191, 216, 1 } }, |
| 107 | + { "tomato", { 255, 99, 71, 1 } }, { "turquoise", { 64, 224, 208, 1 } }, |
| 108 | + { "violet", { 238, 130, 238, 1 } }, { "wheat", { 245, 222, 179, 1 } }, |
| 109 | + { "white", { 255, 255, 255, 1 } }, { "whitesmoke", { 245, 245, 245, 1 } }, |
| 110 | + { "yellow", { 255, 255, 0, 1 } }, { "yellowgreen", { 154, 205, 50, 1 } } |
| 111 | +}; |
| 112 | + |
| 113 | + |
| 114 | +template <typename T> |
| 115 | +uint8_t clamp_css_byte(T i) { // Clamp to integer 0 .. 255. |
| 116 | + i = round(i); // Seems to be what Chrome does (vs truncation). |
| 117 | + return i < 0 ? 0 : i > 255 ? 255 : i; |
| 118 | +} |
| 119 | + |
| 120 | +template <typename T> |
| 121 | +float clamp_css_float(T f) { // Clamp to float 0.0 .. 1.0. |
| 122 | + return f < 0 ? 0 : f > 1 ? 1 : f; |
| 123 | +} |
| 124 | + |
| 125 | +float parseFloat(const std::string& str) { |
| 126 | + return strtof(str.c_str(), nullptr); |
| 127 | +} |
| 128 | + |
| 129 | +int64_t parseInt(const std::string& str, uint8_t base = 10) { |
| 130 | + return strtoll(str.c_str(), nullptr, base); |
| 131 | +} |
| 132 | + |
| 133 | +uint8_t parse_css_int(const std::string& str) { // int or percentage. |
| 134 | + if (str.length() && str.back() == '%') { |
| 135 | + return clamp_css_byte(parseFloat(str) / 100.0f * 255.0f); |
| 136 | + } else { |
| 137 | + return clamp_css_byte(parseInt(str)); |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +float parse_css_float(const std::string& str) { // float or percentage. |
| 142 | + if (str.length() && str.back() == '%') { |
| 143 | + return clamp_css_float(parseFloat(str) / 100.0f); |
| 144 | + } else { |
| 145 | + return clamp_css_float(parseFloat(str)); |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +float css_hue_to_rgb(float m1, float m2, float h) { |
| 150 | + if (h < 0.0f) { |
| 151 | + h += 1.0f; |
| 152 | + } else if (h > 1.0f) { |
| 153 | + h -= 1.0f; |
| 154 | + } |
| 155 | + |
| 156 | + if (h * 6.0f < 1.0f) { |
| 157 | + return m1 + (m2 - m1) * h * 6.0f; |
| 158 | + } |
| 159 | + if (h * 2.0f < 1.0f) { |
| 160 | + return m2; |
| 161 | + } |
| 162 | + if (h * 3.0f < 2.0f) { |
| 163 | + return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0f; |
| 164 | + } |
| 165 | + return m1; |
| 166 | +} |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | +std::vector<std::string> split(const std::string& s, char delim) { |
| 171 | + std::vector<std::string> elems; |
| 172 | + std::stringstream ss(s); |
| 173 | + std::string item; |
| 174 | + while (std::getline(ss, item, delim)) { |
| 175 | + elems.push_back(item); |
| 176 | + } |
| 177 | + return elems; |
| 178 | +} |
| 179 | + |
| 180 | +Color CSSColorParser::parse(const std::string& css_str) { |
| 181 | + std::string str = css_str; |
| 182 | + |
| 183 | + // Remove all whitespace, not compliant, but should just be more accepting. |
| 184 | + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); |
| 185 | + |
| 186 | + // Convert to lowercase. |
| 187 | + std::transform(str.begin(), str.end(), str.begin(), ::tolower); |
| 188 | + |
| 189 | + // Color keywords (and transparent) lookup. |
| 190 | + auto it = kCSSColorTable.find(str); |
| 191 | + if (it != kCSSColorTable.end()) { |
| 192 | + return it->second; |
| 193 | + } |
| 194 | + |
| 195 | + // #abc and #abc123 syntax. |
| 196 | + if (str.length() && str.front() == '#') { |
| 197 | + if (str.length() == 4) { |
| 198 | + int64_t iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. |
| 199 | + if (!(iv >= 0 && iv <= 0xfff)) { |
| 200 | + return {}; |
| 201 | + } else { |
| 202 | + return { |
| 203 | + static_cast<uint8_t>(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)), |
| 204 | + static_cast<uint8_t>((iv & 0xf0) | ((iv & 0xf0) >> 4)), |
| 205 | + static_cast<uint8_t>((iv & 0xf) | ((iv & 0xf) << 4)), |
| 206 | + 1 |
| 207 | + }; |
| 208 | + } |
| 209 | + } else if (str.length() == 7) { |
| 210 | + int64_t iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. |
| 211 | + if (!(iv >= 0 && iv <= 0xffffff)) { |
| 212 | + return {}; // Covers NaN. |
| 213 | + } else { |
| 214 | + return { |
| 215 | + static_cast<uint8_t>((iv & 0xff0000) >> 16), |
| 216 | + static_cast<uint8_t>((iv & 0xff00) >> 8), |
| 217 | + static_cast<uint8_t>(iv & 0xff), |
| 218 | + 1 |
| 219 | + }; |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + return {}; |
| 224 | + } |
| 225 | + |
| 226 | + size_t op = str.find_first_of('('), ep = str.find_first_of(')'); |
| 227 | + if (op != std::string::npos && ep + 1 == str.length()) { |
| 228 | + const std::string fname = str.substr(0, op); |
| 229 | + const std::vector<std::string> params = split(str.substr(op + 1, ep - (op + 1)), ','); |
| 230 | + |
| 231 | + float alpha = 1.0f; |
| 232 | + |
| 233 | + if (fname == "rgba" || fname == "rgb") { |
| 234 | + if (fname == "rgba") { |
| 235 | + if (params.size() != 4) { |
| 236 | + return {}; |
| 237 | + } |
| 238 | + alpha = parse_css_float(params.back()); |
| 239 | + } else { |
| 240 | + if (params.size() != 3) { |
| 241 | + return {}; |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + return { |
| 246 | + parse_css_int(params[0]), |
| 247 | + parse_css_int(params[1]), |
| 248 | + parse_css_int(params[2]), |
| 249 | + alpha |
| 250 | + }; |
| 251 | + |
| 252 | + } else if (fname == "hsla" || fname == "hsl") { |
| 253 | + if (fname == "hsla") { |
| 254 | + if (params.size() != 4) { |
| 255 | + return {}; |
| 256 | + } |
| 257 | + alpha = parse_css_float(params.back()); |
| 258 | + } else { |
| 259 | + if (params.size() != 3) { |
| 260 | + return {}; |
| 261 | + } |
| 262 | + } |
| 263 | + |
| 264 | + float h = parseFloat(params[0]) / 360.0f; |
| 265 | + while (h < 0.0f) h++; |
| 266 | + while (h > 1.0f) h--; |
| 267 | + |
| 268 | + // NOTE(deanm): According to the CSS spec s/l should only be |
| 269 | + // percentages, but we don't bother and let float or percentage. |
| 270 | + float s = parse_css_float(params[1]); |
| 271 | + float l = parse_css_float(params[2]); |
| 272 | + |
| 273 | + float m2 = l <= 0.5f ? l * (s + 1.0f) : l + s - l * s; |
| 274 | + float m1 = l * 2.0f - m2; |
| 275 | + |
| 276 | + return { |
| 277 | + clamp_css_byte(css_hue_to_rgb(m1, m2, h + 1.0f / 3.0f) * 255.0f), |
| 278 | + clamp_css_byte(css_hue_to_rgb(m1, m2, h) * 255.0f), |
| 279 | + clamp_css_byte(css_hue_to_rgb(m1, m2, h - 1.0f / 3.0f) * 255.0f), |
| 280 | + alpha |
| 281 | + }; |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + return {}; |
| 286 | +} |
0 commit comments