use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use crate::font_handle::FontHandle;
use crate::types::GlyphBounds;
const DEFAULT_MAX_ENTRIES: usize = 16384;
type BoundsCacheKey = (FontHandle, u32);
pub struct RasterBoundsCache {
inner: RwLock<FxHashMap<BoundsCacheKey, GlyphBounds>>,
max_entries: usize,
}
impl RasterBoundsCache {
pub fn new() -> Self {
Self {
inner: RwLock::new(FxHashMap::default()),
max_entries: DEFAULT_MAX_ENTRIES,
}
}
pub fn with_max_entries(max_entries: usize) -> Self {
Self {
inner: RwLock::new(FxHashMap::default()),
max_entries,
}
}
pub fn max_entries(&self) -> usize {
self.max_entries
}
#[inline]
pub fn get(&self, font: FontHandle, glyph_id: u32) -> Option<GlyphBounds> {
self.inner.read().get(&(font, glyph_id)).copied()
}
pub fn insert(
&self,
font: FontHandle,
glyph_id: u32,
bounds: GlyphBounds,
) -> Option<GlyphBounds> {
let mut guard = self.inner.write();
if guard.len() >= self.max_entries {
guard.clear();
}
guard.insert((font, glyph_id), bounds)
}
pub fn get_or_insert_with<F>(&self, font: FontHandle, glyph_id: u32, f: F) -> GlyphBounds
where
F: FnOnce() -> GlyphBounds,
{
let key = (font, glyph_id);
if let Some(bounds) = self.inner.read().get(&key).copied() {
return bounds;
}
let mut guard = self.inner.write();
if let Some(bounds) = guard.get(&key).copied() {
return bounds;
}
if guard.len() >= self.max_entries {
guard.clear();
}
*guard.entry(key).or_insert_with(f)
}
pub fn len(&self) -> usize {
self.inner.read().len()
}
pub fn is_empty(&self) -> bool {
self.inner.read().is_empty()
}
pub fn clear(&self) {
self.inner.write().clear();
}
}
impl Default for RasterBoundsCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_insert_and_get() {
let cache = RasterBoundsCache::new();
let font = FontHandle::from_face_id(0x1000, 16.0, 2.0);
assert!(cache.get(font, 65).is_none());
let bounds = GlyphBounds {
width: 10,
height: 20,
};
cache.insert(font, 65, bounds);
assert_eq!(cache.get(font, 65), Some(bounds));
assert_eq!(cache.len(), 1);
}
#[test]
fn cache_get_or_insert() {
let cache = RasterBoundsCache::new();
let font = FontHandle::from_face_id(0x2000, 16.0, 1.0);
let mut call_count = 0;
let bounds = cache.get_or_insert_with(font, 66, || {
call_count += 1;
GlyphBounds {
width: 5,
height: 10,
}
});
assert_eq!(
bounds,
GlyphBounds {
width: 5,
height: 10
}
);
assert_eq!(call_count, 1);
let bounds2 = cache.get_or_insert_with(font, 66, || {
call_count += 1;
GlyphBounds {
width: 99,
height: 99,
}
});
assert_eq!(
bounds2,
GlyphBounds {
width: 5,
height: 10
}
);
assert_eq!(call_count, 1);
}
#[test]
fn eviction_clears_on_overflow_insert() {
let cache = RasterBoundsCache::with_max_entries(4);
let font = FontHandle::from_face_id(0x3000, 12.0, 1.0);
let bounds = GlyphBounds {
width: 8,
height: 8,
};
for glyph_id in 0..4u32 {
cache.insert(font, glyph_id, bounds);
}
assert_eq!(cache.len(), 4);
cache.insert(font, 99, bounds);
assert_eq!(cache.len(), 1);
}
#[test]
fn eviction_clears_on_overflow_get_or_insert() {
let cache = RasterBoundsCache::with_max_entries(3);
let font = FontHandle::from_face_id(0x4000, 10.0, 1.0);
let bounds = GlyphBounds {
width: 5,
height: 5,
};
for glyph_id in 0..3u32 {
cache.insert(font, glyph_id, bounds);
}
assert_eq!(cache.len(), 3);
let result = cache.get_or_insert_with(font, 200, || GlyphBounds {
width: 7,
height: 7,
});
assert_eq!(
result,
GlyphBounds {
width: 7,
height: 7
}
);
assert_eq!(cache.len(), 1);
}
#[test]
fn whitespace_detection() {
assert!(GlyphBounds::ZERO.is_whitespace());
assert!(
GlyphBounds {
width: 0,
height: 10
}
.is_whitespace()
);
assert!(
GlyphBounds {
width: 10,
height: 0
}
.is_whitespace()
);
assert!(
!GlyphBounds {
width: 10,
height: 10
}
.is_whitespace()
);
}
}