Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small refactor of color utils for readibility and type checkability #55

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions a11y_pygments/utils/wcag_contrast.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,56 @@
from typing import NewType, Tuple, TypeAlias, Union


# float01 is a float greater than or equal to 0 and less than or equal to 1
float01 = NewType("float01", float)
RGBColor: TypeAlias = Tuple[float01, float01, float01]


def xx_01(xx: str) -> float01:
"""Convert an 8-bit hex value to a float between 0 and 1.

>>> xx_01("00")
0.0
>>> xx_01("33")
0.2
>>> xx_01("55")
0.3333333333333333
>>> xx_01("cc")
0.8
>>> xx_01("ff")
1.0
"""
return float01(int(xx, 16) / 255)


def hex_to_rgb01(hex: str) -> RGBColor:
"""Convert a hex defined colour to RGB.

Args:
hex (string): color in hex format (#rrggbb/#rgb)

Returns:
rgb: tuple of rgb values ``(r, g, b)``, where each channel (red, green, blue,
alpha) can assume values between 0 and 1.
rgb: tuple of rgb values ``(r, g, b)``, where each channel (red, green,
blue) can assume values between 0 and 1 (inclusive).
"""
# hex color in #rrggbb format
match = re.match(r"\A#[a-fA-F0-9]{6}\Z", hex)
if match:
res = tuple(float01(int(n, 16) / 255) for n in [hex[1:3], hex[3:5], hex[5:7]])
assert len(res) == 3, "please type checker with length of tuple"
return res
# hex color in #rgb format, shorthand for #rrggbb.
match = re.match(r"\A#[a-fA-F0-9]{3}\Z", hex)
if match:
res = tuple(
float01(int(n, 16) / 255) for n in [hex[1] * 2, hex[2] * 2, hex[3] * 2]

if re.match(r"\A#[a-fA-F0-9]{6}\Z", hex):
# Full hex color (#rrggbb) format
return (
xx_01(hex[1:3]),
xx_01(hex[3:5]),
xx_01(hex[5:7]),
)
assert len(res) == 3, "please type checker with length of tuple"
return res
assert False, "please type checker with unreachable end"

if re.match(r"\A#[a-fA-F0-9]{3}\Z", hex):
# Short hex color (#rgb) format, shorthand for #rrggbb
return (
xx_01(hex[1] * 2),
xx_01(hex[2] * 2),
xx_01(hex[3] * 2),
)

raise ValueError("Invalid hex color format")


def sRGB_channel(v: float01) -> float:
Expand Down Expand Up @@ -92,7 +113,7 @@ def contrast_ratio(color1: RGBColor, color2: RGBColor) -> float:

def passes_contrast(
color1: RGBColor, color2: RGBColor, level="AA"
) -> Union[bool, float, None]:
) -> Union[bool, float]:
"""Method to verify the contrast ratio between two colours.

Args:
Expand All @@ -101,9 +122,6 @@ def passes_contrast(
level (str, optional): WCAG contrast level. Defaults to "AA".
"""

if level not in ["AA", "AAA"]:
raise ValueError("level must be either 'AA' or 'AAA'")

ratio = contrast_ratio(color1, color2)

if level == "AA":
Expand All @@ -116,7 +134,8 @@ def passes_contrast(
return round(ratio, 2)
else:
return False
return None

raise ValueError("level must be either 'AA' or 'AAA'")


def get_wcag_level_normal_text(contrast: float) -> str:
Expand Down
Loading