///|
pub(all) enum ContrastRating {
Fail
LargeText
Pass
} derive(Debug, Eq)
///|
pub(all) struct ContrastReport {
foreground : String
background : String
ratio : Int
rating : ContrastRating
} derive(Debug, Eq)
///|
fn hex_digit(value : Char) -> Int {
match value {
'0'..='9' => value.to_int() - '0'.to_int()
'a'..='f' => value.to_int() - 'a'.to_int() + 10
'A'..='F' => value.to_int() - 'A'.to_int() + 10
_ => 0
}
}
///|
fn channel(css : String, index : Int) -> Int {
if css.char_length() < index + 2 {
0
} else {
match (css.get_char(index), css.get_char(index + 1)) {
(Some(first), Some(second)) => hex_digit(first) * 16 + hex_digit(second)
_ => 0
}
}
}
///|
fn luminance(color : Color) -> Int {
let offset = if color.css.has_prefix("#") { 1 } else { 0 }
let red = channel(color.css, offset)
let green = channel(color.css, offset + 2)
let blue = channel(color.css, offset + 4)
(red * 299 + green * 587 + blue * 114) / 1000
}
///|
pub fn contrast_ratio(foreground : Color, background : Color) -> Int {
let first = luminance(foreground) + 5
let second = luminance(background) + 5
if first >= second {
first * 100 / second
} else {
second * 100 / first
}
}
///|
pub fn contrast_report(
foreground : Color,
background : Color,
) -> ContrastReport {
let ratio = contrast_ratio(foreground, background)
{
foreground: foreground.css,
background: background.css,
ratio,
rating: if ratio >= 700 {
Pass
} else if ratio >= 450 {
LargeText
} else {
Fail
},
}
}
///|
pub fn Theme::body_contrast(self : Theme) -> ContrastReport {
contrast_report(self.foreground, self.background)
}
///|
pub fn Theme::accent_contrast(self : Theme) -> ContrastReport {
contrast_report(self.accent, self.background)
}
///|
pub fn contrast_rating_text(rating : ContrastRating) -> String {
match rating {
Fail => "fail"
LargeText => "large-text"
Pass => "pass"
}
}