Skip to main content

ass_renderer/utils/
caches.rs

1//! Caching utilities for glyphs and textures
2
3use ahash::AHashMap;
4
5#[cfg(feature = "nostd")]
6use alloc::{string::String, vec, vec::Vec};
7#[cfg(not(feature = "nostd"))]
8use std::{string::String, vec::Vec};
9
10/// Glyph cache for rendered glyphs
11pub struct GlyphCache {
12    cache: AHashMap<GlyphKey, CachedGlyph>,
13    max_entries: usize,
14}
15
16/// Key for glyph cache lookup
17#[derive(Debug, Clone, Hash, PartialEq, Eq)]
18pub struct GlyphKey {
19    /// Font family
20    pub font_family: String,
21    /// Glyph ID
22    pub glyph_id: u16,
23    /// Font size
24    pub size: u32,
25    /// Style flags (bold, italic, etc.)
26    pub style_flags: u32,
27}
28
29/// Cached glyph data
30pub struct CachedGlyph {
31    /// Glyph bitmap data
32    pub bitmap: Vec<u8>,
33    /// Bitmap width
34    pub width: u32,
35    /// Bitmap height
36    pub height: u32,
37    /// Horizontal advance
38    pub advance: f32,
39    /// Bearing X
40    pub bearing_x: f32,
41    /// Bearing Y
42    pub bearing_y: f32,
43}
44
45impl GlyphCache {
46    /// Create a new glyph cache
47    pub fn new(max_entries: usize) -> Self {
48        Self {
49            cache: AHashMap::new(),
50            max_entries,
51        }
52    }
53
54    /// Get a cached glyph
55    pub fn get(&self, key: &GlyphKey) -> Option<&CachedGlyph> {
56        self.cache.get(key)
57    }
58
59    /// Insert a glyph into the cache
60    pub fn insert(&mut self, key: GlyphKey, glyph: CachedGlyph) {
61        if self.cache.len() >= self.max_entries {
62            // Simple eviction: remove first entry
63            if let Some(first_key) = self.cache.keys().next().cloned() {
64                self.cache.remove(&first_key);
65            }
66        }
67        self.cache.insert(key, glyph);
68    }
69
70    /// Clear the cache
71    pub fn clear(&mut self) {
72        self.cache.clear();
73    }
74
75    /// Get cache size
76    pub fn len(&self) -> usize {
77        self.cache.len()
78    }
79
80    /// Check if cache is empty
81    pub fn is_empty(&self) -> bool {
82        self.cache.is_empty()
83    }
84}
85
86/// Texture atlas for GPU backends
87pub struct TextureAtlas {
88    width: u32,
89    height: u32,
90    data: Vec<u8>,
91    allocations: Vec<AtlasRegion>,
92    next_x: u32,
93    next_y: u32,
94    row_height: u32,
95}
96
97/// Region in texture atlas
98#[derive(Debug, Clone)]
99pub struct AtlasRegion {
100    /// Region ID
101    pub id: u32,
102    /// X coordinate in atlas
103    pub x: u32,
104    /// Y coordinate in atlas
105    pub y: u32,
106    /// Region width
107    pub width: u32,
108    /// Region height
109    pub height: u32,
110}
111
112impl TextureAtlas {
113    /// Create a new texture atlas
114    pub fn new(width: u32, height: u32) -> Self {
115        Self {
116            width,
117            height,
118            data: vec![0; (width * height * 4) as usize],
119            allocations: Vec::new(),
120            next_x: 0,
121            next_y: 0,
122            row_height: 0,
123        }
124    }
125
126    /// Allocate a region in the atlas
127    pub fn allocate(&mut self, width: u32, height: u32) -> Option<AtlasRegion> {
128        // Simple row-based packing
129        if self.next_x + width > self.width {
130            // Move to next row
131            self.next_x = 0;
132            self.next_y += self.row_height;
133            self.row_height = 0;
134        }
135
136        if self.next_y + height > self.height {
137            // Atlas is full
138            return None;
139        }
140
141        let region = AtlasRegion {
142            id: self.allocations.len() as u32,
143            x: self.next_x,
144            y: self.next_y,
145            width,
146            height,
147        };
148
149        self.next_x += width;
150        self.row_height = self.row_height.max(height);
151        self.allocations.push(region.clone());
152
153        Some(region)
154    }
155
156    /// Write data to a region
157    pub fn write_region(&mut self, region: &AtlasRegion, data: &[u8]) {
158        let stride = self.width * 4;
159        for y in 0..region.height {
160            let src_offset = (y * region.width * 4) as usize;
161            let dst_offset = ((region.y + y) * stride + region.x * 4) as usize;
162            let src_end = src_offset + (region.width * 4) as usize;
163            let dst_end = dst_offset + (region.width * 4) as usize;
164
165            if src_end <= data.len() && dst_end <= self.data.len() {
166                self.data[dst_offset..dst_end].copy_from_slice(&data[src_offset..src_end]);
167            }
168        }
169    }
170
171    /// Get atlas data
172    pub fn data(&self) -> &[u8] {
173        &self.data
174    }
175
176    /// Clear the atlas
177    pub fn clear(&mut self) {
178        self.data.fill(0);
179        self.allocations.clear();
180        self.next_x = 0;
181        self.next_y = 0;
182        self.row_height = 0;
183    }
184}