use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use xxhash_rust::xxh3::{xxh3_64, Xxh3};
use super::pbf::{decode_glyph_range, GlyphPbfError};
pub const SDF_EM_PX: f32 = 24.0;
pub const SDF_RADIUS_PX: f32 = 8.0;
pub const SDF_EDGE: f32 = 0.75;
pub const SDF_BORDER: u32 = 3;
pub const SDF_Y_OFFSET_PX: f32 = -17.0;
#[derive(Debug, Clone)]
pub struct SdfGlyph {
pub id: u32,
pub bitmap: Vec<u8>,
pub width: u32,
pub height: u32,
pub left: i32,
pub top: i32,
pub advance: u32,
}
pub type RangeFetcher = Box<dyn Fn(u32, u32) -> Result<Vec<u8>, String> + Send + Sync>;
enum RangeSlot {
Loaded {
glyphs: HashMap<u16, Arc<SdfGlyph>>,
hash: u64,
},
Failed,
}
pub struct SdfFontStack {
ranges: RwLock<HashMap<u16, RangeSlot>>,
fetcher: Option<RangeFetcher>,
}
impl SdfFontStack {
pub fn new() -> Self {
SdfFontStack {
ranges: RwLock::new(HashMap::new()),
fetcher: None,
}
}
pub fn with_fetcher(fetcher: RangeFetcher) -> Self {
SdfFontStack {
ranges: RwLock::new(HashMap::new()),
fetcher: Some(fetcher),
}
}
pub fn has_fetcher(&self) -> bool {
self.fetcher.is_some()
}
pub fn block_of(c: char) -> Option<u16> {
u16::try_from(c as u32).ok().map(|u| u >> 8)
}
pub fn block_bounds(block: u16) -> (u32, u32) {
let start = u32::from(block) << 8;
(start, start + 255)
}
pub fn blocks_for(text: &str) -> Vec<u16> {
let mut blocks: Vec<u16> = text.chars().filter_map(Self::block_of).collect();
blocks.sort_unstable();
blocks.dedup();
blocks
}
pub fn is_loaded(&self, block: u16) -> bool {
self.ranges
.read()
.expect("range map poisoned")
.contains_key(&block)
}
pub fn loaded_size(&self) -> (usize, usize) {
let ranges = self.ranges.read().expect("range map poisoned");
let bytes = ranges
.values()
.map(|slot| match slot {
RangeSlot::Loaded { glyphs, .. } => {
glyphs.values().map(|g| g.bitmap.len()).sum::<usize>()
}
RangeSlot::Failed => 0,
})
.sum();
(ranges.len(), bytes)
}
pub fn insert_range(&self, bytes: &[u8]) -> Result<(), GlyphPbfError> {
let decoded = decode_glyph_range(bytes)?;
let block = (decoded.start >> 8) as u16;
let glyphs = decoded
.glyphs
.into_iter()
.filter_map(|g| u16::try_from(g.id).ok().map(|id| (id, Arc::new(g))))
.collect();
self.ranges.write().expect("range map poisoned").insert(
block,
RangeSlot::Loaded {
glyphs,
hash: xxh3_64(bytes),
},
);
Ok(())
}
pub fn glyph(&self, c: char) -> Option<Arc<SdfGlyph>> {
let block = Self::block_of(c)?;
self.ensure(block);
match self.ranges.read().expect("range map poisoned").get(&block) {
Some(RangeSlot::Loaded { glyphs, .. }) => glyphs.get(&(c as u16)).cloned(),
_ => None,
}
}
pub fn coverage(&self, c: char) -> SdfCoverage {
let Some(block) = Self::block_of(c) else {
return SdfCoverage::Absent;
};
self.ensure(block);
match self.ranges.read().expect("range map poisoned").get(&block) {
Some(RangeSlot::Loaded { glyphs, .. }) => {
if glyphs.contains_key(&(c as u16)) {
SdfCoverage::Present
} else {
SdfCoverage::Absent
}
}
_ => SdfCoverage::RangeUnavailable,
}
}
pub fn ranges_hash(&self) -> u128 {
let map = self.ranges.read().expect("range map poisoned");
let mut entries: Vec<(u16, u64)> = map
.iter()
.map(|(&block, slot)| match slot {
RangeSlot::Loaded { hash, .. } => (block, *hash),
RangeSlot::Failed => (block, u64::MAX),
})
.collect();
entries.sort_unstable_by_key(|&(block, _)| block);
let mut h = Xxh3::new();
for (block, hash) in entries {
h.update(&block.to_le_bytes());
h.update(&hash.to_le_bytes());
}
h.digest128()
}
fn ensure(&self, block: u16) {
let Some(fetcher) = &self.fetcher else {
return;
};
if self.is_loaded(block) {
return;
}
let (start, end) = Self::block_bounds(block);
let slot = match fetcher(start, end) {
Ok(bytes) => match decode_glyph_range(&bytes) {
Ok(_) => {
let _ = self.insert_range(&bytes);
return;
}
Err(e) => {
tracing::warn!("glyph range {start}-{end}: decode failed: {e}");
RangeSlot::Failed
}
},
Err(e) => {
tracing::warn!("glyph range {start}-{end}: fetch failed: {e}");
RangeSlot::Failed
}
};
self.ranges
.write()
.expect("range map poisoned")
.insert(block, slot);
}
}
impl Default for SdfFontStack {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for SdfFontStack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let map = self.ranges.read().expect("range map poisoned");
f.debug_struct("SdfFontStack")
.field("ranges", &map.len())
.field("fetcher", &self.fetcher.is_some())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdfCoverage {
Present,
Absent,
RangeUnavailable,
}