vtk-pure-rs 0.2.0

Pure Rust visualization toolkit — data structures, filters, I/O, rendering
Documentation
use crate::render_wgpu::overlay::{OverlayVertex, push_quad};

/// Minimal 5x7 bitmap font for ASCII characters 32-126.
/// Each glyph is stored as 7 rows of 5 bits (packed into u8 per row).
const GLYPH_W: f32 = 5.0;
const GLYPH_H: f32 = 7.0;

/// Render a text string into overlay vertices at the given NDC position.
///
/// `x, y` is bottom-left in NDC [0,1]. `scale` is height in NDC units.
/// Returns the width of the rendered text in NDC units.
pub fn render_text(
    vertices: &mut Vec<OverlayVertex>,
    indices: &mut Vec<u32>,
    text: &str,
    x: f32,
    y: f32,
    scale: f32,
    color: [f32; 4],
) -> f32 {
    let pixel_h = scale;
    let pixel_w = pixel_h * (GLYPH_W / GLYPH_H);
    let char_w = pixel_w * 1.2; // spacing

    let mut cx = x;
    for ch in text.chars() {
        let glyph = get_glyph(ch);
        for row in 0..7 {
            let bits = glyph[row];
            for col in 0..5 {
                if bits & (1 << (4 - col)) != 0 {
                    let px = cx + col as f32 * (pixel_w / GLYPH_W);
                    let py = y + (6 - row) as f32 * (pixel_h / GLYPH_H);
                    let pw = pixel_w / GLYPH_W;
                    let ph = pixel_h / GLYPH_H;
                    push_quad(vertices, indices, px, py, pw, ph, color);
                }
            }
        }
        cx += char_w;
    }

    cx - x
}

/// Get the 5x7 bitmap for a character.
fn get_glyph(ch: char) -> [u8; 7] {
    let idx = ch as usize;
    if idx >= 32 && idx <= 126 {
        FONT_DATA[idx - 32]
    } else {
        FONT_DATA[0] // space
    }
}

/// 5x7 font data for ASCII 32-126.
/// Each entry is 7 bytes, each byte has 5 significant bits (MSB = leftmost pixel).
#[rustfmt::skip]
const FONT_DATA: [[u8; 7]; 95] = [
    [0x00,0x00,0x00,0x00,0x00,0x00,0x00], // 32 ' '
    [0x04,0x04,0x04,0x04,0x00,0x04,0x00], // 33 '!'
    [0x0A,0x0A,0x00,0x00,0x00,0x00,0x00], // 34 '"'
    [0x0A,0x1F,0x0A,0x0A,0x1F,0x0A,0x00], // 35 '#'
    [0x04,0x0F,0x14,0x0E,0x05,0x1E,0x04], // 36 '$'
    [0x18,0x19,0x02,0x04,0x08,0x13,0x03], // 37 '%'
    [0x08,0x14,0x14,0x08,0x15,0x12,0x0D], // 38 '&'
    [0x04,0x04,0x00,0x00,0x00,0x00,0x00], // 39 '''
    [0x02,0x04,0x08,0x08,0x08,0x04,0x02], // 40 '('
    [0x08,0x04,0x02,0x02,0x02,0x04,0x08], // 41 ')'
    [0x00,0x04,0x15,0x0E,0x15,0x04,0x00], // 42 '*'
    [0x00,0x04,0x04,0x1F,0x04,0x04,0x00], // 43 '+'
    [0x00,0x00,0x00,0x00,0x04,0x04,0x08], // 44 ','
    [0x00,0x00,0x00,0x1F,0x00,0x00,0x00], // 45 '-'
    [0x00,0x00,0x00,0x00,0x00,0x04,0x00], // 46 '.'
    [0x01,0x01,0x02,0x04,0x08,0x10,0x10], // 47 '/'
    [0x0E,0x11,0x13,0x15,0x19,0x11,0x0E], // 48 '0'
    [0x04,0x0C,0x04,0x04,0x04,0x04,0x0E], // 49 '1'
    [0x0E,0x11,0x01,0x06,0x08,0x10,0x1F], // 50 '2'
    [0x0E,0x11,0x01,0x06,0x01,0x11,0x0E], // 51 '3'
    [0x02,0x06,0x0A,0x12,0x1F,0x02,0x02], // 52 '4'
    [0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E], // 53 '5'
    [0x06,0x08,0x10,0x1E,0x11,0x11,0x0E], // 54 '6'
    [0x1F,0x01,0x02,0x04,0x08,0x08,0x08], // 55 '7'
    [0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E], // 56 '8'
    [0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C], // 57 '9'
    [0x00,0x04,0x00,0x00,0x04,0x00,0x00], // 58 ':'
    [0x00,0x04,0x00,0x00,0x04,0x04,0x08], // 59 ';'
    [0x02,0x04,0x08,0x10,0x08,0x04,0x02], // 60 '<'
    [0x00,0x00,0x1F,0x00,0x1F,0x00,0x00], // 61 '='
    [0x08,0x04,0x02,0x01,0x02,0x04,0x08], // 62 '>'
    [0x0E,0x11,0x01,0x02,0x04,0x00,0x04], // 63 '?'
    [0x0E,0x11,0x17,0x15,0x17,0x10,0x0E], // 64 '@'
    [0x0E,0x11,0x11,0x1F,0x11,0x11,0x11], // 65 'A'
    [0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E], // 66 'B'
    [0x0E,0x11,0x10,0x10,0x10,0x11,0x0E], // 67 'C'
    [0x1E,0x11,0x11,0x11,0x11,0x11,0x1E], // 68 'D'
    [0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F], // 69 'E'
    [0x1F,0x10,0x10,0x1E,0x10,0x10,0x10], // 70 'F'
    [0x0E,0x11,0x10,0x17,0x11,0x11,0x0F], // 71 'G'
    [0x11,0x11,0x11,0x1F,0x11,0x11,0x11], // 72 'H'
    [0x0E,0x04,0x04,0x04,0x04,0x04,0x0E], // 73 'I'
    [0x07,0x02,0x02,0x02,0x02,0x12,0x0C], // 74 'J'
    [0x11,0x12,0x14,0x18,0x14,0x12,0x11], // 75 'K'
    [0x10,0x10,0x10,0x10,0x10,0x10,0x1F], // 76 'L'
    [0x11,0x1B,0x15,0x15,0x11,0x11,0x11], // 77 'M'
    [0x11,0x19,0x15,0x13,0x11,0x11,0x11], // 78 'N'
    [0x0E,0x11,0x11,0x11,0x11,0x11,0x0E], // 79 'O'
    [0x1E,0x11,0x11,0x1E,0x10,0x10,0x10], // 80 'P'
    [0x0E,0x11,0x11,0x11,0x15,0x12,0x0D], // 81 'Q'
    [0x1E,0x11,0x11,0x1E,0x14,0x12,0x11], // 82 'R'
    [0x0E,0x11,0x10,0x0E,0x01,0x11,0x0E], // 83 'S'
    [0x1F,0x04,0x04,0x04,0x04,0x04,0x04], // 84 'T'
    [0x11,0x11,0x11,0x11,0x11,0x11,0x0E], // 85 'U'
    [0x11,0x11,0x11,0x11,0x0A,0x0A,0x04], // 86 'V'
    [0x11,0x11,0x11,0x15,0x15,0x1B,0x11], // 87 'W'
    [0x11,0x11,0x0A,0x04,0x0A,0x11,0x11], // 88 'X'
    [0x11,0x11,0x0A,0x04,0x04,0x04,0x04], // 89 'Y'
    [0x1F,0x01,0x02,0x04,0x08,0x10,0x1F], // 90 'Z'
    [0x0E,0x08,0x08,0x08,0x08,0x08,0x0E], // 91 '['
    [0x10,0x10,0x08,0x04,0x02,0x01,0x01], // 92 '\'
    [0x0E,0x02,0x02,0x02,0x02,0x02,0x0E], // 93 ']'
    [0x04,0x0A,0x11,0x00,0x00,0x00,0x00], // 94 '^'
    [0x00,0x00,0x00,0x00,0x00,0x00,0x1F], // 95 '_'
    [0x08,0x04,0x00,0x00,0x00,0x00,0x00], // 96 '`'
    [0x00,0x00,0x0E,0x01,0x0F,0x11,0x0F], // 97 'a'
    [0x10,0x10,0x1E,0x11,0x11,0x11,0x1E], // 98 'b'
    [0x00,0x00,0x0E,0x11,0x10,0x11,0x0E], // 99 'c'
    [0x01,0x01,0x0F,0x11,0x11,0x11,0x0F], // 100 'd'
    [0x00,0x00,0x0E,0x11,0x1F,0x10,0x0E], // 101 'e'
    [0x06,0x08,0x1E,0x08,0x08,0x08,0x08], // 102 'f'
    [0x00,0x00,0x0F,0x11,0x0F,0x01,0x0E], // 103 'g'
    [0x10,0x10,0x1E,0x11,0x11,0x11,0x11], // 104 'h'
    [0x04,0x00,0x0C,0x04,0x04,0x04,0x0E], // 105 'i'
    [0x02,0x00,0x06,0x02,0x02,0x12,0x0C], // 106 'j'
    [0x10,0x10,0x12,0x14,0x18,0x14,0x12], // 107 'k'
    [0x0C,0x04,0x04,0x04,0x04,0x04,0x0E], // 108 'l'
    [0x00,0x00,0x1A,0x15,0x15,0x11,0x11], // 109 'm'
    [0x00,0x00,0x1E,0x11,0x11,0x11,0x11], // 110 'n'
    [0x00,0x00,0x0E,0x11,0x11,0x11,0x0E], // 111 'o'
    [0x00,0x00,0x1E,0x11,0x1E,0x10,0x10], // 112 'p'
    [0x00,0x00,0x0F,0x11,0x0F,0x01,0x01], // 113 'q'
    [0x00,0x00,0x16,0x19,0x10,0x10,0x10], // 114 'r'
    [0x00,0x00,0x0F,0x10,0x0E,0x01,0x1E], // 115 's'
    [0x08,0x08,0x1E,0x08,0x08,0x09,0x06], // 116 't'
    [0x00,0x00,0x11,0x11,0x11,0x13,0x0D], // 117 'u'
    [0x00,0x00,0x11,0x11,0x11,0x0A,0x04], // 118 'v'
    [0x00,0x00,0x11,0x11,0x15,0x15,0x0A], // 119 'w'
    [0x00,0x00,0x11,0x0A,0x04,0x0A,0x11], // 120 'x'
    [0x00,0x00,0x11,0x11,0x0F,0x01,0x0E], // 121 'y'
    [0x00,0x00,0x1F,0x02,0x04,0x08,0x1F], // 122 'z'
    [0x02,0x04,0x04,0x08,0x04,0x04,0x02], // 123 '{'
    [0x04,0x04,0x04,0x04,0x04,0x04,0x04], // 124 '|'
    [0x08,0x04,0x04,0x02,0x04,0x04,0x08], // 125 '}'
    [0x00,0x00,0x08,0x15,0x02,0x00,0x00], // 126 '~'
];

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

    #[test]
    fn render_text_produces_vertices() {
        let mut verts = Vec::new();
        let mut idxs = Vec::new();
        let w = render_text(&mut verts, &mut idxs, "Hi", 0.0, 0.0, 0.02, [1.0, 1.0, 1.0, 1.0]);
        assert!(w > 0.0);
        assert!(!verts.is_empty());
        assert!(!idxs.is_empty());
    }

    #[test]
    fn render_empty_text() {
        let mut verts = Vec::new();
        let mut idxs = Vec::new();
        let w = render_text(&mut verts, &mut idxs, "", 0.0, 0.0, 0.02, [1.0, 1.0, 1.0, 1.0]);
        assert_eq!(w, 0.0);
        assert!(verts.is_empty());
    }

    #[test]
    fn glyph_space_empty() {
        let g = get_glyph(' ');
        assert!(g.iter().all(|&b| b == 0));
    }

    #[test]
    fn glyph_a_nonempty() {
        let g = get_glyph('A');
        assert!(g.iter().any(|&b| b != 0));
    }
}