use std::collections::VecDeque;
use crate::rasterizer::AlphaBitmap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GlyphKey {
pub face_id: u64,
pub glyph_id: u16,
pub size_q8: u32,
pub shear_q14: i32,
}
impl GlyphKey {
pub fn new(face_id: u64, glyph_id: u16, size_px: f32) -> Self {
Self::new_styled(face_id, glyph_id, size_px, 0.0)
}
pub fn new_styled(face_id: u64, glyph_id: u16, size_px: f32, shear_x_per_y: f32) -> Self {
let size_q8 = (size_px * 256.0).round().max(0.0) as u32;
let shear_q14 = (shear_x_per_y * 16384.0).round() as i32;
Self {
face_id,
glyph_id,
size_q8,
shear_q14,
}
}
}
#[derive(Debug, Clone)]
pub struct CachedGlyph {
pub bitmap: AlphaBitmap,
pub offset_x: f32,
pub offset_y: f32,
}
pub const DEFAULT_CAPACITY: usize = 256;
#[derive(Debug)]
pub struct GlyphCache {
entries: VecDeque<(GlyphKey, CachedGlyph)>,
capacity: usize,
hits: u64,
misses: u64,
}
impl GlyphCache {
pub fn new(capacity: usize) -> Self {
Self {
entries: VecDeque::with_capacity(capacity.max(1)),
capacity: capacity.max(1),
hits: 0,
misses: 0,
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn misses(&self) -> u64 {
self.misses
}
pub fn get(&mut self, key: &GlyphKey) -> Option<CachedGlyph> {
let pos = self.entries.iter().position(|(k, _)| k == key);
match pos {
Some(0) => {
self.hits += 1;
Some(self.entries[0].1.clone())
}
Some(i) => {
self.hits += 1;
let entry = self.entries.remove(i).expect("position in range");
let val = entry.1.clone();
self.entries.push_front(entry);
Some(val)
}
None => {
self.misses += 1;
None
}
}
}
pub fn insert(&mut self, key: GlyphKey, value: CachedGlyph) {
if let Some(i) = self.entries.iter().position(|(k, _)| k == &key) {
self.entries.remove(i);
}
self.entries.push_front((key, value));
while self.entries.len() > self.capacity {
self.entries.pop_back();
}
}
}
impl Default for GlyphCache {
fn default() -> Self {
Self::new(DEFAULT_CAPACITY)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy(w: u32, h: u32) -> CachedGlyph {
CachedGlyph {
bitmap: AlphaBitmap::new(w, h),
offset_x: 0.0,
offset_y: 0.0,
}
}
#[test]
fn key_quantises_to_q8() {
let k1 = GlyphKey::new(7, 42, 16.0);
let k2 = GlyphKey::new(7, 42, 16.001);
assert_eq!(k1, k2);
let k3 = GlyphKey::new(7, 42, 16.5);
assert_ne!(k1, k3);
}
#[test]
fn lru_evicts_eldest() {
let mut c = GlyphCache::new(2);
c.insert(GlyphKey::new(0, 1, 16.0), dummy(2, 2));
c.insert(GlyphKey::new(0, 2, 16.0), dummy(2, 2));
c.insert(GlyphKey::new(0, 3, 16.0), dummy(2, 2));
assert!(c.get(&GlyphKey::new(0, 1, 16.0)).is_none());
assert!(c.get(&GlyphKey::new(0, 2, 16.0)).is_some());
assert!(c.get(&GlyphKey::new(0, 3, 16.0)).is_some());
}
#[test]
fn get_promotes_to_front() {
let mut c = GlyphCache::new(2);
c.insert(GlyphKey::new(0, 1, 16.0), dummy(2, 2));
c.insert(GlyphKey::new(0, 2, 16.0), dummy(2, 2));
let _ = c.get(&GlyphKey::new(0, 1, 16.0));
c.insert(GlyphKey::new(0, 3, 16.0), dummy(2, 2));
assert!(c.get(&GlyphKey::new(0, 1, 16.0)).is_some());
assert!(c.get(&GlyphKey::new(0, 2, 16.0)).is_none());
}
}