Files
pdfcraft/crates/engine/src/text_fonts.rs
T
a047c6ba09 chore: resolve SonarCloud maintainability issues (#4356)
* chore: resolve SonarCloud maintainability issues

* fix(packaging): use verify_sha256 helper function

* style: cargo fmt

* fix: restore wildcard imports and fix method-reference type errors

Revert the glob-import expansions (they missed SomIndices and left unused
imports), revert char::is_ascii_digit where the iterator yields char by
value, and pass the content slice to prune_doomed_xobjects as &[Vec<u8>].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep XObject decodability check without binding content in interp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-23 23:28:18 -04:00

79 lines
3.2 KiB
Rust

//! Explicit document-font discovery. Interface fallbacks never enter this catalogue.
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
pub struct FontFile {
pub path: PathBuf,
pub family: String,
pub face: String,
}
/// Standard user-installed font folders; discovery is performed only on request.
pub fn installed_directories() -> Vec<PathBuf> {
let home = std::env::var_os("HOME").map(PathBuf::from);
if cfg!(target_os = "macos") {
let mut dirs = vec![PathBuf::from("/Library/Fonts")];
if let Some(home) = home {
dirs.push(home.join("Library/Fonts"));
}
dirs
} else if cfg!(windows) {
let mut dirs = std::env::var_os("WINDIR").map(|p| PathBuf::from(p).join("Fonts")).into_iter().collect::<Vec<_>>();
if let Some(local) = std::env::var_os("LOCALAPPDATA") {
dirs.push(PathBuf::from(local).join("Microsoft/Windows/Fonts"));
}
dirs
} else {
let mut dirs = vec![PathBuf::from("/usr/share/fonts"), PathBuf::from("/usr/local/share/fonts")];
if let Some(home) = home {
dirs.extend([home.join(".local/share/fonts"), home.join(".fonts")]);
}
dirs
}
}
/// Bounded, symlink-free scan. Only standalone, openly licensed, embeddable faces are listed.
/// The caller controls roots (automation resolves confinement before calling this).
pub fn discover(roots: &[PathBuf]) -> Vec<FontFile> {
use std::io::Read;
let mut pending: Vec<_> = roots.iter().take(16).map(|r| (r.clone(), 0usize)).collect();
let mut files = Vec::new();
let mut entries = 0usize;
let mut budget = 64u64 << 20;
let mut seen = std::collections::HashSet::new();
while let Some((path, depth)) = pending.pop() {
if entries >= 4096 || budget == 0 {
break;
}
entries += 1;
let Ok(meta) = std::fs::symlink_metadata(&path) else { continue };
if meta.file_type().is_symlink() || !seen.insert(path.clone()) {
continue;
}
if meta.is_dir() && depth < 4 {
if let Ok(children) = std::fs::read_dir(&path) {
let remaining = 4096usize.saturating_sub(entries.saturating_add(pending.len()));
pending.extend(children.take(remaining).filter_map(Result::ok).map(|p| (p.path(), depth + 1)));
}
} else if meta.is_file() && meta.len() <= (16 << 20) && font_extension(&path) {
if meta.len() > budget {
continue;
}
budget = budget.saturating_sub(meta.len());
let mut bytes = Vec::new();
let read = std::fs::File::open(&path).and_then(|f| f.take((16 << 20) + 1).read_to_end(&mut bytes));
if read.is_err() || pdfcraft_fonts::embedding::validate_document_input(&bytes).is_err() {
continue;
}
if let Ok((family, face)) = pdfcraft_fonts::embedding::names(&bytes) {
files.push(FontFile { path, family, face });
}
}
}
files.sort_by(|a, b| a.face.cmp(&b.face).then(a.path.cmp(&b.path)));
files
}
fn font_extension(path: &Path) -> bool {
path.extension().and_then(std::ffi::OsStr::to_str).is_some_and(|e| e.eq_ignore_ascii_case("ttf") || e.eq_ignore_ascii_case("otf"))
}