flams_stex/
math.rs

1use ftml_ontology::utils::time::measure;
2use ftml_uris::ModuleUri;
3use rustex_lib::engine::RusTeXEngineExt;
4
5use crate::{rustex::EngineBase, RusTeX};
6
7static PREAMBLE: &str = r"
8\documentclass{stex}
9\usepackage[T1]{fontenc}
10\usepackage[utf8]{inputenc}
11\usepackage[hide]{ed}
12\usepackage[hyperref=auto,style=alphabetic,backend=bibtex]{biblatex}
13\usepackage{url,amstext,amsfonts,amsmath,bbm,amssymb,stix2,csquotes,listings}
14\lstset{columns=fullflexible,basicstyle=\ttfamily}
15\usepackage[hidelinks]{hyperref}
16\usepackage[dvipsnames]{xcolor}
17\usepackage{stex-highlighting,stexthm}
18";
19
20pub struct RusTeXMath {
21    engine: RusTeX,
22    modules: parking_lot::Mutex<String>, //preamble: std::sync::Mutex<String>,
23}
24impl std::fmt::Debug for RusTeXMath {
25    #[inline]
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.write_str("RusTeXMath")
28    }
29}
30
31static BASE: std::sync::LazyLock<RusTeX> = std::sync::LazyLock::new(|| {
32    RusTeX::initialize();
33    tracing::info_span!("initializing math engine").in_scope(move || {
34        tracing::info!("Getting RusTeX");
35        let mut engine = RusTeX::get().expect("wut").0.into_inner().into_engine();
36        tracing::info!("Compiling preamble");
37        engine.run_string(std::path::PathBuf::from("/rustex_math.tex"), PREAMBLE);
38        tracing::info!("Packaging");
39        let r = RusTeX(parking_lot::Mutex::new(EngineBase::from_engine(engine)));
40        tracing::info!("Done");
41        r
42    })
43});
44
45impl Default for RusTeXMath {
46    fn default() -> Self {
47        Self {
48            engine: RusTeX(parking_lot::Mutex::new(BASE.0.lock().clone())),
49            modules: parking_lot::Mutex::new(String::new()),
50        }
51    }
52}
53
54impl RusTeXMath {
55    #[inline]
56    pub fn initialize() {
57        std::sync::LazyLock::force(&BASE);
58    }
59
60    pub fn add_usemodule(&self, module: &ModuleUri) {
61        use std::fmt::Write;
62        let mut lock = self.modules.lock();
63        let Some(rs) = self
64            .engine
65            .builder()
66            .set_sourcerefs(false)
67            .set_font_debug_info(false)
68            .set_string_noaux(
69                std::path::Path::new("./rustex_math.tex"),
70                &format!(
71                    "\\begin{{document}}\\usemodule{}\\end{{document}}",
72                    module.short_id_string()
73                ),
74            )
75        else {
76            return;
77        };
78        let (_, res) = rs.run();
79        let _ = write!(lock, "\\usemodule{}", module.short_id_string());
80        res.memorize(&self.engine);
81        drop(lock);
82    }
83
84    /// ### Errors
85    pub fn run(&self, math: &str) -> Result<String, String> {
86        tracing::info_span!("running {}", math).in_scope(move || {
87            let rs = self
88                .engine
89                .builder()
90                .set_sourcerefs(false)
91                .set_font_debug_info(false);
92            let Some(rs) = rs.set_string_noaux(
93                std::path::Path::new("./rustex_math.tex"),
94                &format!(
95                    "\\begin{{document}}\n{}\n{math}\\end{{document}}",
96                    &*self.modules.lock()
97                ),
98            ) else {
99                return Err(format!("Could not add file with content \"{math}\""));
100            };
101            let (out, _) = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| rs.run()))
102            {
103                Ok(v) => v,
104                Err(e) => {
105                    return Err({
106                        e.downcast_ref::<&str>().map_or_else(
107                            || {
108                                e.downcast_ref::<String>()
109                                    .map_or_else(|| "Unknown error".to_string(), String::clone)
110                            },
111                            ToString::to_string,
112                        )
113                    })
114                }
115            };
116            if out.error.is_some() {
117                // SAFETY: we just checked is_some()
118                return unsafe { Err(out.error.unwrap_unchecked().0.to_string()) };
119            }
120            let html = out.to_string();
121            let Some(start) = html.find("<math") else {
122                return Err("No math node found".to_string());
123            };
124            let Some(end) = html.rfind("</math>") else {
125                return Err("No math node found".to_string());
126            };
127            Ok(html[start..end + "</math>".len()].to_string())
128        })
129    }
130}
131
132#[test]
133fn math_test() {
134    use ftml_ontology::utils::time::measure;
135
136    tracing_subscriber::fmt().init();
137    let (engine, t) = measure(RusTeXMath::default);
138    tracing::info!("Initialized after {t}");
139    let ((), t) = measure(|| {
140        engine.add_usemodule(
141            &"http://mathhub.info?a=smglom/arithmetics&p=mod&m=realarith"
142                .parse()
143                .expect("foo"),
144        );
145    });
146    tracing::info!("Added module in {t}");
147    let ((), t) = measure(|| {
148        engine.add_usemodule(
149            &"http://mathhub.info?a=smglom/arithmetics&p=mod&m=ratarith"
150                .parse()
151                .expect("foo"),
152        );
153    });
154    tracing::info!("Added redundant module in {t}");
155    let (out, t) = measure(|| {
156        engine
157            .run("$\\realabsval{\\realplus{a,b,c,d,e}}$")
158            .expect("works")
159    });
160    tracing::info!("Converted first math in {t} to:\n{out}");
161    let (out, t) = measure(|| {
162        engine
163            .run("$$\\realabsval{\\realplus{f,g,h}}$$")
164            .expect("works")
165    });
166    tracing::info!("Converted second math in {t} to:\n{out}");
167}