Skip to main content

retex_engine/
lib.rs

1//! The retex TeX engine: a clean-room implementation of LuaTeX's
2//! typesetting machine.
3//!
4//! The [`Engine`] holds all state of a TeX run. Its methods are spread
5//! over modules following the parts of *TeX: The Program*:
6//!
7//! * [`print`] and [`error`] — terminal and log output, error recovery;
8//! * [`hash`], [`eqtb`], [`save`] — control sequences, equivalents, grouping;
9//! * [`input`] — the input stack and the tokenizer (`get_next`);
10//! * [`expand`], [`cond`], [`scan`] — expansion, conditionals, scanning;
11//! * [`prefixed`] — assignments;
12//! * [`maincontrol`] — the chief executive.
13
14#![forbid(unsafe_code)]
15#![allow(clippy::too_many_lines)]
16
17pub mod align;
18pub mod backend;
19mod boxnote;
20pub mod build;
21pub mod clock;
22pub mod cmd;
23mod cmd_gen;
24pub mod codes;
25pub mod cond;
26pub mod describe;
27pub mod diag;
28pub mod dirs;
29pub mod display;
30pub mod dvi;
31pub mod eqtb;
32pub mod error;
33pub mod expand;
34pub mod expansion;
35pub mod files;
36pub mod fingerprint;
37pub mod fonts;
38pub mod format;
39pub mod glue;
40pub mod hash;
41pub mod hmode;
42pub mod hyph;
43pub mod image;
44pub mod init;
45pub mod input;
46pub mod linebreak;
47pub mod maincontrol;
48pub mod math;
49pub mod mathdisplay;
50pub mod mathparams;
51pub mod meaning;
52pub mod misc;
53pub mod mlist;
54pub mod native;
55pub mod nodeproc;
56pub mod nodes;
57pub mod pack;
58pub mod page;
59pub mod pagenote;
60pub mod pdfext;
61pub(crate) mod pdfship;
62pub mod prefixed;
63pub mod primitives;
64pub mod primitives_gen;
65pub mod print;
66pub mod profile;
67pub mod protrusion;
68pub mod save;
69pub mod scan;
70pub mod shipout;
71pub mod showbox;
72pub mod snapshot;
73pub mod synctex;
74pub mod the;
75pub mod timing;
76pub mod token;
77pub mod typeset;
78pub mod utf8x;
79
80use cmd::Cmd;
81use cond::CondState;
82use eqtb::Eqtb;
83use error::ErrorState;
84use files::FileState;
85use hash::{CsId, CsTable};
86use input::InputState;
87use print::Printer;
88use save::SaveStack;
89use token::Token;
90
91/// Callbacks from the engine into an embedded Lua interpreter.
92///
93/// The engine does not depend on Lua. `\directlua` and related primitives
94/// call this trait, which `retex-lua` implements. The host is shared
95/// (`Rc`), so nested calls (Lua → TeX → Lua) reach it too.
96pub trait LuaHost {
97    /// Runs a chunk of Lua code for `\directlua` (or `\latelua`, etc.).
98    /// `name` is the chunk name. Text printed with `tex.print` must be
99    /// pushed back into the engine's input by the host.
100    fn run(&self, engine: &mut Engine, name: &str, code: &[u8]) -> Result<(), String>;
101
102    /// Calls the registered Lua function `n` (`\luafunction n`) with its
103    /// number, like [`run`](Self::run) runs a chunk; `None` if there is
104    /// no such function.
105    fn call_function(&self, _engine: &mut Engine, _n: i32) -> Option<Result<(), String>> {
106        None
107    }
108
109    /// Sets entries of expl3's intarrays (l3intarray under LuaTeX keeps
110    /// them in Lua tables), as `\__intarray_gset:w` does: for each
111    /// `(array, first, values)`, entry `first + k` of the array numbered
112    /// `array` (`\__intarray:w <array>`) gets `values[k]`. `gset` is the
113    /// `\luafunction` number of `\__intarray_gset:w`, through which the
114    /// arrays are found. `false`, with nothing changed, when the arrays
115    /// are not reachable natively.
116    fn intarray_set(&self, _gset: i32, _writes: &[(i64, i64, &[i64])]) -> bool {
117        false
118    }
119
120    /// Data to store in the format file (e.g. `lua.bytecode`).
121    fn dump(&self, _engine: &mut Engine) -> Vec<u8> {
122        Vec::new()
123    }
124
125    /// Restores data stored by [`dump`](Self::dump).
126    fn undump(&self, _engine: &mut Engine, _data: &[u8]) -> Result<(), String> {
127        Ok(())
128    }
129
130    /// Calls callback `name` without arguments, ignoring its results
131    /// (`pre_dump`, `stop_run`, …). Does nothing if none is registered.
132    fn call_simple(&self, _engine: &mut Engine, _name: &str) -> Result<(), String> {
133        Ok(())
134    }
135
136    /// Calls callback `name` with one string argument, ignoring its
137    /// results (`buildpage_filter`, `contribute_filter`).
138    fn call_with_string(
139        &self,
140        _engine: &mut Engine,
141        _name: &str,
142        _arg: &str,
143    ) -> Result<(), String> {
144        Ok(())
145    }
146
147    /// Whether a Lua function is registered for callback `name`.
148    fn has_callback(&self, _name: &str) -> bool {
149        false
150    }
151
152    /// Calls the `define_font` callback. Returns the id of the font it
153    /// defined, or `None` if the font could not be loaded.
154    fn define_font(&self, _engine: &mut Engine, _name: &[u8], _size: i32) -> Option<u32> {
155        None
156    }
157
158    /// Calls a node-list filter callback (`pre_linebreak_filter`,
159    /// `hpack_filter`, `post_linebreak_filter`, …) on the list starting at
160    /// `head`. Returns the new head; [`ListFilter::Flush`] means the list
161    /// must be discarded.
162    fn list_filter(
163        &self,
164        _engine: &mut Engine,
165        _callback: &str,
166        head: nodes::NodeRef,
167        _group: &str,
168    ) -> ListFilter {
169        ListFilter::Keep(head)
170    }
171
172    /// Calls the `hpack_quality` or `vpack_quality` callback `cb` for box
173    /// `b` with `incident` (overfull, underfull, loose, tight) and its
174    /// detail; returns the node the callback gave (null if none).
175    #[allow(clippy::too_many_arguments)]
176    fn pack_quality(
177        &self,
178        _engine: &mut Engine,
179        _cb: &str,
180        _incident: &str,
181        _detail: i32,
182        _b: nodes::NodeRef,
183        _first: i32,
184        _last: i32,
185    ) -> nodes::NodeRef {
186        nodes::NodeRef::NULL
187    }
188
189    /// Runs the `hyphenate`, `ligaturing` or `kerning` callback `cb` on
190    /// the list after `head` (a temporary node), with its last node:
191    /// `true` if a function or `false` is registered, which replaces the
192    /// built-in pass.
193    fn node_pass(&self, _engine: &mut Engine, _cb: &str, _head: nodes::NodeRef) -> bool {
194        false
195    }
196
197    /// Calls the `linebreak_filter` callback on paragraph `head`
198    /// (`display`: a display follows): `Some(list)` if it returned a node
199    /// list, which replaces line breaking; `None` otherwise.
200    fn linebreak_filter(
201        &self,
202        _engine: &mut Engine,
203        _head: nodes::NodeRef,
204        _display: bool,
205    ) -> Option<nodes::NodeRef> {
206        None
207    }
208
209    /// Calls the `append_to_vlist_filter` callback for box `b`, about to be
210    /// appended to a vertical list at `location` (`box`, `post_linebreak`,
211    /// `equation`, `equation_number`, `alignment`) after `prev_depth`:
212    /// `None` if none is set; else the list to append instead (null for
213    /// none) and the new `\prevdepth`, if it gave one.
214    fn append_to_vlist_filter(
215        &self,
216        _engine: &mut Engine,
217        _b: nodes::NodeRef,
218        _location: &str,
219        _prev_depth: i32,
220    ) -> Option<(nodes::NodeRef, Option<i32>)> {
221        None
222    }
223
224    /// Calls the `mlist_to_hlist` callback on math list `head` in style
225    /// `style` (0–7); `None` if the callback failed or none is set.
226    fn mlist_to_hlist(
227        &self,
228        _engine: &mut Engine,
229        _head: nodes::NodeRef,
230        _style: i32,
231        _penalties: bool,
232    ) -> Option<nodes::NodeRef> {
233        None
234    }
235
236    /// Runs shell command `cmd` under the shell escape rules (as Lua's
237    /// `io.popen` does) and returns its output; `None` if it may not run.
238    fn pipe_read(&self, _engine: &mut Engine, _cmd: &[u8]) -> Option<Vec<u8>> {
239        None
240    }
241
242    /// Calls the `input_level_string` callback with input-stack depth `n`;
243    /// `None` if it returned no string.
244    fn input_level_string(&self, _engine: &mut Engine, _n: i64) -> Option<Vec<u8>> {
245        None
246    }
247
248    /// Calls the `glyph_info` callback for glyph node `p`: `Some(text)` is
249    /// what to print, `None` means the callback returned no string.
250    fn glyph_info(&self, _engine: &mut Engine, _p: nodes::NodeRef) -> Option<Vec<u8>> {
251        None
252    }
253
254    /// Runs the Lua value kept under reference number `reference` (see
255    /// [`nodes::LuaRefs`]) at shipout: a function is called with `args`,
256    /// a string is run as Lua code. For the `lua` commands of virtual
257    /// characters and `\latelua` functions.
258    fn call_reference(
259        &self,
260        _engine: &mut Engine,
261        _reference: i32,
262        _args: ReferenceArgs,
263    ) -> Result<(), String> {
264        Ok(())
265    }
266}
267
268/// The arguments of a Lua function called at shipout (see
269/// [`LuaHost::call_reference`]).
270#[derive(Clone, Copy, Debug, PartialEq, Eq)]
271pub enum ReferenceArgs {
272    /// A virtual character's `lua` command: the font id and the
273    /// character.
274    Glyph(u32, u32),
275    /// A `\latelua` function: the whatsit (as a node userdata).
276    Node(nodes::NodeRef),
277}
278
279/// Result of a node-list filter callback.
280#[derive(Clone, Copy, Debug, PartialEq, Eq)]
281pub enum ListFilter {
282    /// Continue with this list (possibly the original head).
283    Keep(nodes::NodeRef),
284    /// The callback returned `false`: discard the list.
285    Flush,
286}
287
288impl Engine {
289    /// Whether the Lua callback `name` is set.
290    pub fn has_callback(&self, name: &str) -> bool {
291        self.lua.as_ref().is_some_and(|h| h.has_callback(name))
292    }
293
294    /// Calls the notification callback `name` (`buildpage_filter`,
295    /// `contribute_filter`) with `info`, if one is set.
296    pub fn notify_callback(&mut self, name: &str, info: &str) {
297        if let Some(h) = self.lua.clone()
298            && h.has_callback(name)
299            && let Err(e) = h.call_with_string(self, name, info)
300        {
301            self.lua_error(&e);
302        }
303    }
304
305    /// Calls `buildpage_filter` with `info` (where the page builder was
306    /// called from, observed), then builds the page.
307    pub fn build_page_from(&mut self, info: &str) {
308        self.notify_callback("buildpage_filter", info);
309        self.build_page();
310    }
311
312    /// Runs a node-list filter callback if one is registered (the hook
313    /// typesetting code calls at LuaTeX's callback points).
314    pub fn run_list_filter(
315        &mut self,
316        callback: &str,
317        head: nodes::NodeRef,
318        group: &str,
319    ) -> ListFilter {
320        match self.lua.clone() {
321            Some(h) if h.has_callback(callback) => h.list_filter(self, callback, head, group),
322            _ => ListFilter::Keep(head),
323        }
324    }
325}
326
327impl Engine {
328    /// Converts a finished formula's math list into an hlist, through the
329    /// `mlist_to_hlist` callback when one is set (LuaTeX calls it for the
330    /// outermost list of each formula and equation number; observed: not
331    /// for an empty one).
332    pub fn formula_to_hlist(
333        &mut self,
334        head: nodes::NodeRef,
335        style: i32,
336        penalties: bool,
337    ) -> nodes::NodeRef {
338        if head.is_null() {
339            return nodes::NodeRef::NULL;
340        }
341        if let Some(h) = self.lua.clone()
342            && h.has_callback("mlist_to_hlist")
343        {
344            if let Some(r) = h.mlist_to_hlist(self, head, style, penalties) {
345                if !r.is_null() {
346                    self.nodes.set_prev(r, nodes::NodeRef::NULL);
347                }
348                return r;
349            }
350            return nodes::NodeRef::NULL;
351        }
352        self.mlist_to_hlist(head, style, penalties)
353    }
354
355    /// Prints the input level before a line of macro tracing through the
356    /// `input_level_string` callback (LaTeX's `\tracingstacklevels`);
357    /// returns false if no callback is set. A newline in the string ends
358    /// the line.
359    pub fn print_input_level(&mut self) -> bool {
360        let Some(h) = self.lua.clone() else {
361            return false;
362        };
363        if !h.has_callback("input_level_string") {
364            return false;
365        }
366        let n = self.input.stack.len().saturating_sub(1) as i64;
367        if let Some(s) = h.input_level_string(self, n) {
368            for (i, part) in s.split(|&b| b == b'\n').enumerate() {
369                if i > 0 {
370                    self.printer.print_ln();
371                }
372                self.printer.print_bytes(part);
373            }
374        }
375        true
376    }
377
378    /// Runs the `glyph_info` callback for glyph `p` when one is set:
379    /// `None` without a callback, `Some(None)` when it returned no string.
380    pub fn run_glyph_info(&mut self, p: nodes::NodeRef) -> Option<Option<Vec<u8>>> {
381        match self.lua.clone() {
382            Some(h) if h.has_callback("glyph_info") => Some(h.glyph_info(self, p)),
383            _ => None,
384        }
385    }
386}
387
388/// A one-shot callback into the host, run with the engine borrowed.
389pub type EngineHook = Box<dyn FnOnce(&mut Engine)>;
390
391/// Shows diagnostics before the terminal is read (see
392/// [`Engine::prompt_hook`]).
393pub type PromptHook = Box<dyn FnMut(&[diag::Diagnostic], &str) -> bool>;
394
395/// The global state of a TeX run.
396pub struct Engine {
397    /// Control-sequence names.
398    pub hash: CsTable,
399    /// The table of equivalents.
400    pub eqtb: Eqtb,
401    /// The save stack and grouping state.
402    pub save: SaveStack,
403    /// The input stack.
404    pub input: InputState,
405    /// Terminal, log and `\write` output.
406    pub printer: Printer,
407    /// Error-handling state.
408    pub err: ErrorState,
409    /// Conditionals.
410    pub cond: CondState,
411    /// Files: `\openin`/`\read`, `\openout`, input file stack.
412    pub files: FileState,
413    /// The file finder.
414    pub kpse: Option<retex_kpath::Shared>,
415    /// The Lua interpreter, when available.
416    pub lua: Option<std::rc::Rc<dyn LuaHost>>,
417
418    /// The current command code (`cur_cmd`).
419    pub cur_cmd: Cmd,
420    /// The current modifier (`cur_chr`).
421    pub cur_chr: u32,
422    /// The current control sequence (`cur_cs`); [`hash::NO_CS`] for
423    /// characters.
424    pub cur_cs: CsId,
425    /// The current token (`cur_tok`).
426    pub cur_tok: Token,
427
428    /// The value most recently scanned (`cur_val`).
429    pub cur_val: i32,
430    /// The glue most recently scanned, when `cur_val_level` is glue.
431    pub cur_glue: glue::Glue,
432    /// The token list most recently scanned, when `cur_val_level` is tokens.
433    pub cur_toks: Option<token::TokenList>,
434    /// The level of `cur_val`.
435    pub cur_val_level: scan::ValLevel,
436
437    /// Well-known control sequences.
438    pub cs: init::KnownCs,
439    /// Whether the run is in IniTeX mode.
440    pub ini_version: bool,
441    /// Shell escape mode (see [`init::Options::shell_escape`]).
442    pub shell_escape: u8,
443    /// The format identification printed in the banner.
444    pub format_ident: String,
445    /// The job name, once known.
446    pub job_name: Option<Vec<u8>>,
447    /// Token saved by `\afterassignment` (`after_token`).
448    pub after_token: Token,
449    /// Number of nested expansions (LuaTeX's `expand_depth_count`).
450    pub expand_depth: u32,
451    /// The current mode (copy of the innermost nest level's mode).
452    pub mode: maincontrol::Mode,
453    /// The mode last shown by `show_cur_cmd_chr`.
454    pub shown_mode: maincontrol::Mode,
455    /// The format name (`\formatname`).
456    pub format_name: String,
457    /// Math as LuaTeX 1.24 (TeX Live 2026) typesets it, chosen for formats
458    /// of LaTeX 2026-06 and later (see [`Engine::load_format`]); else
459    /// LuaTeX 1.22 (TeX Live 2025), the version retex reports.
460    pub math_124: bool,
461    /// Building an operator's character nucleus, which gets no italic
462    /// correction in OpenType math (LuaTeX 1.24 as 1.22).
463    pub math_op_nucleus: bool,
464    /// Node memory.
465    pub nodes: nodes::Nodes,
466    /// The semantic nest; the last entry is the current list.
467    pub nest: Vec<maincontrol::ListState>,
468    /// Loaded fonts.
469    pub fonts: fonts::FontTable,
470    /// The `long_state` of the macro being called.
471    pub long_state: Cmd,
472    /// The control sequence being defined (for diagnostics).
473    pub def_ref_cs: CsId,
474    /// The partial token list printed by `runaway`.
475    pub runaway_list: Vec<Token>,
476    /// The control sequence last made by `\csname` (`\lastnamedcs`).
477    pub last_cs_name: CsId,
478    /// Frozen copies made by `\primitive`.
479    pub frozen_prims: std::collections::HashMap<Vec<u8>, CsId>,
480    /// Mark texts.
481    pub marks: typeset::Marks,
482    /// Set when a `\read` line has been consumed.
483    pub input_read_finished: bool,
484    /// Whether the long `\errmessage` help has been shown.
485    pub long_help_seen: bool,
486    /// `\deadcycles`.
487    pub dead_cycles: i32,
488    /// `\insertpenalties`.
489    pub insert_penalties: i32,
490    /// `\badness`.
491    pub last_badness: i32,
492    /// Token lists show a macro parameter character once, not doubled
493    /// (`\luaescapestring`).
494    pub single_mac_param: bool,
495    /// The direction the next box made by `hpack` or `vpackage` gets
496    /// (from `dir`/`bdir` of a box specification), or -1. It is set before
497    /// packing because over- and underfull box reports show it.
498    pub pack_dir: i8,
499    /// `\randomseed`: the seed `\setrandomseed` last gave (its absolute
500    /// value), or the one chosen at startup.
501    pub random_seed: i32,
502    /// The state of the random number generator (METAFONT's, see
503    /// [`retex_arith::random`]) behind `\uniformdeviate`, `\normaldeviate`
504    /// and the `tex` library's randomizers.
505    pub randoms: retex_arith::random::Randoms,
506    /// `\lastxpos`.
507    pub last_x_pos: i32,
508    /// `\lastypos`.
509    pub last_y_pos: i32,
510    /// The magnification frozen by `prepare_mag`.
511    pub mag_set: i32,
512    /// The font dimension located by `find_font_dimen`.
513    pub font_dimen_loc: Option<(u32, u32)>,
514    /// `\showboxdepth` in effect while displaying boxes.
515    pub depth_threshold: i32,
516    /// `\showboxbreadth` in effect while displaying boxes.
517    pub breadth_max: i32,
518    /// The paragraph-ending control sequence (`\partokenname`).
519    pub par_token_cs: CsId,
520    /// Whether `\dump` ended the run.
521    pub dump_requested: bool,
522    /// Whether a macro expanded now would have its body executed by main
523    /// control (see [`native`]).
524    pub exec_context: bool,
525    /// Whether the next [`expand`](Engine::expand) is a single expansion
526    /// step whose result is not expanded further (`\expandafter`, `\span`
527    /// in a preamble, Lua's `token.expand`): expandable native
528    /// implementations do not run there (see [`native`]). `expand` clears
529    /// it on entry.
530    pub one_step: bool,
531    /// Where macros are defined (`retex profile`, `retex explain`).
532    pub origins: Option<Box<profile::Origins>>,
533    /// The sampling profiler (`retex profile`).
534    pub profiler: Option<Box<profile::Profiler>>,
535    /// Integer parameters set from the command line when the job starts
536    /// ([`init::Options::int_params`]).
537    pub start_ints: Vec<(u32, i32)>,
538    /// `-output-comment`.
539    pub output_comment: Option<String>,
540    /// The job's start time (`\time`, the banner, the PDF creation date).
541    pub start_time: clock::StartTime,
542    /// The page builder.
543    pub page: typeset::PageState,
544    /// Lua functions registered for `\luafunction` (by number).
545    pub lua_functions: std::collections::HashMap<i32, u32>,
546    /// Called to store the format when `\dump` is executed.
547    pub format_hook: Option<EngineHook>,
548    /// Called at the end of the run to finish output files.
549    pub finish_hook: Option<EngineHook>,
550    /// Non-immediate `\write`s waiting for shipout.
551    pub deferred_writes: Vec<(i32, token::TokenList)>,
552    /// Text directions opened in the current box: (group level,
553    /// direction, the text direction before), innermost last (see
554    /// [`dirs`]).
555    pub text_dirs: Vec<(u16, i8, i8)>,
556    /// Box resources to write after the page or form being written, one
557    /// list per open page or form.
558    pub pending_forms: Vec<Vec<u32>>,
559    /// The packets of virtual characters whose `lua` command is running,
560    /// innermost last: what the `vf` library draws into.
561    pub(crate) vf_frames: Vec<shipout::VfFrame>,
562    /// Objects referred to (`\pdfextension refobj`) in the box resources
563    /// being written, one list per open form.
564    pub form_objects: Vec<Vec<pdfext::PdfObject>>,
565    /// The `text_dirs` of the enclosing boxes.
566    pub outer_text_dirs: Vec<Vec<(u16, i8, i8)>>,
567    /// The math and text directions outside the open formulas, with the
568    /// save level of each formula's group.
569    pub outer_math_dirs: Vec<(u16, i32, i32)>,
570    /// Contents of files written by `\write`, for the pass driver.
571    pub written_sink: files::WrittenSink,
572    /// State of the typesetting routines.
573    pub ts: typeset::TsState,
574    /// Tokens printed from Lua waiting to be pushed as a token list.
575    pub pending_lua_tokens: Option<Vec<Token>>,
576    /// Default output mode: errors and warnings recorded for the driver
577    /// (`None` in compatibility mode).
578    pub diagnostics: Option<Vec<diag::Diagnostic>>,
579    /// Default output mode, interactive: shows the diagnostics recorded
580    /// since the last prompt, and the prompt, before the terminal is read.
581    /// Returns `false` if it showed nothing (the error only repeats one
582    /// already reported): TeX then goes on without asking.
583    pub prompt_hook: Option<PromptHook>,
584    /// Default output mode: called when a page is shipped or a file opened,
585    /// to show progress and the errors recorded so far.
586    pub progress_hook: Option<fn(&mut Engine)>,
587    /// The number of diagnostics the prompt hook has shown.
588    pub diagnostics_shown: usize,
589    /// Whether the terminal was read (an error or a `\read` prompt).
590    pub prompted: bool,
591    /// Image resources (`\saveimageresource`), in the order saved (their
592    /// indices are their object numbers).
593    pub images: Vec<image::ImageResource>,
594    /// `\lastsavedimageresourceindex`.
595    pub last_image_index: i32,
596    /// `\lastsavedimageresourcepages`.
597    pub last_image_pages: i32,
598    /// Whether SVG text has been given its font finder.
599    pub svg_fonts_set: bool,
600    /// Box resources (`\saveboxresource`), in the order saved (their
601    /// indices are their object numbers).
602    pub box_resources: Vec<image::BoxResource>,
603    /// `\lastsavedboxresourceindex`.
604    pub last_box_index: i32,
605    /// Scratch buffer for macro arguments (reused by `macro_call`).
606    pub arg_scratch: Vec<Token>,
607    /// Scratch buffer for the argument list of a macro call.
608    pub param_scratch: Vec<token::TokenList>,
609    /// The file of each `\write` stream opened (see [`snapshot`]).
610    pub write_paths: Vec<Option<std::path::PathBuf>>,
611    /// The macro at whose expansion [`snapshot_hook`](Self::snapshot_hook)
612    /// runs (LaTeX's `\document`).
613    pub snapshot_cs: Option<CsId>,
614    /// Called once, when `snapshot_cs` is expanded (see [`snapshot`]).
615    pub snapshot_hook: Option<snapshot::Hook>,
616    /// Called after each page is shipped out, to take a page checkpoint,
617    /// and once when the document has begun (see [`snapshot`]).
618    pub page_hook: Option<fn(&mut Engine)>,
619    /// Called when the run is over, before the output file is finished
620    /// (see [`snapshot`]).
621    pub final_hook: Option<fn(&mut Engine)>,
622    /// The input level of the file `\begin{document}` was read from: the
623    /// page hook runs before its next line is read.
624    pub(crate) begun_level: Option<usize>,
625    /// Hashes of the code-table pages and macros that existed when
626    /// fingerprints were prepared (see [`fingerprint`]).
627    pub fp_cache: Option<Box<fingerprint::FpCache>>,
628    /// The file and line the user asked to edit at an error (`E`); the
629    /// program runs the editor at the end.
630    pub edit_request: Option<(Vec<u8>, i32)>,
631    /// The output file written at the end: name, pages and bytes.
632    pub output_written: Option<(String, u32, u64)>,
633    /// The line number reported while reading Lua output.
634    pub input_line_for_lua: i32,
635    /// Internal tokens made by `\pdfvariable`, by variable name.
636    pub backend_cs: std::collections::HashMap<&'static str, CsId>,
637    /// Nesting depth of local control loops (`tex.runtoks`).
638    pub local_level: u32,
639    /// The box `token.scan_list` scanned, once finished.
640    pub lua_scanned_box: Option<nodes::NodeRef>,
641    /// Whether `token.scan_list` runs local control for its box.
642    pub lua_scan_waiting: bool,
643    /// The output backend (PDF writer), if any.
644    pub backend: Option<Box<dyn shipout::Backend>>,
645    /// The DVI writer (`\outputmode=0`).
646    pub dvi: dvi::DviWriter,
647    /// Hyphenation languages, by `\language` number.
648    pub languages: Vec<Option<retex_hyph::Language>>,
649    /// Document-level PDF data from `\pdfextension`.
650    pub pdf: pdfext::PdfState,
651    /// Current values of the colour stacks during shipout.
652    pub colorstack_values: std::collections::HashMap<i32, Vec<Vec<u8>>>,
653    /// Number of pages shipped out.
654    pub total_pages: u32,
655    /// Muglue values of `\Umath…` spacing parameters.
656    pub math_glues: Vec<glue::Glue>,
657    /// Whether the parameters set by the first formula are initialized
658    /// (see [`mathparams`]).
659    pub math_params_initialized: bool,
660    /// SyncTeX output.
661    pub synctex: synctex::Synctex,
662}
663
664impl std::fmt::Debug for Engine {
665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
666        f.debug_struct("Engine")
667            .field("cur_cmd", &self.cur_cmd)
668            .field("cur_chr", &self.cur_chr)
669            .finish_non_exhaustive()
670    }
671}