Skip to main content

text_typeset/atlas/
allocator.rs

1use etagere::{AllocId, Allocation, BucketedAtlasAllocator, size2};
2
3const INITIAL_ATLAS_SIZE: i32 = 512;
4const MAX_ATLAS_SIZE: i32 = 4096;
5
6pub struct GlyphAtlas {
7    pub allocator: BucketedAtlasAllocator,
8    pub pixels: Vec<u8>,
9    pub width: u32,
10    pub height: u32,
11    pub dirty: bool,
12}
13
14impl Default for GlyphAtlas {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl GlyphAtlas {
21    pub fn new() -> Self {
22        let size = INITIAL_ATLAS_SIZE;
23        let pixel_count = (size * size) as usize * 4;
24        Self {
25            allocator: BucketedAtlasAllocator::new(size2(size, size)),
26            pixels: vec![0u8; pixel_count],
27            width: size as u32,
28            height: size as u32,
29            dirty: false,
30        }
31    }
32
33    /// Allocate a region for a glyph of `width`×`height` pixels. The
34    /// returned allocation reserves a 1-pixel gutter on the right and
35    /// bottom so adjacent glyphs can't bleed into each other under
36    /// float-precision UV rounding. Callers still blit only the
37    /// requested `width`×`height` pixels at `rect.min`; the extra
38    /// column/row of transparent pixels is the safety margin.
39    pub fn allocate(&mut self, width: u32, height: u32) -> Option<Allocation> {
40        let padded = size2(width as i32 + 1, height as i32 + 1);
41        if let Some(alloc) = self.allocator.allocate(padded) {
42            return Some(alloc);
43        }
44        // Try growing the atlas
45        let new_w = (self.width * 2).min(MAX_ATLAS_SIZE as u32) as i32;
46        let new_h = (self.height * 2).min(MAX_ATLAS_SIZE as u32) as i32;
47        if new_w as u32 == self.width && new_h as u32 == self.height {
48            return None; // Already at max size
49        }
50        self.grow(new_w as u32, new_h as u32);
51        self.allocator.allocate(padded)
52    }
53
54    pub fn deallocate(&mut self, id: AllocId) {
55        self.allocator.deallocate(id);
56    }
57
58    fn grow(&mut self, new_width: u32, new_height: u32) {
59        let mut new_pixels = vec![0u8; (new_width * new_height) as usize * 4];
60        // Copy existing pixels row by row
61        for y in 0..self.height {
62            let src_start = (y * self.width) as usize * 4;
63            let src_end = src_start + self.width as usize * 4;
64            let dst_start = (y * new_width) as usize * 4;
65            let dst_end = dst_start + self.width as usize * 4;
66            new_pixels[dst_start..dst_end].copy_from_slice(&self.pixels[src_start..src_end]);
67        }
68        self.allocator
69            .grow(size2(new_width as i32, new_height as i32));
70        self.pixels = new_pixels;
71        self.width = new_width;
72        self.height = new_height;
73        self.dirty = true;
74    }
75
76    /// Blit RGBA pixel data into the atlas at the given position.
77    pub fn blit_rgba(&mut self, x: u32, y: u32, w: u32, h: u32, data: &[u8]) {
78        for row in 0..h {
79            let src_start = (row * w) as usize * 4;
80            let src_end = src_start + w as usize * 4;
81            let dst_start = ((y + row) * self.width + x) as usize * 4;
82            let dst_end = dst_start + w as usize * 4;
83            self.pixels[dst_start..dst_end].copy_from_slice(&data[src_start..src_end]);
84        }
85        self.dirty = true;
86    }
87
88    /// Blit a single-channel alpha mask into the atlas as white RGBA.
89    pub fn blit_mask(&mut self, x: u32, y: u32, w: u32, h: u32, data: &[u8]) {
90        for row in 0..h {
91            for col in 0..w {
92                let alpha = data[(row * w + col) as usize];
93                let dst = ((y + row) * self.width + x + col) as usize * 4;
94                self.pixels[dst] = 255;
95                self.pixels[dst + 1] = 255;
96                self.pixels[dst + 2] = 255;
97                self.pixels[dst + 3] = alpha;
98            }
99        }
100        self.dirty = true;
101    }
102}