summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--requirements.txt1
-rwxr-xr-xscripts/update_contrasts.py21
-rw-r--r--tests/contrast/min_contrasts.json43
-rw-r--r--tests/contrast/test_contrasts.py96
-rw-r--r--tox.ini1
6 files changed, 163 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
index 9fc03933..849c7245 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -246,5 +246,6 @@ Other contributors, listed alphabetically, are:
* Fabian Neumann -- CDDL lexer
* Thomas Duboucher -- CDDL lexer
* Philipp Imhof -- Pango Markup formatter
+* Martin Fischer -- WCAG contrast testing
Many thanks for all contributions!
diff --git a/requirements.txt b/requirements.txt
index 49a627f2..7240ed5b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,4 @@ pytest>=6.0
pyflakes
pylint
tox
+wcag-contrast-ratio
diff --git a/scripts/update_contrasts.py b/scripts/update_contrasts.py
new file mode 100755
index 00000000..156bc5cb
--- /dev/null
+++ b/scripts/update_contrasts.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+"""
+ Updates tests/contrast/min_contrasts.json
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Whenever you have improved the minimum contrast of a style you should run
+ this script, so that the test_contrasts.py test prevents future degredations.
+"""
+
+import os
+import sys
+
+# always prefer Pygments from source if exists
+srcpath = os.path.join(os.path.dirname(__file__), "..")
+if os.path.isdir(os.path.join(srcpath, "pygments")):
+ sys.path.insert(0, srcpath)
+
+import tests.contrast.test_contrasts
+
+tests.contrast.test_contrasts.test_contrasts(fail_if_improved=False)
+tests.contrast.test_contrasts.update_json()
diff --git a/tests/contrast/min_contrasts.json b/tests/contrast/min_contrasts.json
new file mode 100644
index 00000000..e215a26c
--- /dev/null
+++ b/tests/contrast/min_contrasts.json
@@ -0,0 +1,43 @@
+{
+ "default": 1.8,
+ "emacs": 1.8,
+ "friendly": 1.7,
+ "colorful": 1.9,
+ "autumn": 1.9,
+ "murphy": 1.4,
+ "manni": 1.4,
+ "material": 1.6,
+ "monokai": 1.4,
+ "perldoc": 1.6,
+ "pastie": 1.9,
+ "borland": 1.9,
+ "trac": 1.9,
+ "native": 2.1,
+ "fruity": 1.9,
+ "bw": 21.0,
+ "vim": 1.3,
+ "vs": 3.6,
+ "tango": 1.0,
+ "rrt": 1.0,
+ "xcode": 5.1,
+ "igor": 3.6,
+ "paraiso-light": 1.3,
+ "paraiso-dark": 1.3,
+ "lovelace": 3.1,
+ "algol": 3.5,
+ "algol_nu": 3.5,
+ "arduino": 2.6,
+ "rainbow_dash": 1.0,
+ "abap": 2.5,
+ "solarized-dark": 2.8,
+ "solarized-light": 2.5,
+ "sas": 1.9,
+ "stata": 1.9,
+ "stata-light": 1.9,
+ "stata-dark": 1.4,
+ "inkpot": 1.0,
+ "zenburn": 1.6,
+ "gruvbox-dark": 1.0,
+ "gruvbox-light": 1.0,
+ "dracula": 1.4
+}
diff --git a/tests/contrast/test_contrasts.py b/tests/contrast/test_contrasts.py
new file mode 100644
index 00000000..d503a0c4
--- /dev/null
+++ b/tests/contrast/test_contrasts.py
@@ -0,0 +1,96 @@
+"""
+ 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 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):
+ bgcolor = hex2rgb(style.background_color)
+ return [
+ (
+ round(
+ wcag_contrast_ratio.rgb(
+ bgcolor,
+ hex2rgb(style["color"] or "#000000")
+ # we default to black because browsers also do
+ ),
+ 1,
+ ),
+ ttype,
+ )
+ for ttype, style in style.list_styles()
+ ]
+
+
+def builtin_styles():
+ for style_name in pygments.styles.STYLE_MAP:
+ yield (style_name, pygments.styles.get_style_by_name(style_name))
+
+
+def update_json():
+ with open(JSON_FILENAME, "w") as f:
+ json.dump(
+ {
+ name: min(x[0] for x in get_style_contrasts(style))
+ for name, style in builtin_styles()
+ },
+ 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"
diff --git a/tox.ini b/tox.ini
index aa26979c..dea9ee41 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,6 +5,7 @@ envlist = py35, py36, py37, py38, py39, py310, pypy3, lint
deps =
pytest
pytest-cov
+ wcag-contrast-ratio
commands = pytest {posargs}