1use std::collections::hash_map::DefaultHasher;
13use std::hash::{Hash, Hasher};
14use std::sync::{Arc, RwLock};
15
16use crate::cache::RenderOutputCache;
17use crate::cache_config;
18use crate::types::{RenderOutput, ShapingResult};
19use crate::RenderParams;
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct GlyphCacheKey {
24 pub renderer: String,
26 pub font_id: u64,
28 pub shaped_hash: u64,
30 pub render_hash: u64,
32}
33
34impl GlyphCacheKey {
35 pub fn new(
37 renderer: impl Into<String>,
38 font_data: &[u8],
39 shaped: &ShapingResult,
40 render_params: &RenderParams,
41 ) -> Self {
42 let font_id = hash_bytes(font_data);
43 let shaped_hash = hash_shaping_result(shaped);
44 let render_hash = hash_render_params(render_params);
45
46 Self {
47 renderer: renderer.into(),
48 font_id,
49 shaped_hash,
50 render_hash,
51 }
52 }
53}
54
55fn hash_bytes(bytes: &[u8]) -> u64 {
56 let mut hasher = DefaultHasher::new();
57 bytes.hash(&mut hasher);
58 hasher.finish()
59}
60
61fn hash_shaping_result(shaped: &ShapingResult) -> u64 {
62 let mut hasher = DefaultHasher::new();
63
64 shaped.direction.hash(&mut hasher);
65 shaped.advance_width.to_bits().hash(&mut hasher);
66 shaped.advance_height.to_bits().hash(&mut hasher);
67
68 for glyph in &shaped.glyphs {
69 glyph.id.hash(&mut hasher);
70 glyph.cluster.hash(&mut hasher);
71 glyph.x.to_bits().hash(&mut hasher);
72 glyph.y.to_bits().hash(&mut hasher);
73 glyph.advance.to_bits().hash(&mut hasher);
74 }
75
76 hasher.finish()
77}
78
79fn hash_render_params(params: &RenderParams) -> u64 {
80 let mut hasher = DefaultHasher::new();
81
82 params.padding.hash(&mut hasher);
83 params.antialias.hash(&mut hasher);
84 params.color_palette.hash(&mut hasher);
85 params.output.hash(&mut hasher);
86 params.foreground.hash(&mut hasher);
87 params.background.hash(&mut hasher);
88
89 for (tag, value) in ¶ms.variations {
90 tag.hash(&mut hasher);
91 value.to_bits().hash(&mut hasher);
92 }
93
94 for source in params.glyph_sources.effective_order() {
95 source.hash(&mut hasher);
96 }
97
98 let mut denied: Vec<_> = params.glyph_sources.deny.iter().copied().collect();
99 denied.sort();
100 for deny in denied {
101 deny.hash(&mut hasher);
102 }
103
104 hasher.finish()
105}
106
107pub struct GlyphCache {
112 cache: RenderOutputCache<GlyphCacheKey>,
113}
114
115impl GlyphCache {
116 pub fn new() -> Self {
118 Self {
119 cache: RenderOutputCache::with_default_limit(),
120 }
121 }
122
123 pub fn with_max_bytes(max_bytes: u64) -> Self {
125 Self {
126 cache: RenderOutputCache::new(max_bytes),
127 }
128 }
129
130 pub fn get(&self, key: &GlyphCacheKey) -> Option<RenderOutput> {
134 if !cache_config::is_caching_enabled() {
135 return None;
136 }
137 self.cache.get(key)
138 }
139
140 pub fn insert(&self, key: GlyphCacheKey, output: RenderOutput) {
145 if !cache_config::is_caching_enabled() {
146 return;
147 }
148 self.cache.insert(key, output);
149 }
150
151 pub fn hit_rate(&self) -> f64 {
152 self.cache.hit_rate()
153 }
154
155 pub fn metrics(&self) -> crate::cache::CacheMetrics {
156 self.cache.metrics()
157 }
158
159 pub fn weighted_size(&self) -> u64 {
161 self.cache.weighted_size()
162 }
163
164 pub fn entry_count(&self) -> u64 {
166 self.cache.entry_count()
167 }
168
169 pub fn clear(&self) {
171 self.cache.clear();
172 }
173}
174
175impl Default for GlyphCache {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181pub type SharedGlyphCache = Arc<RwLock<GlyphCache>>;
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::types::{Direction, PositionedGlyph};
188
189 fn shaped() -> ShapingResult {
190 ShapingResult {
191 glyphs: vec![PositionedGlyph {
192 id: 42,
193 x: 1.5,
194 y: 0.0,
195 advance: 10.0,
196 cluster: 0,
197 }],
198 advance_width: 10.0,
199 advance_height: 16.0,
200 direction: Direction::LeftToRight,
201 }
202 }
203
204 fn render_params() -> RenderParams {
205 RenderParams::default()
206 }
207
208 #[test]
209 fn key_changes_with_renderer() {
210 let s = shaped();
211 let p = render_params();
212 let k1 = GlyphCacheKey::new("r1", b"font", &s, &p);
213 let k2 = GlyphCacheKey::new("r2", b"font", &s, &p);
214 assert_ne!(k1, k2);
215 }
216
217 #[test]
218 fn cache_stores_and_retrieves() {
219 let _guard = crate::cache_config::scoped_caching_enabled(true);
220
221 let cache = GlyphCache::new();
222 let key = GlyphCacheKey::new("r1", b"font", &shaped(), &render_params());
223 let output = RenderOutput::Json("x".into());
224
225 cache.insert(key.clone(), output.clone());
226 let hit = match cache.get(&key) {
227 Some(hit) => hit,
228 None => unreachable!("cache should return stored value"),
229 };
230
231 if let RenderOutput::Json(body) = hit {
232 assert_eq!(body, "x");
233 } else {
234 unreachable!("expected json");
235 }
236 }
237}