Skip to main content

pack_native_format/
pack_native_format.rs

1//! Builds the native packaged LaTeX format used by Notatus.
2
3use std::path::{Path, PathBuf};
4use std::process::ExitCode;
5use std::sync::Arc;
6
7use mathtex_engine::font::{
8    FontData, FontError, FontQuery, FontSystem, RustybuzzFontSystem, ShapeRequest, ShapedText,
9};
10use mathtex_engine::portable_engine as pe;
11use mathtex_engine::{
12    GeneratedFontSystemAdapter, GeneratedFormatCache, GeneratedResourceProvider,
13    ProviderResourceRequest as ResourceRequest, Resource, ResourceError, ResourceFontSystem,
14    ResourceKind, ResourceProvider, TexmfResources,
15};
16
17const TEXMF_ROOT_ENV: &str = "MATHTEX_TEXMF_ROOT";
18const TEXMF_ROOT_CANDIDATES: &[&str] = &[
19    "/usr/local/texlive/2026/texmf-dist",
20    "/usr/local/texlive/2025/texmf-dist",
21    "/Library/TeX/texmf-dist",
22];
23const PREAMBLE: &[u8] = br"\nonstopmode\documentclass{article}\usepackage{amsmath}\usepackage{unicode-math}\setmathfont{latinmodern-math.otf}\def\hostbox#1{\Uhostbox #1\relax}\begin{document}\dump";
24
25struct TexmfFontProvider {
26    texmf: Arc<TexmfResources>,
27}
28
29impl TexmfFontProvider {
30    fn font_name(spec: &str) -> String {
31        let spec = spec.trim();
32        if let Some(rest) = spec.strip_prefix('[') {
33            return rest.split(']').next().unwrap_or(rest).trim().to_string();
34        }
35        spec.split([':', '/'])
36            .next()
37            .unwrap_or(spec)
38            .trim()
39            .to_string()
40    }
41}
42
43impl ResourceProvider for TexmfFontProvider {
44    fn read_request(&self, request: &ResourceRequest) -> Result<Resource, ResourceError> {
45        let name = Self::font_name(&request.canonical_name());
46        self.texmf.read(&name, ResourceKind::Font)
47    }
48}
49
50struct NativeFontSystem {
51    inner: RustybuzzFontSystem<ResourceFontSystem<TexmfFontProvider>>,
52}
53
54impl NativeFontSystem {
55    fn new(texmf: Arc<TexmfResources>) -> Self {
56        Self {
57            inner: RustybuzzFontSystem::new(ResourceFontSystem::new(TexmfFontProvider { texmf })),
58        }
59    }
60}
61
62impl FontSystem for NativeFontSystem {
63    fn load_font(&self, query: &FontQuery) -> Result<FontData, FontError> {
64        self.inner.load_font(query)
65    }
66
67    fn shape_text(&self, request: &ShapeRequest<'_>) -> Result<ShapedText, FontError> {
68        self.inner.shape_text(request)
69    }
70}
71
72fn main() -> ExitCode {
73    let Some(output) = output_path() else {
74        eprintln!("usage: pack_native_format <output path>");
75        return ExitCode::FAILURE;
76    };
77    let Some(texmf_root) = find_texmf_root() else {
78        eprintln!("cannot find a texmf-dist ls-R index");
79        return ExitCode::FAILURE;
80    };
81    let Some(texmf) = TexmfResources::from_root(texmf_root) else {
82        eprintln!("cannot load the texmf-dist ls-R index");
83        return ExitCode::FAILURE;
84    };
85    let texmf = Arc::new(texmf);
86
87    let latex = match texmf.read("latex.ltx", ResourceKind::TexInput) {
88        Ok(resource) => resource.bytes,
89        Err(error) => {
90            eprintln!("cannot read latex.ltx: {error:?}");
91            return ExitCode::FAILURE;
92        }
93    };
94
95    let cache = GeneratedFormatCache::initialized(pe::EngineProfile::xetex());
96    let mut engine = cache
97        .instantiate(
98            pe::EngineProfile::xetex(),
99            GeneratedResourceProvider::new(&*texmf),
100        )
101        .with_font_platform(GeneratedFontSystemAdapter::new(NativeFontSystem::new(
102            texmf.clone(),
103        )));
104
105    if !engine.begin_primary_input("latex.ltx", latex) {
106        eprintln!("failed to begin latex.ltx");
107        return ExitCode::FAILURE;
108    }
109    engine.run_format_initialization();
110    if !engine.begin_primary_input("preamble.tex", PREAMBLE.to_vec()) {
111        eprintln!("failed to begin preamble");
112        return ExitCode::FAILURE;
113    }
114    engine.run_format_initialization();
115
116    engine.finalize_trie();
117    let format_bytes = GeneratedFormatCache::from_engine(&engine).to_bytes();
118    let font_table = engine.native_font_table();
119    let packaged = mathtex_engine::generated::pack_packaged_format(&format_bytes, &font_table);
120    if let Err(error) = std::fs::write(&output, packaged) {
121        eprintln!("cannot write {}: {error}", output.display());
122        return ExitCode::FAILURE;
123    }
124    ExitCode::SUCCESS
125}
126
127fn output_path() -> Option<PathBuf> {
128    let mut args = std::env::args_os().skip(1);
129    let output = PathBuf::from(args.next()?);
130    if args.next().is_some() {
131        return None;
132    }
133    Some(output)
134}
135
136fn find_texmf_root() -> Option<PathBuf> {
137    if let Ok(root) = std::env::var(TEXMF_ROOT_ENV) {
138        let root = PathBuf::from(root);
139        if root.join("ls-R").is_file() {
140            return Some(root);
141        }
142    }
143
144    TEXMF_ROOT_CANDIDATES
145        .iter()
146        .map(Path::new)
147        .find(|root| root.join("ls-R").is_file())
148        .map(Path::to_path_buf)
149}