1 // (c) Dean McNamee <dean@gmail.com>, 2012.
2 // C++ port by Mapbox, Konstantin Käfer <mail@kkaefer.com>, 2014-2017.
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 <algorithm>
31 
32 namespace CSSColorParser {
33 
34 // http://www.w3.org/TR/css3-color/
35 struct NamedColor { const char *const name; const Color color; };
36 const NamedColor namedColors[] = {
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 template <typename T>
clamp_css_byte(T i)114 uint8_t clamp_css_byte(T i) {  // Clamp to integer 0 .. 255.
115     i = ::round(i);  // Seems to be what Chrome does (vs truncation).
116     return i < 0 ? 0 : i > 255 ? 255 : uint8_t(i);
117 }
118 
119 template <typename T>
clamp_css_float(T f)120 float clamp_css_float(T f) {  // Clamp to float 0.0 .. 1.0.
121     return f < 0 ? 0 : f > 1 ? 1 : float(f);
122 }
123 
parseFloat(const std::string & str)124 float parseFloat(const std::string& str) {
125     return strtof(str.c_str(), nullptr);
126 }
127 
parseInt(const std::string & str,uint8_t base=10)128 int64_t parseInt(const std::string& str, uint8_t base = 10) {
129     return strtoll(str.c_str(), nullptr, base);
130 }
131 
parse_css_int(const std::string & str)132 uint8_t parse_css_int(const std::string& str) {  // int or percentage.
133     if (str.length() && str.back() == '%') {
134         return clamp_css_byte(parseFloat(str) / 100.0f * 255.0f);
135     } else {
136         return clamp_css_byte(parseInt(str));
137     }
138 }
139 
parse_css_float(const std::string & str)140 float parse_css_float(const std::string& str) {  // float or percentage.
141     if (str.length() && str.back() == '%') {
142         return clamp_css_float(parseFloat(str) / 100.0f);
143     } else {
144         return clamp_css_float(parseFloat(str));
145     }
146 }
147 
css_hue_to_rgb(float m1,float m2,float h)148 float css_hue_to_rgb(float m1, float m2, float h) {
149     if (h < 0.0f) {
150         h += 1.0f;
151     } else if (h > 1.0f) {
152         h -= 1.0f;
153     }
154 
155     if (h * 6.0f < 1.0f) {
156         return m1 + (m2 - m1) * h * 6.0f;
157     }
158     if (h * 2.0f < 1.0f) {
159         return m2;
160     }
161     if (h * 3.0f < 2.0f) {
162         return m1 + (m2 - m1) * (2.0f / 3.0f - h) * 6.0f;
163     }
164     return m1;
165 }
166 
167 
168 
split(const std::string & s,char delim)169 std::vector<std::string> split(const std::string& s, char delim) {
170     std::vector<std::string> elems;
171     std::stringstream ss(s);
172     std::string item;
173     while (std::getline(ss, item, delim)) {
174         elems.push_back(item);
175     }
176     return elems;
177 }
178 
parse(const std::string & css_str)179 optional<Color> parse(const std::string& css_str) {
180     std::string str = css_str;
181 
182     // Remove all whitespace, not compliant, but should just be more accepting.
183     str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
184 
185     // Convert to lowercase.
186     std::transform(str.begin(), str.end(), str.begin(), ::tolower);
187 
188     for (const auto& namedColor : namedColors) {
189         if (str == namedColor.name) {
190             return { namedColor.color };
191         }
192     }
193 
194     // #abc and #abc123 syntax.
195     if (str.length() && str.front() == '#') {
196         if (str.length() == 4) {
197             int64_t iv = parseInt(str.substr(1), 16);  // TODO(deanm): Stricter parsing.
198             if (!(iv >= 0 && iv <= 0xfff)) {
199                 return {};
200             } else {
201                 return {{
202                     static_cast<uint8_t>(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)),
203                     static_cast<uint8_t>((iv & 0xf0) | ((iv & 0xf0) >> 4)),
204                     static_cast<uint8_t>((iv & 0xf) | ((iv & 0xf) << 4)),
205                     1
206                 }};
207             }
208         } else if (str.length() == 7) {
209             int64_t iv = parseInt(str.substr(1), 16);  // TODO(deanm): Stricter parsing.
210             if (!(iv >= 0 && iv <= 0xffffff)) {
211                 return {};  // Covers NaN.
212             } else {
213                 return {{
214                     static_cast<uint8_t>((iv & 0xff0000) >> 16),
215                     static_cast<uint8_t>((iv & 0xff00) >> 8),
216                     static_cast<uint8_t>(iv & 0xff),
217                     1
218                 }};
219             }
220         }
221 
222         return {};
223     }
224 
225     size_t op = str.find_first_of('('), ep = str.find_first_of(')');
226     if (op != std::string::npos && ep + 1 == str.length()) {
227         const std::string fname = str.substr(0, op);
228         const std::vector<std::string> params = split(str.substr(op + 1, ep - (op + 1)), ',');
229 
230         float alpha = 1.0f;
231 
232         if (fname == "rgba" || fname == "rgb") {
233             if (fname == "rgba") {
234                 if (params.size() != 4) {
235                     return {};
236                 }
237                 alpha = parse_css_float(params.back());
238             } else {
239                 if (params.size() != 3) {
240                     return {};
241                 }
242             }
243 
244             return {{
245                 parse_css_int(params[0]),
246                 parse_css_int(params[1]),
247                 parse_css_int(params[2]),
248                 alpha
249             }};
250 
251         } else if (fname == "hsla" || fname == "hsl") {
252             if (fname == "hsla") {
253                 if (params.size() != 4) {
254                     return {};
255                 }
256                 alpha = parse_css_float(params.back());
257             } else {
258                 if (params.size() != 3) {
259                     return {};
260                 }
261             }
262 
263             float h = parseFloat(params[0]) / 360.0f;
264             float i;
265             // Normalize the hue to [0..1[
266             h = std::modf(h, &i);
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 }
287 
288 } // namespace CSSColorParser
289