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,
pub x_subpixel_q4: u8,
}
pub const SUBPIXEL_STEPS: u8 = 16;
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 {
Self::new_subpixel(face_id, glyph_id, size_px, shear_x_per_y, 0)
}
pub fn new_subpixel(
face_id: u64,
glyph_id: u16,
size_px: f32,
shear_x_per_y: f32,
x_subpixel_q4: u8,
) -> 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;
let x_subpixel_q4 = x_subpixel_q4.min(SUBPIXEL_STEPS - 1);
Self {
face_id,
glyph_id,
size_q8,
shear_q14,
x_subpixel_q4,
}
}
}
pub fn subpixel_slot(x_fract: f32) -> u8 {
if !x_fract.is_finite() || x_fract <= 0.0 {
return 0;
}
if x_fract >= 1.0 {
return SUBPIXEL_STEPS - 1;
}
let q = (x_fract * SUBPIXEL_STEPS as f32).floor() as u8;
q.min(SUBPIXEL_STEPS - 1)
}
pub fn subpixel_offset(slot: u8) -> f32 {
let s = slot.min(SUBPIXEL_STEPS - 1);
(s as f32) / (SUBPIXEL_STEPS as f32)
}
#[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 subpixel_slot_quantises_within_pixel() {
assert_eq!(subpixel_slot(0.0), 0);
assert_eq!(subpixel_slot(0.0625), 1); assert_eq!(subpixel_slot(0.5), 8);
assert_eq!(subpixel_slot(0.999), SUBPIXEL_STEPS - 1);
assert_eq!(subpixel_slot(-0.1), 0);
assert_eq!(subpixel_slot(f32::NAN), 0);
assert_eq!(subpixel_slot(1.5), SUBPIXEL_STEPS - 1);
}
#[test]
fn subpixel_offset_round_trips_via_slot() {
for s in 0..SUBPIXEL_STEPS {
let x = subpixel_offset(s);
assert_eq!(subpixel_slot(x), s, "slot {s} -> x {x}");
}
}
#[test]
fn subpixel_keys_are_distinct() {
let k0 = GlyphKey::new_subpixel(7, 42, 16.0, 0.0, 0);
let k1 = GlyphKey::new_subpixel(7, 42, 16.0, 0.0, 1);
let k8 = GlyphKey::new_subpixel(7, 42, 16.0, 0.0, 8);
assert_ne!(k0, k1);
assert_ne!(k0, k8);
assert_ne!(k1, k8);
assert_eq!(GlyphKey::new(7, 42, 16.0), k0);
}
#[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());
}
}