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}