1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
"""
Contrast Tests
~~~~~~~~~~
Pygments styles should be accessible to people with suboptimal vision.
This test ensures that the minimum contrast of styles does not degrade,
and that every rule of a new style fulfills the WCAG AA standard.[1]
[1]: https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
"""
import json
import os
import sys
import pygments.styles
import pygments.token
import wcag_contrast_ratio
JSON_FILENAME = os.path.join(os.path.dirname(__file__), "min_contrasts.json")
WCAG_AA_CONTRAST = 4.5
def hex2rgb(hexstr):
hexstr = hexstr.lstrip("#")
r = int(hexstr[:2], 16) / 255
g = int(hexstr[2:4], 16) / 255
b = int(hexstr[4:], 16) / 255
return (r, g, b)
def get_style_contrasts(style_cls):
return [
(
round(
wcag_contrast_ratio.rgb(
hex2rgb(style["bgcolor"] or style_cls.background_color),
hex2rgb(style["color"] or "#000000")
# we default to black because browsers also do
),
1,
),
ttype,
)
for ttype, style in style_cls.list_styles()
if ttype != pygments.token.Whitespace
]
def builtin_styles():
for style_name in pygments.styles.STYLE_MAP:
yield (style_name, pygments.styles.get_style_by_name(style_name))
def min_contrasts():
return {
name: min(x[0] for x in get_style_contrasts(style))
for name, style in builtin_styles()
}
def update_json():
with open(JSON_FILENAME, "w") as f:
json.dump(
min_contrasts(),
f,
indent=2,
)
def test_contrasts(fail_if_improved=True):
with open(JSON_FILENAME) as f:
previous_contrasts = json.load(f)
for style_name in pygments.styles.STYLE_MAP:
style = pygments.styles.get_style_by_name(style_name)
contrasts = get_style_contrasts(style)
min_contrast = min([x[0] for x in contrasts])
bar = previous_contrasts.get(style_name, WCAG_AA_CONTRAST)
assert not min_contrast < bar, (
"contrast degradation for style '{}'\n"
"The following rules have a contrast lower than the required {}:\n\n"
"{}\n"
).format(
style_name,
bar,
"\n".join(
[
"* {:.2f} {}".format(contrast, ttype)
for contrast, ttype in contrasts
if contrast < bar
]
),
)
if fail_if_improved:
assert (
not min_contrast > bar
), "congrats, you improved a contrast! please run ./scripts/update_contrasts.py"
|