From b80382b216d79b56358002687ab288241873bb5b Mon Sep 17 00:00:00 2001 From: 7sDream <7822577+7sDream@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:38:08 +0800 Subject: [PATCH] Use fontdb to search fonts, with basic fontconfig support (#2472) --- Cargo.lock | 18 ++++-- crates/typst-cli/Cargo.toml | 3 +- crates/typst-cli/src/fonts.rs | 104 +++++++++++----------------------- crates/typst/src/font/book.rs | 11 ++-- 4 files changed, 54 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc06e3182..e271201c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -786,13 +786,24 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontconfig-parser" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4" +dependencies = [ + "roxmltree", +] + [[package]] name = "fontdb" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38" dependencies = [ + "fontconfig-parser", "log", + "memmap2", "slotmap", "tinyvec", "ttf-parser", @@ -1458,9 +1469,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" dependencies = [ "libc", ] @@ -2821,8 +2832,8 @@ dependencies = [ "env_proxy", "filetime", "flate2", + "fontdb", "inferno", - "memmap2", "notify", "once_cell", "open", @@ -2845,7 +2856,6 @@ dependencies = [ "typst", "typst-library", "ureq", - "walkdir", "xz2", "zip", ] diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 5df740296..250fa8e74 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -30,8 +30,8 @@ ecow = "0.2" dirs = "5" flate2 = "1" filetime = "0.2" +fontdb = "0.15.0" inferno = "0.11.15" -memmap2 = "0.7" notify = "6" once_cell = "1" open = "5" @@ -54,7 +54,6 @@ ureq = "2" rustls = "0.21" rustls-pemfile = "1" env_proxy = "0.4" -walkdir = "2" xz2 = { version = "0.1", optional = true } zip = { version = "0.6", optional = true } diff --git a/crates/typst-cli/src/fonts.rs b/crates/typst-cli/src/fonts.rs index 9eeb15e22..7c7857167 100644 --- a/crates/typst-cli/src/fonts.rs +++ b/crates/typst-cli/src/fonts.rs @@ -1,12 +1,10 @@ use std::cell::OnceCell; -use std::env; -use std::fs::{self, File}; -use std::path::{Path, PathBuf}; +use std::fs; +use std::path::PathBuf; -use memmap2::Mmap; +use fontdb::{Database, Source}; use typst::diag::StrResult; use typst::font::{Font, FontBook, FontInfo, FontVariant}; -use walkdir::WalkDir; use crate::args::FontsCommand; @@ -67,12 +65,39 @@ impl FontSearcher { /// Search everything that is available. pub fn search(&mut self, font_paths: &[PathBuf]) { + let mut db = Database::new(); + + // Font paths have highest priority. for path in font_paths { - self.search_dir(path) + db.load_fonts_dir(path); } - self.search_system(); + // System fonts have second priority. + db.load_system_fonts(); + for face in db.faces() { + let path = match &face.source { + Source::File(path) | Source::SharedFile(path, _) => path, + // We never add binary sources to the database, so there + // shouln't be any. + Source::Binary(_) => continue, + }; + + let info = 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: path.clone(), + index: face.index, + font: OnceCell::new(), + }); + } + } + + // Embedded fonts have lowest priority. #[cfg(feature = "embed-fonts")] self.add_embedded(); } @@ -114,69 +139,4 @@ impl FontSearcher { 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) { - 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(), - }); - } - } - } - } } diff --git a/crates/typst/src/font/book.rs b/crates/typst/src/font/book.rs index 38b9b4217..36d0107a8 100644 --- a/crates/typst/src/font/book.rs +++ b/crates/typst/src/font/book.rs @@ -191,13 +191,16 @@ bitflags::bitflags! { } impl FontInfo { + /// Compute metadata for font at the `index` of the given data. + pub fn new(data: &[u8], index: u32) -> Option { + let ttf = ttf_parser::Face::parse(data, index).ok()?; + Self::from_ttf(&ttf) + } + /// Compute metadata for all fonts in the given data. pub fn iter(data: &[u8]) -> impl Iterator + '_ { let count = ttf_parser::fonts_in_collection(data).unwrap_or(1); - (0..count).filter_map(move |index| { - let ttf = ttf_parser::Face::parse(data, index).ok()?; - Self::from_ttf(&ttf) - }) + (0..count).filter_map(move |index| Self::new(data, index)) } /// Compute metadata for a single ttf-parser face.