diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /crates/typst-cli/src/fonts.rs | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'crates/typst-cli/src/fonts.rs')
| -rw-r--r-- | crates/typst-cli/src/fonts.rs | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/crates/typst-cli/src/fonts.rs b/crates/typst-cli/src/fonts.rs new file mode 100644 index 00000000..d8209fee --- /dev/null +++ b/crates/typst-cli/src/fonts.rs @@ -0,0 +1,183 @@ +use std::cell::OnceCell; +use std::env; +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; + +use memmap2::Mmap; +use typst::diag::StrResult; +use typst::font::{Font, FontBook, FontInfo, FontVariant}; +use typst::util::Bytes; +use walkdir::WalkDir; + +use crate::args::FontsCommand; + +/// Execute a font listing command. +pub fn fonts(command: FontsCommand) -> StrResult<()> { + let mut searcher = FontSearcher::new(); + searcher.search(&command.font_paths); + + for (name, infos) in searcher.book.families() { + println!("{name}"); + if command.variants { + for info in infos { + let FontVariant { style, weight, stretch } = info.variant; + println!("- Style: {style:?}, Weight: {weight:?}, Stretch: {stretch:?}"); + } + } + } + + Ok(()) +} + +/// Searches for fonts. +pub struct FontSearcher { + /// Metadata about all discovered fonts. + pub book: FontBook, + /// Slots that the fonts are loaded into. + pub fonts: Vec<FontSlot>, +} + +/// Holds details about the location of a font and lazily the font itself. +pub struct FontSlot { + /// The path at which the font can be found on the system. + path: PathBuf, + /// The index of the font in its collection. Zero if the path does not point + /// to a collection. + index: u32, + /// The lazily loaded font. + font: OnceCell<Option<Font>>, +} + +impl FontSlot { + /// Get the font for this slot. + pub fn get(&self) -> Option<Font> { + self.font + .get_or_init(|| { + let data = fs::read(&self.path).ok()?.into(); + Font::new(data, self.index) + }) + .clone() + } +} + +impl FontSearcher { + /// Create a new, empty system searcher. + pub fn new() -> Self { + Self { book: FontBook::new(), fonts: vec![] } + } + + /// Search everything that is available. + pub fn search(&mut self, font_paths: &[PathBuf]) { + self.search_system(); + + #[cfg(feature = "embed-fonts")] + self.add_embedded(); + + for path in font_paths { + self.search_dir(path) + } + } + + /// Add fonts that are embedded in the binary. + #[cfg(feature = "embed-fonts")] + fn add_embedded(&mut self) { + let mut process = |bytes: &'static [u8]| { + let buffer = Bytes::from_static(bytes); + for (i, font) in Font::iter(buffer).enumerate() { + self.book.push(font.info().clone()); + self.fonts.push(FontSlot { + path: PathBuf::new(), + index: i as u32, + font: OnceCell::from(Some(font)), + }); + } + }; + + macro_rules! add { + ($filename:literal) => { + process(include_bytes!(concat!("../../../assets/fonts/", $filename))); + }; + } + + // Embed default fonts. + add!("LinLibertine_R.ttf"); + add!("LinLibertine_RB.ttf"); + add!("LinLibertine_RBI.ttf"); + add!("LinLibertine_RI.ttf"); + add!("NewCMMath-Book.otf"); + add!("NewCMMath-Regular.otf"); + add!("NewCM10-Regular.otf"); + add!("NewCM10-Bold.otf"); + add!("NewCM10-Italic.otf"); + add!("NewCM10-BoldItalic.otf"); + add!("DejaVuSansMono.ttf"); + add!("DejaVuSansMono-Bold.ttf"); + add!("DejaVuSansMono-Oblique.ttf"); + add!("DejaVuSansMono-BoldOblique.ttf"); + } + + /// Search for fonts in the linux system font directories. + fn search_system(&mut self) { + if cfg!(target_os = "macos") { + self.search_dir("/Library/Fonts"); + self.search_dir("/Network/Library/Fonts"); + self.search_dir("/System/Library/Fonts"); + } else if cfg!(unix) { + self.search_dir("/usr/share/fonts"); + self.search_dir("/usr/local/share/fonts"); + } else if cfg!(windows) { + self.search_dir( + env::var_os("WINDIR") + .map(PathBuf::from) + .unwrap_or_else(|| "C:\\Windows".into()) + .join("Fonts"), + ); + + if let Some(roaming) = dirs::config_dir() { + self.search_dir(roaming.join("Microsoft\\Windows\\Fonts")); + } + + if let Some(local) = dirs::cache_dir() { + self.search_dir(local.join("Microsoft\\Windows\\Fonts")); + } + } + + if let Some(dir) = dirs::font_dir() { + self.search_dir(dir); + } + } + + /// Search for all fonts in a directory recursively. + fn search_dir(&mut self, path: impl AsRef<Path>) { + for entry in WalkDir::new(path) + .follow_links(true) + .sort_by(|a, b| a.file_name().cmp(b.file_name())) + .into_iter() + .filter_map(|e| e.ok()) + { + let path = entry.path(); + if matches!( + path.extension().and_then(|s| s.to_str()), + Some("ttf" | "otf" | "TTF" | "OTF" | "ttc" | "otc" | "TTC" | "OTC"), + ) { + self.search_file(path); + } + } + } + + /// Index the fonts in the file at the given path. + fn search_file(&mut self, path: &Path) { + if let Ok(file) = File::open(path) { + if let Ok(mmap) = unsafe { Mmap::map(&file) } { + for (i, info) in FontInfo::iter(&mmap).enumerate() { + self.book.push(info); + self.fonts.push(FontSlot { + path: path.into(), + index: i as u32, + font: OnceCell::new(), + }); + } + } + } + } +} |
