///|
pub enum ContrastGrade {
Fail
AaLarge
Aa
Aaa
} derive(Debug, Eq)
///|
pub struct ContrastPair {
foreground : ColorToken
background : ColorToken
ratio_x100 : Int
grade : ContrastGrade
} derive(Debug, Eq)
///|
pub fn ContrastGrade::label(self : ContrastGrade) -> String {
match self {
Fail => "fail"
AaLarge => "AA large text"
Aa => "AA"
Aaa => "AAA"
}
}
///|
pub fn ColorToken::relative_luminance_x10000(self : ColorToken) -> Int {
match parse_hex_rgb(self.value) {
Some(rgb) => luminance_x10000(rgb)
None => 0
}
}
///|
pub fn ColorToken::contrast_against(
self : ColorToken,
background : ColorToken,
) -> ContrastPair {
let fg = self.relative_luminance_x10000()
let bg = background.relative_luminance_x10000()
let lighter = if fg >= bg { fg } else { bg }
let darker = if fg >= bg { bg } else { fg }
let ratio = (lighter + 500) * 100 / (darker + 500)
ContrastPair::{
foreground: self,
background,
ratio_x100: ratio,
grade: contrast_grade(ratio),
}
}
///|
pub fn ContrastPair::describe(self : ContrastPair) -> String {
let whole = self.ratio_x100 / 100
let fraction = self.ratio_x100 % 100
"\{self.foreground.name} on \{self.background.name}: \{whole}.\{fraction}:1, \{self.grade.label()}"
}
///|
fn contrast_grade(ratio_x100 : Int) -> ContrastGrade {
if ratio_x100 >= 700 {
Aaa
} else if ratio_x100 >= 450 {
Aa
} else if ratio_x100 >= 300 {
AaLarge
} else {
Fail
}
}
///|
struct Rgb {
r : Int
g : Int
b : Int
} derive(Debug, Eq)
///|
fn parse_hex_rgb(value : String) -> Rgb? {
if value.length() != 7 || value[0] != '#' {
None
} else {
match
(
hex_digit(value[1]),
hex_digit(value[2]),
hex_digit(value[3]),
hex_digit(value[4]),
hex_digit(value[5]),
hex_digit(value[6]),
) {
(Some(r1), Some(r2), Some(g1), Some(g2), Some(b1), Some(b2)) =>
Some(Rgb::{ r: r1 * 16 + r2, g: g1 * 16 + g2, b: b1 * 16 + b2 })
_ => None
}
}
}
///|
fn hex_digit(ch : UInt16) -> Int? {
if ch >= '0' && ch <= '9' {
Some(ch.to_int() - ('0' : UInt16).to_int())
} else if ch >= 'A' && ch <= 'F' {
Some(ch.to_int() - ('A' : UInt16).to_int() + 10)
} else if ch >= 'a' && ch <= 'f' {
Some(ch.to_int() - ('a' : UInt16).to_int() + 10)
} else {
None
}
}
///|
fn luminance_x10000(rgb : Rgb) -> Int {
(
linear_channel_x10000(rgb.r) * 2126 +
linear_channel_x10000(rgb.g) * 7152 +
linear_channel_x10000(rgb.b) * 722
) /
10000
}
///|
fn linear_channel_x10000(channel : Int) -> Int {
let scaled = channel * 10000 / 255
if scaled <= 392 {
scaled * 10000 / 1292
} else {
let adjusted = (scaled + 550) * 10000 / 10550
adjusted * adjusted / 10000
}
}