ocr 0.1.2

A pure Rust CLI OCR tool for printed text extraction
Documentation
//! Bitmap font renderer for synthetic text-line generation
//!
//! Provides a pure-Rust fallback when no TrueType fonts are available.
//! Uses embedded 5×7 bitmap glyphs for ASCII printable characters.

use image::{DynamicImage, GrayImage, Luma};

/// Render a text string as a bitmap image using embedded 5×7 glyphs
pub fn render_text_bitmap(
    text: &str,
    image_height: u32,
    background_color: Luma<u8>,
    text_color: Luma<u8>,
    padding: u32,
) -> DynamicImage {
    let glyph_width = 5u32;
    let glyph_height = 7u32;
    let scale =
        (image_height.saturating_sub(padding * 2) as f32 / glyph_height as f32).max(1.0) as u32;

    let scaled_glyph_w = glyph_width * scale;
    let scaled_glyph_h = glyph_height * scale;
    let text_width = text.len() as u32 * scaled_glyph_w;
    let img_width = text_width + padding * 2;
    let img_height = image_height;

    let mut image = GrayImage::from_pixel(img_width, img_height, background_color);

    let baseline_y = (img_height.saturating_sub(scaled_glyph_h)) / 2;

    for (char_idx, ch) in text.chars().enumerate() {
        let x_offset = padding + char_idx as u32 * scaled_glyph_w;
        if let Some(rows) = glyph_5x7_rows(ch) {
            for (row_idx, row) in rows.iter().enumerate() {
                for (col_idx, pixel) in row.chars().enumerate() {
                    if pixel == '1' {
                        for dy in 0..scale {
                            for dx in 0..scale {
                                let px = x_offset + col_idx as u32 * scale + dx;
                                let py = baseline_y + row_idx as u32 * scale + dy;
                                if px < img_width && py < img_height {
                                    image.put_pixel(px, py, text_color);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    DynamicImage::ImageLuma8(image)
}

/// Get the 5×7 bitmap rows for a character
pub fn glyph_5x7_rows(ch: char) -> Option<[&'static str; 7]> {
    match ch {
        'A' => Some([
            "01110", "10001", "10001", "11111", "10001", "10001", "10001",
        ]),
        'B' => Some([
            "11110", "10001", "10001", "11110", "10001", "10001", "11110",
        ]),
        'C' => Some([
            "01111", "10000", "10000", "10000", "10000", "10000", "01111",
        ]),
        'D' => Some([
            "11110", "10001", "10001", "10001", "10001", "10001", "11110",
        ]),
        'E' => Some([
            "11111", "10000", "10000", "11110", "10000", "10000", "11111",
        ]),
        'F' => Some([
            "11111", "10000", "10000", "11110", "10000", "10000", "10000",
        ]),
        'G' => Some([
            "01111", "10000", "10000", "10111", "10001", "10001", "01111",
        ]),
        'H' => Some([
            "10001", "10001", "10001", "11111", "10001", "10001", "10001",
        ]),
        'I' => Some([
            "11111", "00100", "00100", "00100", "00100", "00100", "11111",
        ]),
        'J' => Some([
            "00111", "00010", "00010", "00010", "00010", "10010", "01100",
        ]),
        'K' => Some([
            "10001", "10010", "10100", "11000", "10100", "10010", "10001",
        ]),
        'L' => Some([
            "10000", "10000", "10000", "10000", "10000", "10000", "11111",
        ]),
        'M' => Some([
            "10001", "11011", "10101", "10101", "10001", "10001", "10001",
        ]),
        'N' => Some([
            "10001", "11001", "10101", "10011", "10001", "10001", "10001",
        ]),
        'O' => Some([
            "01110", "10001", "10001", "10001", "10001", "10001", "01110",
        ]),
        'P' => Some([
            "11110", "10001", "10001", "11110", "10000", "10000", "10000",
        ]),
        'Q' => Some([
            "01110", "10001", "10001", "10001", "10101", "10010", "01101",
        ]),
        'R' => Some([
            "11110", "10001", "10001", "11110", "10100", "10010", "10001",
        ]),
        'S' => Some([
            "01111", "10000", "10000", "01110", "00001", "00001", "11110",
        ]),
        'T' => Some([
            "11111", "00100", "00100", "00100", "00100", "00100", "00100",
        ]),
        'U' => Some([
            "10001", "10001", "10001", "10001", "10001", "10001", "01110",
        ]),
        'V' => Some([
            "10001", "10001", "10001", "10001", "10001", "01010", "00100",
        ]),
        'W' => Some([
            "10001", "10001", "10001", "10101", "10101", "10101", "01010",
        ]),
        'X' => Some([
            "10001", "10001", "01010", "00100", "01010", "10001", "10001",
        ]),
        'Y' => Some([
            "10001", "10001", "01010", "00100", "00100", "00100", "00100",
        ]),
        'Z' => Some([
            "11111", "00001", "00010", "00100", "01000", "10000", "11111",
        ]),
        '0' => Some([
            "01110", "10001", "10011", "10101", "11001", "10001", "01110",
        ]),
        '1' => Some([
            "00100", "01100", "00100", "00100", "00100", "00100", "01110",
        ]),
        '2' => Some([
            "01110", "10001", "00001", "00010", "00100", "01000", "11111",
        ]),
        '3' => Some([
            "11110", "00001", "00001", "01110", "00001", "00001", "11110",
        ]),
        '4' => Some([
            "00010", "00110", "01010", "10010", "11111", "00010", "00010",
        ]),
        '5' => Some([
            "11111", "10000", "10000", "11110", "00001", "00001", "11110",
        ]),
        '6' => Some([
            "01110", "10000", "10000", "11110", "10001", "10001", "01110",
        ]),
        '7' => Some([
            "11111", "00001", "00010", "00100", "01000", "01000", "01000",
        ]),
        '8' => Some([
            "01110", "10001", "10001", "01110", "10001", "10001", "01110",
        ]),
        '9' => Some([
            "01110", "10001", "10001", "01111", "00001", "00001", "01110",
        ]),
        'a' => Some([
            "00000", "00000", "01110", "00001", "01111", "10001", "01111",
        ]),
        'b' => Some([
            "10000", "10000", "11110", "10001", "10001", "10001", "11110",
        ]),
        'c' => Some([
            "00000", "00000", "01111", "10000", "10000", "10000", "01111",
        ]),
        'd' => Some([
            "00001", "00001", "01111", "10001", "10001", "10001", "01111",
        ]),
        'e' => Some([
            "00000", "00000", "01110", "10001", "11111", "10000", "01111",
        ]),
        'f' => Some([
            "00111", "01000", "01000", "11110", "01000", "01000", "01000",
        ]),
        'g' => Some([
            "00000", "00000", "01111", "10001", "10001", "01111", "00001",
        ]),
        'h' => Some([
            "10000", "10000", "11110", "10001", "10001", "10001", "10001",
        ]),
        'i' => Some([
            "00100", "00000", "00100", "00100", "00100", "00100", "00100",
        ]),
        'j' => Some([
            "00010", "00000", "00010", "00010", "00010", "10010", "01100",
        ]),
        'k' => Some([
            "10000", "10000", "10010", "10100", "11000", "10100", "10010",
        ]),
        'l' => Some([
            "01000", "01000", "01000", "01000", "01000", "01000", "01110",
        ]),
        'm' => Some([
            "00000", "00000", "11110", "10101", "10101", "10101", "10101",
        ]),
        'n' => Some([
            "00000", "00000", "11110", "10001", "10001", "10001", "10001",
        ]),
        'o' => Some([
            "00000", "00000", "01110", "10001", "10001", "10001", "01110",
        ]),
        'p' => Some([
            "00000", "00000", "11110", "10001", "10001", "11110", "10000",
        ]),
        'q' => Some([
            "00000", "00000", "01111", "10001", "10001", "01111", "00001",
        ]),
        'r' => Some([
            "00000", "00000", "11010", "10001", "10000", "10000", "10000",
        ]),
        's' => Some([
            "00000", "00000", "01111", "10000", "01110", "00001", "11110",
        ]),
        't' => Some([
            "01000", "01000", "11110", "01000", "01000", "01001", "00110",
        ]),
        'u' => Some([
            "00000", "00000", "10001", "10001", "10001", "10001", "01110",
        ]),
        'v' => Some([
            "00000", "00000", "10001", "10001", "10001", "01010", "00100",
        ]),
        'w' => Some([
            "00000", "00000", "10001", "10001", "10101", "10101", "01010",
        ]),
        'x' => Some([
            "00000", "00000", "10001", "01010", "00100", "01010", "10001",
        ]),
        'y' => Some([
            "00000", "00000", "10001", "10001", "10001", "01111", "00001",
        ]),
        'z' => Some([
            "00000", "00000", "11111", "00010", "00100", "01000", "11111",
        ]),
        ' ' => Some([
            "00000", "00000", "00000", "00000", "00000", "00000", "00000",
        ]),
        '.' => Some([
            "00000", "00000", "00000", "00000", "00000", "00100", "00100",
        ]),
        '-' => Some([
            "00000", "00000", "00000", "11111", "00000", "00000", "00000",
        ]),
        ':' => Some([
            "00000", "00100", "00100", "00000", "00100", "00100", "00000",
        ]),
        '/' => Some([
            "00001", "00010", "00100", "01000", "10000", "00000", "00000",
        ]),
        ',' => Some([
            "00000", "00000", "00000", "00000", "00000", "00100", "01000",
        ]),
        '!' => Some([
            "00100", "00100", "00100", "00100", "00100", "00000", "00100",
        ]),
        '?' => Some([
            "01110", "10001", "00001", "00010", "00100", "00000", "00100",
        ]),
        ';' => Some([
            "00000", "00100", "00100", "00000", "00100", "01000", "00000",
        ]),
        '(' => Some([
            "00010", "00100", "01000", "01000", "01000", "00100", "00010",
        ]),
        ')' => Some([
            "01000", "00100", "00010", "00010", "00010", "00100", "01000",
        ]),
        '[' => Some([
            "01110", "01000", "01000", "01000", "01000", "01000", "01110",
        ]),
        ']' => Some([
            "01110", "00010", "00010", "00010", "00010", "00010", "01110",
        ]),
        '{' => Some([
            "00010", "00100", "00100", "01000", "00100", "00100", "00010",
        ]),
        '}' => Some([
            "01000", "00100", "00100", "00010", "00100", "00100", "01000",
        ]),
        '"' => Some([
            "01010", "01010", "00000", "00000", "00000", "00000", "00000",
        ]),
        '\'' => Some([
            "00100", "00100", "00000", "00000", "00000", "00000", "00000",
        ]),
        '#' => Some([
            "01010", "01010", "11111", "01010", "11111", "01010", "01010",
        ]),
        '$' => Some([
            "00100", "01111", "10100", "01110", "00101", "11110", "00100",
        ]),
        '%' => Some([
            "11001", "11010", "00010", "00100", "01000", "01011", "10011",
        ]),
        '&' => Some([
            "01100", "10010", "10100", "01000", "10101", "10010", "01101",
        ]),
        '*' => Some([
            "00000", "01010", "10101", "01110", "10101", "01010", "00000",
        ]),
        '+' => Some([
            "00000", "00100", "00100", "11111", "00100", "00100", "00000",
        ]),
        '<' => Some([
            "00001", "00010", "00100", "01000", "00100", "00010", "00001",
        ]),
        '=' => Some([
            "00000", "11111", "00000", "11111", "00000", "00000", "00000",
        ]),
        '>' => Some([
            "10000", "01000", "00100", "00010", "00100", "01000", "10000",
        ]),
        '@' => Some([
            "01110", "10001", "10111", "10101", "10111", "10000", "01110",
        ]),
        '\\' => Some([
            "10000", "01000", "00100", "00010", "00001", "00000", "00000",
        ]),
        '^' => Some([
            "00100", "01010", "10001", "00000", "00000", "00000", "00000",
        ]),
        '_' => Some([
            "00000", "00000", "00000", "00000", "00000", "00000", "11111",
        ]),
        '`' => Some([
            "01000", "00100", "00010", "00000", "00000", "00000", "00000",
        ]),
        '|' => Some([
            "00100", "00100", "00100", "00100", "00100", "00100", "00100",
        ]),
        '~' => Some([
            "00000", "00000", "01010", "10101", "00000", "00000", "00000",
        ]),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_render_bitmap_text() {
        let img = render_text_bitmap("AB", 64, Luma([255]), Luma([0]), 8);
        assert!(img.width() > 0);
        assert_eq!(img.height(), 64);
    }

    #[test]
    fn test_all_ascii_glyphs_exist() {
        let chars: Vec<char> = (' '..='~').collect();
        let mut missing = Vec::new();
        for ch in chars {
            if glyph_5x7_rows(ch).is_none() {
                missing.push(ch);
            }
        }
        assert!(missing.is_empty(), "Missing glyphs for: {:?}", missing);
    }
}