use rustc_hash::FxHashMap;
use crate::{Font, GlyphId, LineMetrics};
pub const MAX_CACHED_CHARS: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MappedGlyph {
pub id: GlyphId,
pub advance: f32,
}
#[derive(Debug, Clone)]
pub struct GlyphMap {
font: Font,
px: f32,
ascii: [Option<MappedGlyph>; 128],
other: FxHashMap<char, Option<MappedGlyph>>,
fallback: Option<MappedGlyph>,
}
impl GlyphMap {
pub fn new(font: Font, px: f32) -> Self {
let resolve = |ch: char| {
font.glyph_id(ch).map(|id| MappedGlyph {
id,
advance: font.advance(id, px),
})
};
let mut ascii = [None; 128];
for (code, slot) in (0u8..).zip(ascii.iter_mut()) {
*slot = resolve(char::from(code));
}
let fallback = resolve('?');
Self {
font,
px,
ascii,
other: FxHashMap::default(),
fallback,
}
}
pub fn font(&self) -> &Font {
&self.font
}
pub fn px(&self) -> f32 {
self.px
}
pub fn cached_chars(&self) -> usize {
self.other.len()
}
pub fn line_metrics(&self) -> LineMetrics {
self.font.line_metrics(self.px)
}
#[inline]
pub fn get(&mut self, ch: char) -> Option<MappedGlyph> {
let mapped = match self.ascii.get(ch as usize) {
Some(slot) => *slot,
None => {
if self.other.len() >= MAX_CACHED_CHARS && !self.other.contains_key(&ch) {
self.other.clear();
}
let (font, px) = (&self.font, self.px);
*self.other.entry(ch).or_insert_with(|| {
font.glyph_id(ch).map(|id| MappedGlyph {
id,
advance: font.advance(id, px),
})
})
}
};
mapped.or(self.fallback)
}
pub fn measure(&mut self, text: &str) -> f32 {
text.chars()
.filter_map(|ch| self.get(ch))
.map(|g| g.advance)
.sum()
}
}