1use crate::color::Color;
10use crate::contrast::{contrast_ratio, AA_TEXT, TEXT_ON_DARK, TEXT_ON_LIGHT};
11
12pub fn readable_text(surface: &Color) -> Color {
16 let light = Color::from_hex(TEXT_ON_LIGHT).expect("constant");
17 let dark = Color::from_hex(TEXT_ON_DARK).expect("constant");
18 if contrast_ratio(surface, &light) >= contrast_ratio(surface, &dark) {
19 light
20 } else {
21 dark
22 }
23}
24
25pub fn resolve_text_token(surface: &Color, requested: &Color) -> Color {
28 let ratio = contrast_ratio(surface, requested);
29 if ratio >= AA_TEXT {
30 return *requested;
31 }
32 log::warn!(
33 "rio-theme: text {} fails AA on surface {} (ratio {:.2} < {:.1}); substituting readable fallback",
34 requested.to_hex(),
35 surface.to_hex(),
36 ratio,
37 AA_TEXT,
38 );
39 readable_text(surface)
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 fn c(hex: &str) -> Color {
47 Color::from_hex(hex).unwrap()
48 }
49
50 #[test]
51 fn near_black_surface_with_dark_gray_text_falls_back_to_light() {
52 let surface = c("#0a0a0a");
53 let bad_text = c("#222222");
54 let resolved = resolve_text_token(&surface, &bad_text);
55 assert_ne!(resolved.to_hex(), bad_text.to_hex());
57 assert!(contrast_ratio(&surface, &resolved) >= AA_TEXT);
59 }
60
61 #[test]
62 fn white_surface_with_dark_text_passes_through() {
63 let surface = c("#ffffff");
64 let text = c("#1a1a1a");
65 assert_eq!(resolve_text_token(&surface, &text).to_hex(), text.to_hex());
66 }
67
68 #[test]
69 fn readable_text_picks_higher_ratio() {
70 let surface = c("#ffffff");
71 let t = readable_text(&surface);
72 assert_eq!(t.to_hex(), "#1a1a1a");
74 }
75}