use std::sync::Arc;
use fontdb::{Database, Family, Query, Source, Style, Weight};
use crate::types::FontFaceId;
pub struct FontEntry {
pub fontdb_id: fontdb::ID,
pub face_index: u32,
pub data: Arc<Vec<u8>>,
pub swash_cache_key: swash::CacheKey,
}
pub struct FontRegistry {
fontdb: Database,
fonts: Vec<Option<FontEntry>>,
generic_families: std::collections::HashMap<String, String>,
default_font: Option<FontFaceId>,
default_size_px: f32,
}
impl Default for FontRegistry {
fn default() -> Self {
Self::new()
}
}
impl FontRegistry {
pub fn new() -> Self {
Self {
fontdb: Database::new(),
fonts: Vec::new(),
generic_families: std::collections::HashMap::new(),
default_font: None,
default_size_px: 16.0,
}
}
pub fn register_font(&mut self, data: &[u8]) -> Vec<FontFaceId> {
let arc_data: Arc<Vec<u8>> = Arc::new(data.to_vec());
let source = Source::Binary(arc_data.clone());
let fontdb_ids = self.fontdb.load_font_source(source);
let mut face_ids = Vec::new();
for fontdb_id in fontdb_ids {
let face_index = self.fontdb.face(fontdb_id).map(|f| f.index).unwrap_or(0);
let swash_cache_key = swash::CacheKey::new();
let entry = FontEntry {
fontdb_id,
face_index,
data: arc_data.clone(),
swash_cache_key,
};
let face_id = FontFaceId(self.fonts.len() as u32);
self.fonts.push(Some(entry));
face_ids.push(face_id);
}
face_ids
}
pub fn register_font_as(
&mut self,
data: &[u8],
family: &str,
weight: u16,
italic: bool,
) -> Vec<FontFaceId> {
let arc_data: Arc<Vec<u8>> = Arc::new(data.to_vec());
let source = Source::Binary(arc_data.clone());
let fontdb_ids = self.fontdb.load_font_source(source);
let mut face_ids = Vec::new();
for fontdb_id in fontdb_ids {
if let Some(face_info) = self.fontdb.face(fontdb_id) {
let mut info = face_info.clone();
info.families = vec![(family.to_string(), fontdb::Language::English_UnitedStates)];
info.weight = Weight(weight);
info.style = if italic { Style::Italic } else { Style::Normal };
let face_index = info.index;
self.fontdb.remove_face(fontdb_id);
let new_id = self.fontdb.push_face_info(info);
let swash_cache_key = swash::CacheKey::new();
let entry = FontEntry {
fontdb_id: new_id,
face_index,
data: arc_data.clone(),
swash_cache_key,
};
let face_id = FontFaceId(self.fonts.len() as u32);
self.fonts.push(Some(entry));
face_ids.push(face_id);
}
}
face_ids
}
pub fn set_default_font(&mut self, face: FontFaceId, size_px: f32) {
self.default_font = Some(face);
self.default_size_px = size_px;
}
pub fn default_font(&self) -> Option<FontFaceId> {
self.default_font
}
pub fn default_size_px(&self) -> f32 {
self.default_size_px
}
pub fn set_generic_family(&mut self, generic: &str, family: &str) {
self.generic_families
.insert(generic.to_string(), family.to_string());
}
pub fn resolve_family_name<'a>(&'a self, family: &'a str) -> &'a str {
self.generic_families
.get(family)
.map(|s| s.as_str())
.unwrap_or(family)
}
pub fn query_font(&self, family: &str, weight: u16, italic: bool) -> Option<FontFaceId> {
let resolved = self.resolve_family_name(family);
let style = if italic { Style::Italic } else { Style::Normal };
let query = Query {
families: &[Family::Name(resolved)],
weight: Weight(weight),
style,
..Query::default()
};
let fontdb_id = self.fontdb.query(&query)?;
self.fontdb_id_to_face_id(fontdb_id)
}
pub fn font_family_name(&self, face_id: FontFaceId) -> Option<String> {
let entry = self.get(face_id)?;
let face_info = self.fontdb.face(entry.fontdb_id)?;
face_info.families.first().map(|(name, _)| name.clone())
}
pub fn get(&self, face_id: FontFaceId) -> Option<&FontEntry> {
self.fonts
.get(face_id.0 as usize)
.and_then(|opt| opt.as_ref())
}
pub fn query_variant(
&self,
base_face: FontFaceId,
weight: u16,
italic: bool,
) -> Option<FontFaceId> {
let entry = self.get(base_face)?;
let face_info = self.fontdb.face(entry.fontdb_id)?;
let family_name = face_info.families.first().map(|(name, _)| name.as_str())?;
self.query_font(family_name, weight, italic)
}
fn fontdb_id_to_face_id(&self, fontdb_id: fontdb::ID) -> Option<FontFaceId> {
self.fonts.iter().enumerate().find_map(|(i, entry)| {
entry
.as_ref()
.filter(|e| e.fontdb_id == fontdb_id)
.map(|_| FontFaceId(i as u32))
})
}
pub fn all_entries(&self) -> impl Iterator<Item = (FontFaceId, &FontEntry)> {
self.fonts
.iter()
.enumerate()
.filter_map(|(i, opt)| opt.as_ref().map(|entry| (FontFaceId(i as u32), entry)))
}
}