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>, }
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 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 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}