summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src/fonts.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /crates/typst-cli/src/fonts.rs
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'crates/typst-cli/src/fonts.rs')
-rw-r--r--crates/typst-cli/src/fonts.rs183
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(),
+ });
+ }
+ }
+ }
+ }
+}