1"""
2Definitions of valid formats and values for colors.
3
4"""
5
6# SPDX-License-Identifier: BSD-3-Clause
7
8import re
9from typing import List
10
11
12def _reversedict(dict_to_reverse: dict) -> dict:
13 """
14 Internal helper for generating reverse mappings; given a dictionary, returns a
15 new dictionary with keys and values swapped.
16
17 """
18 return {value: key for key, value in dict_to_reverse.items()}
19
20
21_HEX_COLOR_RE = re.compile(r"^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$")
22
23HTML4 = "html4"
24CSS2 = "css2"
25CSS21 = "css21"
26CSS3 = "css3"
27
28_SUPPORTED_SPECIFICATIONS = (HTML4, CSS2, CSS21, CSS3)
29
30_SPECIFICATION_ERROR_TEMPLATE = (
31 f"{{spec}} is not a supported specification for color name lookups; "
32 f"supported specifications are: {_SUPPORTED_SPECIFICATIONS}."
33)
34
35# Mappings of color names to normalized hexadecimal color values.
36# --------------------------------------------------------------------------------
37
38# The HTML 4 named colors.
39#
40# The canonical source for these color definitions is the HTML 4 specification:
41#
42# http://www.w3.org/TR/html401/types.html#h-6.5
43#
44# The file tests/definitions.py in the source distribution of this module downloads a
45# copy of the HTML 4 standard and parses out the color names to ensure the values below
46# are correct.
47_HTML4_NAMES_TO_HEX = {
48 "aqua": "#00ffff",
49 "black": "#000000",
50 "blue": "#0000ff",
51 "fuchsia": "#ff00ff",
52 "green": "#008000",
53 "gray": "#808080",
54 "lime": "#00ff00",
55 "maroon": "#800000",
56 "navy": "#000080",
57 "olive": "#808000",
58 "purple": "#800080",
59 "red": "#ff0000",
60 "silver": "#c0c0c0",
61 "teal": "#008080",
62 "white": "#ffffff",
63 "yellow": "#ffff00",
64}
65
66# CSS2 used the same list as HTML 4.
67_CSS2_NAMES_TO_HEX = _HTML4_NAMES_TO_HEX
68
69# CSS2.1 added orange.
70_CSS21_NAMES_TO_HEX = {"orange": "#ffa500", **_HTML4_NAMES_TO_HEX}
71
72# The CSS3/SVG named colors.
73#
74# The canonical source for these color definitions is the SVG specification's color list
75# (which was adopted as CSS 3's color definition):
76#
77# http://www.w3.org/TR/SVG11/types.html#ColorKeywords
78#
79# CSS3 also provides definitions of these colors:
80#
81# http://www.w3.org/TR/css3-color/#svg-color
82#
83# SVG provides the definitions as RGB triplets. CSS3 provides them both as RGB triplets
84# and as hexadecimal. Since hex values are more common in real-world HTML and CSS, the
85# mapping below is to hex values instead. The file tests/definitions.py in the source
86# distribution of this module downloads a copy of the CSS3 color module and parses out
87# the color names to ensure the values below are correct.
88_CSS3_NAMES_TO_HEX = {
89 "aliceblue": "#f0f8ff",
90 "antiquewhite": "#faebd7",
91 "aqua": "#00ffff",
92 "aquamarine": "#7fffd4",
93 "azure": "#f0ffff",
94 "beige": "#f5f5dc",
95 "bisque": "#ffe4c4",
96 "black": "#000000",
97 "blanchedalmond": "#ffebcd",
98 "blue": "#0000ff",
99 "blueviolet": "#8a2be2",
100 "brown": "#a52a2a",
101 "burlywood": "#deb887",
102 "cadetblue": "#5f9ea0",
103 "chartreuse": "#7fff00",
104 "chocolate": "#d2691e",
105 "coral": "#ff7f50",
106 "cornflowerblue": "#6495ed",
107 "cornsilk": "#fff8dc",
108 "crimson": "#dc143c",
109 "cyan": "#00ffff",
110 "darkblue": "#00008b",
111 "darkcyan": "#008b8b",
112 "darkgoldenrod": "#b8860b",
113 "darkgray": "#a9a9a9",
114 "darkgrey": "#a9a9a9",
115 "darkgreen": "#006400",
116 "darkkhaki": "#bdb76b",
117 "darkmagenta": "#8b008b",
118 "darkolivegreen": "#556b2f",
119 "darkorange": "#ff8c00",
120 "darkorchid": "#9932cc",
121 "darkred": "#8b0000",
122 "darksalmon": "#e9967a",
123 "darkseagreen": "#8fbc8f",
124 "darkslateblue": "#483d8b",
125 "darkslategray": "#2f4f4f",
126 "darkslategrey": "#2f4f4f",
127 "darkturquoise": "#00ced1",
128 "darkviolet": "#9400d3",
129 "deeppink": "#ff1493",
130 "deepskyblue": "#00bfff",
131 "dimgray": "#696969",
132 "dimgrey": "#696969",
133 "dodgerblue": "#1e90ff",
134 "firebrick": "#b22222",
135 "floralwhite": "#fffaf0",
136 "forestgreen": "#228b22",
137 "fuchsia": "#ff00ff",
138 "gainsboro": "#dcdcdc",
139 "ghostwhite": "#f8f8ff",
140 "gold": "#ffd700",
141 "goldenrod": "#daa520",
142 "gray": "#808080",
143 "grey": "#808080",
144 "green": "#008000",
145 "greenyellow": "#adff2f",
146 "honeydew": "#f0fff0",
147 "hotpink": "#ff69b4",
148 "indianred": "#cd5c5c",
149 "indigo": "#4b0082",
150 "ivory": "#fffff0",
151 "khaki": "#f0e68c",
152 "lavender": "#e6e6fa",
153 "lavenderblush": "#fff0f5",
154 "lawngreen": "#7cfc00",
155 "lemonchiffon": "#fffacd",
156 "lightblue": "#add8e6",
157 "lightcoral": "#f08080",
158 "lightcyan": "#e0ffff",
159 "lightgoldenrodyellow": "#fafad2",
160 "lightgray": "#d3d3d3",
161 "lightgrey": "#d3d3d3",
162 "lightgreen": "#90ee90",
163 "lightpink": "#ffb6c1",
164 "lightsalmon": "#ffa07a",
165 "lightseagreen": "#20b2aa",
166 "lightskyblue": "#87cefa",
167 "lightslategray": "#778899",
168 "lightslategrey": "#778899",
169 "lightsteelblue": "#b0c4de",
170 "lightyellow": "#ffffe0",
171 "lime": "#00ff00",
172 "limegreen": "#32cd32",
173 "linen": "#faf0e6",
174 "magenta": "#ff00ff",
175 "maroon": "#800000",
176 "mediumaquamarine": "#66cdaa",
177 "mediumblue": "#0000cd",
178 "mediumorchid": "#ba55d3",
179 "mediumpurple": "#9370db",
180 "mediumseagreen": "#3cb371",
181 "mediumslateblue": "#7b68ee",
182 "mediumspringgreen": "#00fa9a",
183 "mediumturquoise": "#48d1cc",
184 "mediumvioletred": "#c71585",
185 "midnightblue": "#191970",
186 "mintcream": "#f5fffa",
187 "mistyrose": "#ffe4e1",
188 "moccasin": "#ffe4b5",
189 "navajowhite": "#ffdead",
190 "navy": "#000080",
191 "oldlace": "#fdf5e6",
192 "olive": "#808000",
193 "olivedrab": "#6b8e23",
194 "orange": "#ffa500",
195 "orangered": "#ff4500",
196 "orchid": "#da70d6",
197 "palegoldenrod": "#eee8aa",
198 "palegreen": "#98fb98",
199 "paleturquoise": "#afeeee",
200 "palevioletred": "#db7093",
201 "papayawhip": "#ffefd5",
202 "peachpuff": "#ffdab9",
203 "peru": "#cd853f",
204 "pink": "#ffc0cb",
205 "plum": "#dda0dd",
206 "powderblue": "#b0e0e6",
207 "purple": "#800080",
208 "red": "#ff0000",
209 "rosybrown": "#bc8f8f",
210 "royalblue": "#4169e1",
211 "saddlebrown": "#8b4513",
212 "salmon": "#fa8072",
213 "sandybrown": "#f4a460",
214 "seagreen": "#2e8b57",
215 "seashell": "#fff5ee",
216 "sienna": "#a0522d",
217 "silver": "#c0c0c0",
218 "skyblue": "#87ceeb",
219 "slateblue": "#6a5acd",
220 "slategray": "#708090",
221 "slategrey": "#708090",
222 "snow": "#fffafa",
223 "springgreen": "#00ff7f",
224 "steelblue": "#4682b4",
225 "tan": "#d2b48c",
226 "teal": "#008080",
227 "thistle": "#d8bfd8",
228 "tomato": "#ff6347",
229 "turquoise": "#40e0d0",
230 "violet": "#ee82ee",
231 "wheat": "#f5deb3",
232 "white": "#ffffff",
233 "whitesmoke": "#f5f5f5",
234 "yellow": "#ffff00",
235 "yellowgreen": "#9acd32",
236}
237
238
239# Mappings of normalized hexadecimal color values to color names.
240# --------------------------------------------------------------------------------
241
242_HTML4_HEX_TO_NAMES = _reversedict(_HTML4_NAMES_TO_HEX)
243
244_CSS2_HEX_TO_NAMES = _HTML4_HEX_TO_NAMES
245
246_CSS21_HEX_TO_NAMES = _reversedict(_CSS21_NAMES_TO_HEX)
247
248_CSS3_HEX_TO_NAMES = _reversedict(_CSS3_NAMES_TO_HEX)
249
250# CSS3 defines both "gray" and "grey", as well as defining either spelling variant for
251# other related colors like "darkgray"/"darkgrey", etc. For a "forward" lookup from
252# name to hex, this is straightforward, but a "reverse" lookup from hex to name requires
253# picking one spelling and being consistent about it.
254#
255# Since "gray" was the only spelling supported in HTML 4, CSS1, and CSS2, "gray" and its
256# variants are chosen here.
257_CSS3_HEX_TO_NAMES["#a9a9a9"] = "darkgray"
258_CSS3_HEX_TO_NAMES["#2f4f4f"] = "darkslategray"
259_CSS3_HEX_TO_NAMES["#696969"] = "dimgray"
260_CSS3_HEX_TO_NAMES["#808080"] = "gray"
261_CSS3_HEX_TO_NAMES["#d3d3d3"] = "lightgray"
262_CSS3_HEX_TO_NAMES["#778899"] = "lightslategray"
263_CSS3_HEX_TO_NAMES["#708090"] = "slategray"
264
265
266_names_to_hex = {
267 HTML4: _HTML4_NAMES_TO_HEX,
268 CSS2: _CSS2_NAMES_TO_HEX,
269 CSS21: _CSS21_NAMES_TO_HEX,
270 CSS3: _CSS3_NAMES_TO_HEX,
271}
272
273_hex_to_names = {
274 HTML4: _HTML4_HEX_TO_NAMES,
275 CSS2: _CSS2_HEX_TO_NAMES,
276 CSS21: _CSS21_HEX_TO_NAMES,
277 CSS3: _CSS3_HEX_TO_NAMES,
278}
279
280
281def _get_name_to_hex_map(spec: str):
282 """
283 Return the name-to-hex mapping for the given specification.
284
285 :raises ValueError: when the given spec is not supported.
286
287 """
288 if spec not in _SUPPORTED_SPECIFICATIONS:
289 raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec))
290 return _names_to_hex[spec]
291
292
293def _get_hex_to_name_map(spec: str):
294 """
295 Return the hex-to-name mapping for the given specification.
296
297 :raises ValueError: when the given spec is not supported.
298
299 """
300 if spec not in _SUPPORTED_SPECIFICATIONS:
301 raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec))
302 return _hex_to_names[spec]
303
304
305def names(spec: str = CSS3) -> List[str]:
306 """
307 Return the list of valid color names for the given specification.
308
309 The color names will be normalized to all-lowercase, and will be returned in
310 alphabetical order.
311
312 .. note:: **Spelling variants**
313
314 Some values representing named gray colors can map to either of two names in
315 CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for
316 those colors. Functions which produce a name from a color value in other formats
317 all normalize to the ``"gray"`` spelling for consistency with earlier CSS and
318 HTML specifications which only supported ``"gray"``. Here, however, *all* valid
319 names are returned, including -- for CSS3 -- both variant spellings for each of
320 the affected ``"gray"``/``"grey"`` colors.
321
322 Examples:
323
324 .. doctest::
325
326 >>> names(spec=HTML4)
327 ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
328 'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
329 'silver', 'teal', 'white', 'yellow']
330 >>> names(spec="CSS1")
331 Traceback (most recent call last):
332 ...
333 ValueError: "CSS1" is not a supported specification ...
334
335
336 :raises ValueError: when the given spec is not supported.
337
338 """
339 if spec not in _SUPPORTED_SPECIFICATIONS:
340 raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec))
341 mapping = _names_to_hex[spec]
342 return list(sorted(mapping.keys()))