use std::fs;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use fontdb::{Database, Source};
use typst_library::foundations::Bytes;
use typst_library::text::{Font, FontBook, FontInfo};
use typst_timing::TimingScope;
#[derive(Debug)]
pub struct FontSlot {
path: Option<PathBuf>,
index: u32,
font: OnceLock<Option<Font>>,
}
impl FontSlot {
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
pub fn index(&self) -> u32 {
self.index
}
pub fn get(&self) -> Option<Font> {
self.font
.get_or_init(|| {
let _scope = TimingScope::new("load font");
let data = fs::read(
self.path
.as_ref()
.expect("`path` is not `None` if `font` is uninitialized"),
)
.ok()?;
Font::new(Bytes::new(data), self.index)
})
.clone()
}
}
#[derive(Debug)]
pub struct Fonts {
pub book: FontBook,
pub fonts: Vec<FontSlot>,
}
impl Fonts {
pub fn searcher() -> FontSearcher {
FontSearcher::new()
}
}
#[derive(Debug)]
pub struct FontSearcher {
db: Database,
include_system_fonts: bool,
#[cfg(feature = "embed-fonts")]
include_embedded_fonts: bool,
book: FontBook,
fonts: Vec<FontSlot>,
}
impl FontSearcher {
pub fn new() -> Self {
Self {
db: Database::new(),
include_system_fonts: true,
#[cfg(feature = "embed-fonts")]
include_embedded_fonts: true,
book: FontBook::new(),
fonts: vec![],
}
}
pub fn include_system_fonts(&mut self, value: bool) -> &mut Self {
self.include_system_fonts = value;
self
}
#[cfg(feature = "embed-fonts")]
pub fn include_embedded_fonts(&mut self, value: bool) -> &mut Self {
self.include_embedded_fonts = value;
self
}
pub fn search(&mut self) -> Fonts {
self.search_with::<_, &str>([])
}
pub fn search_with<I, P>(&mut self, font_dirs: I) -> Fonts
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
for path in font_dirs {
self.db.load_fonts_dir(path);
}
if self.include_system_fonts {
self.db.load_system_fonts();
}
for face in self.db.faces() {
let path = match &face.source {
Source::File(path) | Source::SharedFile(path, _) => path,
Source::Binary(_) => continue,
};
let info = self
.db
.with_face_data(face.id, FontInfo::new)
.expect("database must contain this font");
if let Some(info) = info {
self.book.push(info);
self.fonts.push(FontSlot {
path: Some(path.clone()),
index: face.index,
font: OnceLock::new(),
});
}
}
#[cfg(feature = "embed-fonts")]
if self.include_embedded_fonts {
self.add_embedded();
}
Fonts {
book: std::mem::take(&mut self.book),
fonts: std::mem::take(&mut self.fonts),
}
}
#[cfg(feature = "embed-fonts")]
fn add_embedded(&mut self) {
for data in typst_assets::fonts() {
let buffer = Bytes::new(data);
for (i, font) in Font::iter(buffer).enumerate() {
self.book.push(font.info().clone());
self.fonts.push(FontSlot {
path: None,
index: i as u32,
font: OnceLock::from(Some(font)),
});
}
}
}
}
impl Default for FontSearcher {
fn default() -> Self {
Self::new()
}
}