#[cfg(not(target_arch = "wasm32"))]
use std::collections::HashSet;
#[cfg(not(target_arch = "wasm32"))]
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use typst::foundations::Bytes;
use typst::text::{Font, FontBook};
use typst::utils::LazyHash;
pub struct Fonts {
book: LazyHash<FontBook>,
slots: Vec<FontSlot>,
}
struct FontSlot {
#[cfg(not(target_arch = "wasm32"))]
path: Option<PathBuf>,
#[cfg(not(target_arch = "wasm32"))]
index: u32,
font: OnceLock<Option<Font>>,
}
impl FontSlot {
fn loaded(font: Font) -> Self {
Self {
#[cfg(not(target_arch = "wasm32"))]
path: None,
#[cfg(not(target_arch = "wasm32"))]
index: 0,
font: OnceLock::from(Some(font)),
}
}
fn get(&self) -> Option<Font> {
self.font
.get_or_init(|| {
#[cfg(not(target_arch = "wasm32"))]
{
let data = std::fs::read(self.path.as_ref()?).ok()?;
Font::new(Bytes::new(data), self.index)
}
#[cfg(target_arch = "wasm32")]
{
None
}
})
.clone()
}
}
impl Fonts {
pub fn builder() -> FontsBuilder {
FontsBuilder {
book: FontBook::new(),
slots: Vec::new(),
#[cfg(not(target_arch = "wasm32"))]
seen: HashSet::new(),
}
}
pub fn book(&self) -> &LazyHash<FontBook> {
&self.book
}
pub fn font(&self, index: usize) -> Option<Font> {
self.slots.get(index)?.get()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn loaded_files(&self) -> Vec<(PathBuf, Bytes)> {
self.slots
.iter()
.filter_map(|slot| {
let path = slot.path.clone()?;
let font = slot.font.get()?.as_ref()?;
Some((path, font.data().clone()))
})
.collect()
}
}
pub struct FontsBuilder {
book: FontBook,
slots: Vec<FontSlot>,
#[cfg(not(target_arch = "wasm32"))]
seen: HashSet<PathBuf>,
}
impl FontsBuilder {
#[cfg(feature = "typst-fonts")]
pub fn typst_fonts(mut self) -> Self {
for data in typst_assets::fonts() {
self.push_data(Bytes::new(data));
}
self
}
pub fn data(mut self, data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self {
self.push_data(Bytes::new(data));
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn system_fonts(mut self) -> Self {
let mut database = fontdb::Database::new();
database.load_system_fonts();
self.push_database(&database);
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn path(mut self, path: impl AsRef<Path>) -> Self {
let path = path.as_ref();
let mut database = fontdb::Database::new();
if path.is_dir() {
database.load_fonts_dir(path);
} else if path.is_file() {
let _ = database.load_font_file(path);
}
self.push_database(&database);
self
}
pub fn build(self) -> Fonts {
Fonts {
book: LazyHash::new(self.book),
slots: self.slots,
}
}
fn push_data(&mut self, data: Bytes) {
for font in Font::iter(data) {
self.book.push(font.info().clone());
self.slots.push(FontSlot::loaded(font));
}
}
#[cfg(not(target_arch = "wasm32"))]
fn push_database(&mut self, database: &fontdb::Database) {
let files: Vec<_> = database
.faces()
.filter_map(|face| match &face.source {
fontdb::Source::File(path) | fontdb::Source::SharedFile(path, _) => self
.seen
.insert(path.clone())
.then(|| (face.id, path.clone())),
fontdb::Source::Binary(_) => None,
})
.collect();
for (id, path) in files {
database.with_face_data(id, |data, _| {
for (index, info) in typst::text::FontInfo::iter(data).enumerate() {
self.book.push(info);
self.slots.push(FontSlot {
path: Some(path.clone()),
index: index as u32,
font: OnceLock::new(),
});
}
});
}
}
}