diff --git a/rust/pm-grid/src/fonts.rs b/rust/pm-grid/src/fonts.rs new file mode 100644 index 0000000..8e44dfa --- /dev/null +++ b/rust/pm-grid/src/fonts.rs @@ -0,0 +1,78 @@ +//! 3x5 LED font (bit2 = leftmost column) — shared by the views, the scrolling name, and the splash. +//! Same bit convention as pico-scroll/app.py: glyph row value's bit `(1 << (2 - col))` lights a column. +use alloc::vec::Vec; + +pub const DIGITS: [[u8; 5]; 10] = [ + [7, 5, 5, 5, 7], // 0 + [2, 6, 2, 2, 7], // 1 + [7, 1, 7, 4, 7], // 2 + [7, 1, 7, 1, 7], // 3 + [5, 5, 7, 1, 1], // 4 + [7, 4, 7, 1, 7], // 5 + [7, 4, 7, 5, 7], // 6 + [7, 1, 2, 2, 2], // 7 + [7, 5, 7, 5, 7], // 8 + [7, 5, 7, 1, 7], // 9 +]; + +/// Full 3x5 uppercase glyph for a character (used by the scrolling name + boot splash). +/// Unknown characters render blank. Digits reuse `DIGITS`. +fn glyph(c: char) -> [u8; 5] { + match c { + '0'..='9' => DIGITS[c as usize - '0' as usize], + 'A' => [2, 5, 7, 5, 5], + 'B' => [6, 5, 6, 5, 6], + 'C' => [3, 4, 4, 4, 3], + 'D' => [6, 5, 5, 5, 6], + 'E' => [7, 4, 6, 4, 7], + 'F' => [7, 4, 6, 4, 4], + 'G' => [7, 4, 5, 5, 7], + 'H' => [5, 5, 7, 5, 5], + 'I' => [7, 2, 2, 2, 7], + 'J' => [1, 1, 1, 5, 2], + 'K' => [5, 6, 4, 6, 5], + 'L' => [4, 4, 4, 4, 7], + 'M' => [5, 7, 7, 5, 5], + 'N' => [5, 7, 7, 7, 5], + 'O' => [2, 5, 5, 5, 2], + 'P' => [7, 5, 7, 4, 4], + 'Q' => [2, 5, 5, 6, 3], + 'R' => [7, 5, 7, 6, 5], + 'S' => [3, 4, 2, 1, 6], + 'T' => [7, 2, 2, 2, 2], + 'U' => [5, 5, 5, 5, 7], + 'V' => [5, 5, 5, 5, 2], + 'W' => [5, 5, 7, 7, 5], + 'X' => [5, 5, 2, 5, 5], + 'Y' => [5, 5, 2, 2, 2], + 'Z' => [7, 1, 2, 4, 7], + '-' => [0, 0, 7, 0, 0], + '/' => [1, 1, 2, 4, 4], + '(' => [1, 2, 2, 2, 1], + ')' => [4, 2, 2, 2, 4], + '.' => [0, 0, 0, 0, 2], + '+' => [0, 2, 7, 2, 0], + '&' => [2, 5, 2, 5, 3], + _ => [0, 0, 0, 0, 0], // space + anything unmapped + } +} + +/// Render a string into a list of 5-bit column slices (3 columns per glyph + a 1px gap), for the +/// horizontal scrollers (Ticker name + boot splash). Lowercase is folded to uppercase. +pub fn build_name_cols(name: &str) -> Vec { + let mut cols = Vec::new(); + for ch in name.chars() { + let g = glyph(ch.to_ascii_uppercase()); + for c in 0..3 { + let mut col = 0u8; + for r in 0..5 { + if g[r] & (1 << (2 - c)) != 0 { + col |= 1 << r; + } + } + cols.push(col); + } + cols.push(0); // 1px gap between glyphs + } + cols +} diff --git a/rust/pm-grid/src/main.rs b/rust/pm-grid/src/main.rs index 151daf0..00cd947 100644 --- a/rust/pm-grid/src/main.rs +++ b/rust/pm-grid/src/main.rs @@ -19,6 +19,8 @@ #![no_main] extern crate alloc; +mod fonts; +use fonts::{build_name_cols, DIGITS}; use alloc::collections::VecDeque; use alloc::string::{String, ToString}; use alloc::vec::Vec; @@ -311,63 +313,6 @@ const APP_VERSION: &str = "0.1.0"; const BRIGHTNESS: u8 = 160; // accent const NAME_BRIGHT: u8 = 120; // ticker name pixels -// ============================== FONTS (3x5; bit2 = leftmost column) ============================== -// Same bit convention as pico-scroll/app.py: glyph row value's bit (1<<(2-col)) lights that column. -const DIGITS: [[u8; 5]; 10] = [ - [7, 5, 5, 5, 7], // 0 - [2, 6, 2, 2, 7], // 1 - [7, 1, 7, 4, 7], // 2 - [7, 1, 7, 1, 7], // 3 - [5, 5, 7, 1, 1], // 4 - [7, 4, 7, 1, 7], // 5 - [7, 4, 7, 5, 7], // 6 - [7, 1, 2, 2, 2], // 7 - [7, 5, 7, 5, 7], // 8 - [7, 5, 7, 1, 7], // 9 -]; - -/// Full 3x5 uppercase glyph for a character (used by the scrolling name + boot splash). -/// Unknown characters render blank. Digits reuse `DIGITS`. -fn glyph(c: char) -> [u8; 5] { - match c { - '0'..='9' => DIGITS[c as usize - '0' as usize], - 'A' => [2, 5, 7, 5, 5], - 'B' => [6, 5, 6, 5, 6], - 'C' => [3, 4, 4, 4, 3], - 'D' => [6, 5, 5, 5, 6], - 'E' => [7, 4, 6, 4, 7], - 'F' => [7, 4, 6, 4, 4], - 'G' => [7, 4, 5, 5, 7], - 'H' => [5, 5, 7, 5, 5], - 'I' => [7, 2, 2, 2, 7], - 'J' => [1, 1, 1, 5, 2], - 'K' => [5, 6, 4, 6, 5], - 'L' => [4, 4, 4, 4, 7], - 'M' => [5, 7, 7, 5, 5], - 'N' => [5, 7, 7, 7, 5], - 'O' => [2, 5, 5, 5, 2], - 'P' => [7, 5, 7, 4, 4], - 'Q' => [2, 5, 5, 6, 3], - 'R' => [7, 5, 7, 6, 5], - 'S' => [3, 4, 2, 1, 6], - 'T' => [7, 2, 2, 2, 2], - 'U' => [5, 5, 5, 5, 7], - 'V' => [5, 5, 5, 5, 2], - 'W' => [5, 5, 7, 7, 5], - 'X' => [5, 5, 2, 5, 5], - 'Y' => [5, 5, 2, 2, 2], - 'Z' => [7, 1, 2, 4, 7], - '-' => [0, 0, 7, 0, 0], - '/' => [1, 1, 2, 4, 4], - '(' => [1, 2, 2, 2, 1], - ')' => [4, 2, 2, 2, 4], - '.' => [0, 0, 0, 0, 2], - '+' => [0, 2, 7, 2, 0], - '&' => [2, 5, 2, 5, 3], - _ => [0, 0, 0, 0, 0], // space + anything unmapped - } -} - // ============================== IS31FL3731 DRIVER (bulk framebuffer) ============================== // Faithful port of pico-scroll/app.py's `Matrix`: keep a 144-byte PWM framebuffer and push the // WHOLE thing in one I2C block write per frame (per-pixel I2C is far too slow to animate). The @@ -559,24 +504,6 @@ fn master_bar_ns(track: &track_format::Track, tempo: i64) -> i64 { beat * beats.max(1) } -fn build_name_cols(name: &str) -> Vec { - let mut cols = Vec::new(); - for ch in name.chars() { - let g = glyph(ch.to_ascii_uppercase()); - for c in 0..3 { - let mut col = 0u8; - for r in 0..5 { - if g[r] & (1 << (2 - c)) != 0 { - col |= 1 << r; - } - } - cols.push(col); - } - cols.push(0); // 1px gap between glyphs - } - cols -} - const SCROLL_GAP: i32 = 13; // blank columns between repeats (>= name region width) for a clean loop impl App {