1use crate::engine::extension::CSS;
2use crate::shipout;
3use crate::shipout::html::{CompilationDisplay, ImageOptions};
4use crate::shipout::state::{FontData, Shipout, ShipoutNodeV, ShipoutWrapper, Top};
5use crate::utils::{VecMap, VecSet};
6use extension::RusTeXExtension;
7use fonts::Fontsystem;
8use nodes::RusTeXNode;
9use output::RusTeXOutput;
10use state::RusTeXState;
11use std::fmt::Display;
12use std::io::BufWriter;
13use std::path::{Path, PathBuf};
14use std::sync::Mutex;
15use stomach::RusTeXStomach;
16use tex_engine::commands::{Macro, TeXCommand};
17use tex_engine::engine::filesystem::FileSystem;
18use tex_engine::engine::filesystem::{File, SourceReference, VirtualFile};
19use tex_engine::engine::fontsystem::FontSystem;
20use tex_engine::engine::gullet::DefaultGullet;
21use tex_engine::engine::gullet::Gullet;
22use tex_engine::engine::mouth::DefaultMouth;
23use tex_engine::engine::mouth::Mouth;
24use tex_engine::engine::state::State as OrigState;
25use tex_engine::engine::stomach::Stomach as StomachT;
26use tex_engine::engine::utils::memory::MemoryManager;
27use tex_engine::engine::EngineExtension;
28use tex_engine::engine::TeXEngine;
29use tex_engine::engine::{DefaultEngine, EngineAux, EngineReferences, EngineTypes};
30use tex_engine::pdflatex::nodes::PDFColor;
31use tex_engine::pdflatex::PDFTeXEngine;
32use tex_engine::prelude::*;
33use tex_engine::tex;
34use tex_engine::tex::catcodes::AT_LETTER_SCHEME;
35use tex_engine::tex::numerics::{Dim32, Mu};
36use tex_engine::tex::tokens::CompactToken;
37use tex_engine::utils::errors::{ErrorThrower, TeXError, TeXResult};
38use tex_engine::utils::HMap;
39
40pub mod commands;
41pub(crate) mod extension;
42pub mod files;
43pub mod fonts;
44pub(crate) mod nodes;
45pub mod output;
46pub(crate) mod pgf;
47pub mod state;
48pub mod stomach;
49
50pub type Extension = RusTeXExtension;
51pub(crate) type Font = tex_engine::engine::fontsystem::TfmFont<i32, Dim32, InternedCSName<u8>>;
52pub(crate) type SRef = SourceReference<<<Types as EngineTypes>::File as File>::SourceRefID>;
53pub(crate) type Refs<'a, 'b> = &'a mut EngineReferences<'b, Types>;
54pub(crate) type CSName = InternedCSName<u8>;
55
56#[derive(Clone, Debug, Copy)]
57pub struct Types;
58
59pub type Res<R> = TeXResult<R, Types>;
60
61impl EngineTypes for Types {
62 type Char = u8;
63 type CSName = CSName;
64 type Token = CompactToken;
65 type Extension = Extension;
66 type File = VirtualFile<u8>;
67 type FileSystem = files::RusTeXFileSystem;
68 type Int = i32;
69 type Dim = Dim32;
70 type MuDim = Mu;
71 type Num = tex::numerics::DefaultNumSet;
72 type State = RusTeXState;
73 type Outputs = RusTeXOutput;
74 type Mouth = DefaultMouth<Self>;
75 type Gullet = DefaultGullet<Self>;
76 type Stomach = RusTeXStomach;
77 type CustomNode = RusTeXNode;
78 type Font = tex_engine::engine::fontsystem::TfmFont<i32, Dim32, InternedCSName<u8>>;
79 type FontSystem = Fontsystem;
80 type ErrorHandler = ErrorThrower<Self>;
81}
82
83thread_local! {
84 static MAIN_STATE : Mutex<Option<(RusTeXState,MemoryManager<CompactToken>)>> = const { Mutex::new(None) };
85 static FONT_SYSTEM : Mutex<Option<Fontsystem>> = const { Mutex::new(None) };
86}
87
88fn get_state(log: bool) -> (RusTeXState, MemoryManager<CompactToken>) {
89 MAIN_STATE.with(|state| {
90 let mut guard = state.lock().unwrap();
91 match &mut *guard {
92 Some((s, m)) => (s.clone(), m.clone()),
93 n => {
94 let mut engine = DefaultEngine::<Types>::default();
96 if log {
97 engine.aux.outputs = RusTeXOutput::Print(true);
98 }
99 commands::register_primitives_preinit(&mut engine);
100 engine.initialize_pdflatex().unwrap();
101 commands::register_primitives_postinit(&mut engine);
102 engine.init_file("rustex_defs.def").unwrap();
103 *n = Some((engine.state.clone(), engine.aux.memory.clone()));
104 FONT_SYSTEM.with(|f| f.lock().unwrap().replace(engine.fontsystem.clone()));
105 (engine.state, engine.aux.memory)
107 }
108 }
109 })
110}
111
112fn get_engine(log: bool) -> DefaultEngine<Types> {
113 let (mut state, mut memory) = get_state(log);
114 let fontsystem = FONT_SYSTEM.with(|f| f.lock().unwrap().clone()).unwrap();
115 let mut aux = EngineAux {
116 outputs: RusTeXOutput::None,
117 error_handler: ErrorThrower::new(),
118 start_time: chrono::Local::now(),
119 extension: Extension::new(&mut memory),
120 memory,
121 jobname: String::new(),
122 };
123 let mut mouth = DefaultMouth::new(&mut aux, &mut state);
124 let gullet = DefaultGullet::new(&mut aux, &mut state, &mut mouth);
125 let stomach = RusTeXStomach::new(&mut aux, &mut state);
126 DefaultEngine {
127 state,
128 aux,
129 fontsystem,
130 filesystem: files::RusTeXFileSystem::new(tex_engine::utils::PWD.to_path_buf()),
131 mouth,
132 gullet,
133 stomach,
134 }
135}
136
137pub struct CompilationResult {
138 out: Vec<ShipoutNodeV>,
139 pub error: Option<(TeXError<Types>, Vec<FileTrace>)>,
140 pub font_data: HMap<Box<str>, FontData>,
141 top_font: Font,
142 top_width: i32,
143 page_width: i32,
144 sourcerefs: bool,
145 metas: Vec<VecMap<String, String>>,
146 top: VecMap<String, String>,
147 img: ImageOptions,
148 css: VecSet<CSS>,
149 pub font_info: bool,
150}
151impl CompilationResult {
152 pub fn write_out(&self, path: &Path) -> std::io::Result<()> {
153 use std::io::Write;
154 let mut f = std::fs::File::create(path)?;
155 let mut f = BufWriter::new(&mut f);
156 write!(f, "{self}")?;
157 f.flush()
158 }
159}
160impl Display for CompilationResult {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 let mut dsp = CompilationDisplay {
163 color: PDFColor::default(),
164 font: self.top_font.clone(),
165 width: self.top_width,
166 indent: 0,
167 in_link: false,
168 attrs: VecMap::default(),
169 styles: VecMap::default(),
170 sourcerefs: self.sourcerefs,
171 font_data: &self.font_data,
172 image: &self.img,
173 font_info: self.font_info,
174 f,
175 };
176 dsp.display(
177 &self.metas,
178 &self.top,
179 &self.css.inner,
180 self.page_width,
181 &self.out,
182 )
183 }
184}
185
186pub trait RusTeXEngineT {
187 fn initialize(log: bool);
188 fn get() -> Self;
189 fn run<S: AsRef<str>>(&mut self, file: S, settings: Settings) -> CompilationResult;
190 fn do_file<S: AsRef<str>>(file: S, settings: Settings) -> CompilationResult;
191}
192
193pub(crate) fn register_command(
194 e: &mut DefaultEngine<Types>,
195 globally: bool,
196 name: &'static str,
197 sig: &'static str,
198 exp: &'static str,
199 protect: bool,
200 long: bool,
201) {
202 let e = e.get_engine_refs();
203 let name = e.aux.memory.cs_interner_mut().cs_from_str(name);
204 let mut cmd =
205 Macro::new::<_, _, Types>(e.aux.memory.cs_interner_mut(), &AT_LETTER_SCHEME, sig, exp)
206 .unwrap();
207 if protect {
208 cmd.protected = true
209 }
210 if long {
211 cmd.long = true
212 }
213 e.state
214 .set_command(e.aux, name, Some(TeXCommand::Macro(cmd)), globally)
215}
216
217#[derive(Default)]
218pub struct Settings {
219 pub sourcerefs: bool,
220 pub verbose: bool,
221 pub log: bool,
222 pub image_options: ImageOptions,
223 pub insert_font_info: bool,
224}
225
226pub type RusTeXEngine = DefaultEngine<Types>;
230
231mod sealed {
232 pub trait Sealed {}
233}
234impl sealed::Sealed for RusTeXEngine {}
235
236pub trait RusTeXEngineExt: sealed::Sealed {
237 fn run_string(&mut self, file: PathBuf, content: &str) -> Option<TeXError<Types>>;
238 fn do_result(
239 &mut self,
240 result: Option<TeXError<Types>>,
241 settings: Settings,
242 ) -> CompilationResult;
243}
244
245impl RusTeXEngineExt for RusTeXEngine {
246 fn run_string(&mut self, file: PathBuf, content: &str) -> Option<TeXError<Types>> {
247 let s = file.display().to_string();
248 self.filesystem
249 .set_pwd(file.parent().unwrap().to_path_buf());
250 self.filesystem.add_file(file, content);
251 let r = match self.do_file_pdf(&s, shipout::shipout) {
252 Ok(_) => None,
253 Err(e) => {
254 self.aux.outputs.errmessage(format!(
255 "{}\n\nat {}",
256 e,
257 self.mouth.current_sourceref().display(&self.filesystem)
258 ));
259 Some(e)
260 }
261 };
262 {
263 let mut s = std::mem::take(&mut self.aux.extension.state);
264 let mut refs = self.get_engine_refs();
265 let mut istate = Shipout {
266 nodes: std::mem::take(&mut s.output),
267 state: Top,
268 wrapper: std::mem::take(&mut s.wrapper),
269 previous: std::mem::take(&mut s.previous),
270 top_state: &mut s,
271 engine: &mut refs,
272 };
273 let _ = ShipoutWrapper::close_all(&mut istate);
274 let nodes = std::mem::take(&mut istate.nodes);
275 drop(istate);
276 drop(refs);
277 s.output = nodes;
278 self.aux.extension.state = s;
279 }
280 r
281 }
282 fn do_result(
283 &mut self,
284 result: Option<TeXError<Types>>,
285 settings: Settings,
286 ) -> CompilationResult {
287 let result = result.map(|e| {
288 let filetrace = self
289 .mouth
290 .file_trace()
291 .filter_map(|sr| {
292 let id = sr.file?;
293 let file = self.filesystem.ref_str(Some(id));
294 let file = self
295 .filesystem
296 .inner
297 .kpse
298 .pwd
299 .join(file)
300 .canonicalize()
301 .ok()?;
302 Some(FileTrace {
303 file,
304 line: sr.line as u32,
305 col: sr.column as u32,
306 })
307 })
308 .collect::<Vec<_>>();
309 (e, filetrace)
310 });
311 {
312 let mut s = std::mem::take(&mut self.aux.extension.state);
313 let mut refs = self.get_engine_refs();
314 let mut istate = Shipout {
315 nodes: std::mem::take(&mut s.output),
316 state: Top,
317 wrapper: std::mem::take(&mut s.wrapper),
318 previous: std::mem::take(&mut s.previous),
319 top_state: &mut s,
320 engine: &mut refs,
321 };
322 let _ = ShipoutWrapper::close_all(&mut istate);
323 let nodes = std::mem::take(&mut istate.nodes);
324 drop(istate);
325 drop(refs);
326 s.output = nodes;
327 self.aux.extension.state = s;
328 }
329 let out = std::mem::take(&mut self.aux.extension.state.output);
330 let css = std::mem::take(&mut self.aux.extension.css);
331 let font_data = std::mem::take(&mut self.aux.extension.state.font_data);
332 let top_font = self
333 .aux
334 .extension
335 .state
336 .top_font
337 .clone()
338 .unwrap_or_else(|| self.fontsystem.null());
339 let top_width = self
340 .aux
341 .extension
342 .state
343 .top_width
344 .clone()
345 .unwrap_or_default();
346 let page_width = self
347 .aux
348 .extension
349 .state
350 .page_width
351 .clone()
352 .unwrap_or_default();
353 let top = std::mem::take(&mut self.aux.extension.top);
354 let metas = std::mem::take(&mut self.aux.extension.metas);
355 CompilationResult {
356 out,
357 error: result,
358 css,
359 font_data,
360 top_font,
361 top_width,
362 top,
363 metas,
364 page_width,
365 sourcerefs: settings.sourcerefs,
366 font_info: settings.insert_font_info,
367 img: settings.image_options,
368 }
369 }
370}
371
372#[derive(Debug, Clone)]
373pub struct FileTrace {
374 pub file: PathBuf,
375 pub line: u32,
376 pub col: u32,
377}
378
379impl RusTeXEngineT for RusTeXEngine {
380 fn initialize(log: bool) {
381 let _ = get_engine(log);
382 }
383
384 fn get() -> Self {
385 get_engine(false)
386 }
387
388 fn run<S: AsRef<str>>(&mut self, file: S, settings: Settings) -> CompilationResult {
389 let res = match self.do_file_pdf(file.as_ref(), shipout::shipout) {
390 Ok(_) => None,
391 Err(e) => {
392 self.aux.outputs.errmessage(format!(
393 "{}\n\nat {}",
394 e,
395 self.mouth.current_sourceref().display(&self.filesystem)
396 ));
397 Some(e)
398 }
399 };
400 self.do_result(res, settings)
401 }
402
403 fn do_file<S: AsRef<str>>(file: S, settings: Settings) -> CompilationResult {
404 let mut engine = Self::get();
405 engine.stomach.continuous = true;
406 if settings.log {
407 engine.aux.outputs = RusTeXOutput::Print(settings.verbose);
408 }
409 let ret = <Self as RusTeXEngineT>::run(&mut engine, file, settings);
410 FONT_SYSTEM.with(|f| f.lock().unwrap().replace(engine.fontsystem));
411 ret
412 }
413}