Something went wrong. Try again.
small gleam coding and (not yet) persistent agent daemon with a detachable cli forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262//! Dense 1-bit pixel-font text frames on a fixed grid, bundled X11 8x13.
use std::collections::HashMap;use std::sync::OnceLock;
const FONT: &[u8] = include_bytes!("../fonts/8x13.bdf");const MARGIN: usize = 0;
/// The 8x13 font box's 11px ascent; extra pitch leading sits below it.const BASELINE: usize = 11;const MAX_EDGE: usize = 8000;
pub struct Frame { pub width: u32, pub height: u32, pub png: Vec<u8>,}
struct Glyph { width: usize, height: usize, off_x: i32, off_y: i32, /// One packed bit row per glyph row; column c is bit (width-1-c). rows: Vec<u16>,}
struct Bdf { glyphs: HashMap<u32, Glyph>,}
fn bdf() -> &'static Bdf { static BDF: OnceLock<Bdf> = OnceLock::new(); BDF.get_or_init(|| parse_bdf(FONT).expect("bundled 8x13.bdf parses"))}
fn parse_bdf(bytes: &[u8]) -> Result<Bdf, String> { let text = std::str::from_utf8(bytes).map_err(|e| e.to_string())?; let mut glyphs = HashMap::new(); let mut encoding: Option<u32> = None; let mut box_: Option<(usize, usize, i32, i32)> = None; let mut bitmap: Option<Vec<u16>> = None; for line in text.lines() { let mut fields = line.split_whitespace(); match fields.next() { Some("ENCODING") => { encoding = fields .next() .and_then(|n| n.parse::<i64>().ok()) .and_then(|n| { if (0..=0x10FFFF).contains(&n) { Some(n as u32) } else { None } }); } Some("BBX") => { let mut number = || fields.next().and_then(|n| n.parse::<i64>().ok()); box_ = match (number(), number(), number(), number()) { (Some(w), Some(h), Some(x), Some(y)) if w > 0 && h > 0 => { Some((w as usize, h as usize, x as i32, y as i32)) } _ => None, }; } Some("BITMAP") => { let (w, h, x, y) = box_.ok_or("BITMAP without a BBX")?; bitmap = Some(Vec::with_capacity(h)); let _ = (w, x, y); } Some("ENDCHAR") => { if let (Some(code), Some((w, h, x, y)), Some(rows)) = (encoding, box_, bitmap.take()) { if rows.len() == h { glyphs.insert( code, Glyph { width: w, height: h, off_x: x, off_y: y, rows, }, ); } } encoding = None; box_ = None; } Some(hex) if bitmap.is_some() && !hex.is_empty() => { if let Some(row) = bitmap.as_mut() { row.push(parse_hex_row(hex)); } } _ => {} } } if glyphs.is_empty() { return Err("no glyphs in the bundled font".into()); } Ok(Bdf { glyphs })}
/// Hex bitmap bytes packed big-endian into one bit row (fonts here are/// <= 16 wide). Column c of a glyph of BBX width w is bit/// `(w + 7) / 8 * 8 - 1 - c` of the row.fn parse_hex_row(hex: &str) -> u16 { let mut row = 0u16; for i in 0..hex.len() / 2 { match u16::from_str_radix(&hex[2 * i..2 * i + 2], 16) { Ok(byte) => row = (row << 8) | byte, Err(_) => return row, } } row}
pub fn render(text: &str, advance: usize, pitch: usize, width: usize) -> Result<Frame, String> { let (width, height, pixels) = paint(text, advance, pitch, width)?; encode(width, height, pixels)}
fn paint( text: &str, advance: usize, pitch: usize, width: usize,) -> Result<(usize, usize, Vec<u8>), String> { if !(6..=24).contains(&advance) { return Err(format!("advance must be 6 to 24, not {advance}")); } if !(10..=64).contains(&pitch) { return Err(format!("pitch must be 10 to 64, not {pitch}")); } if !(256..=4096).contains(&width) || width < 2 * MARGIN + advance { return Err(format!("width must be 256 to 4096, not {width}")); } let columns = (width - 2 * MARGIN) / advance; let font = &bdf().glyphs; let question = font.get(&u32::from(b'?')).ok_or("the font lacks '?'")?;
let mut rows: Vec<Vec<char>> = Vec::new(); for line in text.split('\n') { let chars: Vec<char> = line.chars().collect(); if chars.is_empty() { rows.push(Vec::new()); continue; } for chunk in chars.chunks(columns) { rows.push(chunk.to_vec()); } } if rows.is_empty() { rows.push(Vec::new()); } let height = 2 * MARGIN + rows.len() * pitch; if height > MAX_EDGE { return Err(format!( "{height}px exceeds the {MAX_EDGE}px image edge; chunk the text into smaller frames" )); } let mut pixels = vec![255u8; width * height]; for (index, row) in rows.iter().enumerate() { let top = MARGIN + index * pitch; for (column, ch) in row.iter().enumerate() { let cell = MARGIN + column * advance; // U+2588 marks a newline: fill the whole cell box with ink. if *ch as u32 == 0x2588 { for py in top..top + pitch { for px in cell..cell + advance { if px < width && py < height { pixels[py * width + px] = 0; } } } continue; } let glyph = font.get(&(*ch as u32)).unwrap_or(question); let x = cell as isize + glyph.off_x as isize; let baseline = (top + BASELINE) as isize; let glyph_top = baseline - (glyph.height as isize + glyph.off_y as isize); let byte_bits = 8 * ((glyph.width + 7) / 8); for (r, bits) in glyph.rows.iter().enumerate() { for c in 0..glyph.width { if bits & (1 << (byte_bits - 1 - c)) != 0 { let px = x + c as isize; let py = glyph_top + r as isize; if px >= 0 && py >= 0 && (px as usize) < width && (py as usize) < height { pixels[py as usize * width + px as usize] = 0; } } } } } }
Ok((width, height, pixels))}
fn encode(width: usize, height: usize, pixels: Vec<u8>) -> Result<Frame, String> { // One bit per pixel, MSB first: ink is the 0 bit, white the 1 bit. let stride = (width + 7) / 8; let mut bits = vec![0xFFu8; stride * height]; for (y, row) in pixels.chunks(width).enumerate() { for (x, ink) in row.iter().enumerate() { if *ink < 128 { bits[y * stride + x / 8] &= !(0x80 >> (x % 8)); } } } let mut out = Vec::new(); let mut encoder = png::Encoder::new(&mut out, width as u32, height as u32); encoder.set_color(png::ColorType::Grayscale); encoder.set_depth(png::BitDepth::One); encoder.set_compression(png::Compression::High); let mut writer = encoder.write_header().expect("header fits in memory"); writer .write_image_data(&bits) .expect("pixels match the header"); drop(writer); Ok(Frame { width: width as u32, height: height as u32, png: out, })}
#[cfg(test)]mod tests { use super::*;
fn ink(text: &str) -> usize { let (_, _, pixels) = paint(text, 11, 16, 1568).unwrap(); pixels.iter().filter(|&&p| p < 128).count() }
#[test] fn glyphs_leave_ink() { assert!(ink("hello world") > 50, "text must leave ink"); assert_eq!(ink(""), 0); assert!(ink("A") > 5, "one glyph leaves ink"); }
#[test] fn full_block_fills_its_whole_cell() { let (width, height, pixels) = paint("\u{2588}", 11, 16, 1568).unwrap(); assert_eq!((width, height), (1568, 16)); let ink = pixels.iter().filter(|&&p| p < 128).count(); assert_eq!(ink, 11 * 16, "the block fills exactly one cell box"); }
#[test] fn every_ascii_glyph_leaves_ink() { let text: String = (0x21u32..=0x7e) .map(|c| char::from_u32(c).unwrap()) .collect(); assert!(ink(&text) > 1000, "the printable ASCII range is covered"); }}