ailloli_ui_render_wgpu/icons/
raster.rs

1//! Shared rasterization (font glyphs and texture upload).
2
3use fontdue::Font;
4
5/// Rasterizes a glyph into a square RGBA buffer (`px_size × px_size`, white mask + alpha).
6///
7/// The font is sampled at `max(px_size, 8)` pixels. Glyph pixels are centered,
8/// clipped to the requested square, and written as white RGB with font coverage
9/// in alpha. A zero size returns an empty vector.
10///
11/// # Panics
12///
13/// May panic if `px_size * px_size * 4` overflows or cannot be allocated.
14///
15/// # Examples
16///
17/// ```
18/// use ailloli_ui_devicons_font::{DEVICON_FONT_BYTES, GENERIC_FILE_GLYPH};
19/// use ailloli_ui_render_wgpu::icons::raster::rasterize_glyph_mask;
20/// let font = fontdue::Font::from_bytes(DEVICON_FONT_BYTES,
21///     fontdue::FontSettings::default())?;
22/// let rgba = rasterize_glyph_mask(&font, GENERIC_FILE_GLYPH, 8);
23/// assert_eq!(rgba.len(), 8 * 8 * 4);
24/// # Ok::<(), &'static str>(())
25/// ```
26pub fn rasterize_glyph_mask(font: &Font, ch: char, px_size: u32) -> Vec<u8> {
27    let px = px_size.max(8) as f32;
28    let (metrics, bitmap) = font.rasterize(ch, px);
29    let gw = metrics.width.max(1) as u32;
30    let gh = metrics.height.max(1) as u32;
31
32    let mut rgba = vec![0u8; (px_size * px_size * 4) as usize];
33    let off_x = ((px_size as i32 - gw as i32) / 2).max(0) as u32;
34    let off_y = ((px_size as i32 - gh as i32) / 2).max(0) as u32;
35
36    for gy in 0..gh {
37        for gx in 0..gw {
38            let src_i = (gy * gw + gx) as usize;
39            let a = bitmap[src_i];
40            let dx = off_x + gx;
41            let dy = off_y + gy;
42            if dx >= px_size || dy >= px_size {
43                continue;
44            }
45            let dst_i = ((dy * px_size + dx) * 4) as usize;
46            rgba[dst_i] = 255;
47            rgba[dst_i + 1] = 255;
48            rgba[dst_i + 2] = 255;
49            rgba[dst_i + 3] = a;
50        }
51    }
52    rgba
53}
54
55/// Pads rows to 256-byte alignment for `queue.write_texture`.
56///
57/// `rgba` must contain at least `px_size * px_size * 4` bytes. The returned row
58/// pitch is the smallest multiple of 256 that can hold one RGBA row; padding is
59/// zero-filled.
60///
61/// # Panics
62///
63/// Panics for a short source slice, arithmetic overflow, or allocation failure.
64///
65/// # Examples
66///
67/// ```
68/// use ailloli_ui_render_wgpu::icons::raster::pad_rgba_rows;
69/// let (padded, pitch) = pad_rgba_rows(&[255; 2 * 2 * 4], 2);
70/// assert_eq!(pitch, 256);
71/// assert_eq!(padded.len(), 2 * 256);
72/// ```
73pub fn pad_rgba_rows(rgba: &[u8], px_size: u32) -> (Vec<u8>, u32) {
74    let unpadded_bpr = px_size * 4;
75    let padded_bpr = unpadded_bpr.div_ceil(256) * 256;
76    let mut padded = vec![0u8; (padded_bpr * px_size) as usize];
77    for row in 0..px_size as usize {
78        let src = row * unpadded_bpr as usize;
79        let dst = row * padded_bpr as usize;
80        padded[dst..dst + unpadded_bpr as usize]
81            .copy_from_slice(&rgba[src..src + unpadded_bpr as usize]);
82    }
83    (padded, padded_bpr)
84}