use super::canvas::{LumaCanvas, Rect};
pub const ROWS: i32 = 7;
type Glyph = (u8, [u8; ROWS as usize]);
fn glyph(ch: char) -> Option<Glyph> {
Some(match ch {
'0' => (
5,
[
0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110,
],
),
'1' => (
5,
[
0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110,
],
),
'2' => (
5,
[
0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111,
],
),
'3' => (
5,
[
0b11111, 0b00010, 0b00100, 0b00010, 0b00001, 0b10001, 0b01110,
],
),
'4' => (
5,
[
0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010,
],
),
'5' => (
5,
[
0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110,
],
),
'6' => (
5,
[
0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110,
],
),
'7' => (
5,
[
0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000,
],
),
'8' => (
5,
[
0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110,
],
),
'9' => (
5,
[
0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100,
],
),
'×' => (5, [0, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0]),
'·' => (3, [0, 0, 0, 0b010, 0, 0, 0]),
'.' => (2, [0, 0, 0, 0, 0, 0b11, 0b11]),
':' => (2, [0, 0b11, 0b11, 0, 0b11, 0b11, 0]),
'/' => (
5,
[
0b00001, 0b00010, 0b00010, 0b00100, 0b01000, 0b01000, 0b10000,
],
),
'-' => (4, [0, 0, 0, 0, 0b1111, 0, 0]),
'A' => (
5,
[
0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001,
],
),
'H' => (
5,
[
0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001,
],
),
'a' => (4, [0, 0, 0b0110, 0b0001, 0b0111, 0b1001, 0b0111]),
'b' => (4, [0b1000, 0b1000, 0b1110, 0b1001, 0b1001, 0b1001, 0b1110]),
'c' => (4, [0, 0, 0b0111, 0b1000, 0b1000, 0b1000, 0b0111]),
'd' => (4, [0b0001, 0b0001, 0b0111, 0b1001, 0b1001, 0b1001, 0b0111]),
'e' => (4, [0, 0, 0b0110, 0b1001, 0b1111, 0b1000, 0b0111]),
'f' => (4, [0b0011, 0b0100, 0b1111, 0b0100, 0b0100, 0b0100, 0b0100]),
'g' => (4, [0, 0, 0b0111, 0b1001, 0b0111, 0b0001, 0b0110]),
'h' => (4, [0b1000, 0b1000, 0b1110, 0b1001, 0b1001, 0b1001, 0b1001]),
'i' => (3, [0b010, 0, 0b110, 0b010, 0b010, 0b010, 0b111]),
'j' => (3, [0b001, 0, 0b011, 0b001, 0b001, 0b101, 0b010]),
'k' => (4, [0b1000, 0b1000, 0b1001, 0b1010, 0b1100, 0b1010, 0b1001]),
'l' => (3, [0b110, 0b010, 0b010, 0b010, 0b010, 0b010, 0b111]),
'm' => (5, [0, 0, 0b11010, 0b10101, 0b10101, 0b10101, 0b10101]),
'n' => (4, [0, 0, 0b1110, 0b1001, 0b1001, 0b1001, 0b1001]),
'o' => (4, [0, 0, 0b0110, 0b1001, 0b1001, 0b1001, 0b0110]),
'p' => (4, [0, 0, 0b1110, 0b1001, 0b1110, 0b1000, 0b1000]),
'q' => (4, [0, 0, 0b0111, 0b1001, 0b0111, 0b0001, 0b0001]),
'r' => (4, [0, 0, 0b1011, 0b1100, 0b1000, 0b1000, 0b1000]),
's' => (4, [0, 0, 0b0111, 0b1000, 0b0110, 0b0001, 0b1110]),
't' => (4, [0b0100, 0b0100, 0b1111, 0b0100, 0b0100, 0b0100, 0b0011]),
'u' => (4, [0, 0, 0b1001, 0b1001, 0b1001, 0b1001, 0b0111]),
'v' => (5, [0, 0, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100]),
'w' => (5, [0, 0, 0b10001, 0b10001, 0b10101, 0b10101, 0b01010]),
'x' => (5, [0, 0, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001]),
'y' => (4, [0, 0, 0b1001, 0b1001, 0b0111, 0b0001, 0b0110]),
'z' => (4, [0, 0, 0b1111, 0b0010, 0b0100, 0b1000, 0b1111]),
' ' => (3, [0; ROWS as usize]),
_ => return None,
})
}
#[cfg(test)]
pub fn supports(text: &str) -> bool {
text.chars().all(|c| glyph(c).is_some())
}
pub fn text_width(text: &str, scale: i32) -> i32 {
let units: i32 = text
.chars()
.filter_map(glyph)
.map(|(w, _)| i32::from(w) + 1)
.sum();
(units - 1).max(0) * scale
}
pub fn draw_text(canvas: &mut LumaCanvas<'_>, x: i32, y: i32, scale: i32, text: &str, value: u8) {
let mut cx = x;
for (width, rows) in text.chars().filter_map(glyph) {
let width = i32::from(width);
for (row, bits) in (0..).zip(rows) {
for col in 0..width {
if bits >> (width - 1 - col) & 1 == 1 {
let px = Rect::new(cx + col * scale, y + row * scale, scale, scale);
canvas.fill_rect(px, value);
}
}
}
cx += (width + 1) * scale;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pattern::canvas::LumaImage;
#[test]
fn widths_add_up() {
assert_eq!(text_width("", 3), 0);
assert_eq!(text_width("1", 1), 5);
assert_eq!(text_width("1.5", 2), (5 + 1 + 2 + 1 + 5) * 2);
assert!(supports("1920×1080 · 29.97 fps · 1:00:00.5"));
assert!(supports("github.com/AlexHayton/av-drift-detector"));
assert!(supports("abcdefghijklmnopqrstuvwxyz"));
assert!(!supports("1920X1080"));
}
#[test]
fn glyphs_fit_their_widths() {
for ch in "0123456789AHabcdefghijklmnopqrstuvwxyz×·.:/- ".chars() {
let (width, rows) = glyph(ch).unwrap();
for bits in rows {
assert_eq!(bits >> width, 0, "{ch:?} has pixels beyond its width");
}
}
}
#[test]
fn draws_within_its_box() {
let mut img = LumaImage::new(80, 20, 0);
let text = "12×3 · 4.5 fps";
let (x, y, scale) = (2, 3, 1);
draw_text(&mut img.canvas(), x, y, scale, text, 200);
let w = text_width(text, scale);
for py in 0..20 {
for px in 0..80 {
let inside =
(x..x + w).contains(&(px as i32)) && (y..y + ROWS).contains(&(py as i32));
if !inside {
assert_eq!(img.view().get(px, py), 0, "pixel ({px}, {py})");
}
}
}
assert_eq!(img.view().get(4, 3), 200);
}
}