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;
19pub mod build;
20pub mod clock;
21pub mod cmd;
22mod cmd_gen;
23pub mod codes;
24pub mod cond;
25pub mod diag;
26pub mod dirs;
27pub mod display;
28pub mod dvi;
29pub mod eqtb;
30pub mod error;
31pub mod expand;
32pub mod expansion;
33pub mod files;
34pub mod fonts;
35pub mod format;
36pub mod glue;
37pub mod hash;
38pub mod hmode;
39pub mod hyph;
40pub mod image;
41pub mod init;
42pub mod input;
43pub mod linebreak;
44pub mod maincontrol;
45pub mod math;
46pub mod mathdisplay;
47pub mod mathparams;
48pub mod meaning;
49pub mod misc;
50pub mod mlist;
51pub mod nodeproc;
52pub mod nodes;
53pub mod pack;
54pub mod page;
55pub mod pdfext;
56pub(crate) mod pdfship;
57pub mod prefixed;
58pub mod primitives;
59pub mod primitives_gen;
60pub mod print;
61pub mod protrusion;
62pub mod save;
63pub mod scan;
64pub mod shipout;
65pub mod showbox;
66pub mod snapshot;
67pub mod the;
68pub mod timing;
69pub mod token;
70pub mod typeset;
71pub mod utf8x;
72
73use cmd::Cmd;
74use cond::CondState;
75use eqtb::Eqtb;
76use error::ErrorState;
77use files::FileState;
78use hash::{CsId, CsTable};
79use input::InputState;
80use print::Printer;
81use save::SaveStack;
82use token::Token;
83
84/// Callbacks from the engine into an embedded Lua interpreter.
85///
86/// The engine does not depend on Lua. `\directlua` and related primitives
87/// call this trait, which `retex-lua` implements. The host is shared
88/// (`Rc`), so nested calls (Lua → TeX → Lua) reach it too.
89pub trait LuaHost {
90 /// Runs a chunk of Lua code for `\directlua` (or `\latelua`, etc.).
91 /// `name` is the chunk name. Text printed with `tex.print` must be
92 /// pushed back into the engine's input by the host.
93 fn run(&self, engine: &mut Engine, name: &str, code: &[u8]) -> Result<(), String>;
94
95 /// Data to store in the format file (e.g. `lua.bytecode`).
96 fn dump(&self, _engine: &mut Engine) -> Vec<u8> {
97 Vec::new()
98 }
99
100 /// Restores data stored by [`dump`](Self::dump).
101 fn undump(&self, _engine: &mut Engine, _data: &[u8]) -> Result<(), String> {
102 Ok(())
103 }
104
105 /// Calls callback `name` without arguments, ignoring its results
106 /// (`pre_dump`, `stop_run`, …). Does nothing if none is registered.
107 fn call_simple(&self, _engine: &mut Engine, _name: &str) -> Result<(), String> {
108 Ok(())
109 }
110
111 /// Whether a Lua function is registered for callback `name`.
112 fn has_callback(&self, _name: &str) -> bool {
113 false
114 }
115
116 /// Calls the `define_font` callback. Returns the id of the font it
117 /// defined, or `None` if the font could not be loaded.
118 fn define_font(&self, _engine: &mut Engine, _name: &[u8], _size: i32) -> Option<u32> {
119 None
120 }
121
122 /// Calls a node-list filter callback (`pre_linebreak_filter`,
123 /// `hpack_filter`, `post_linebreak_filter`, …) on the list starting at
124 /// `head`. Returns the new head; [`ListFilter::Flush`] means the list
125 /// must be discarded.
126 fn list_filter(
127 &self,
128 _engine: &mut Engine,
129 _callback: &str,
130 head: nodes::NodeRef,
131 _group: &str,
132 ) -> ListFilter {
133 ListFilter::Keep(head)
134 }
135
136 /// Calls the `glyph_info` callback for glyph node `p`: `Some(text)` is
137 /// what to print, `None` means the callback returned no string.
138 fn glyph_info(&self, _engine: &mut Engine, _p: nodes::NodeRef) -> Option<Vec<u8>> {
139 None
140 }
141}
142
143/// Result of a node-list filter callback.
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub enum ListFilter {
146 /// Continue with this list (possibly the original head).
147 Keep(nodes::NodeRef),
148 /// The callback returned `false`: discard the list.
149 Flush,
150}
151
152impl Engine {
153 /// Whether the Lua callback `name` is set.
154 pub fn has_callback(&self, name: &str) -> bool {
155 self.lua.as_ref().is_some_and(|h| h.has_callback(name))
156 }
157
158 /// Runs a node-list filter callback if one is registered (the hook
159 /// typesetting code calls at LuaTeX's callback points).
160 pub fn run_list_filter(
161 &mut self,
162 callback: &str,
163 head: nodes::NodeRef,
164 group: &str,
165 ) -> ListFilter {
166 match self.lua.clone() {
167 Some(h) if h.has_callback(callback) => h.list_filter(self, callback, head, group),
168 _ => ListFilter::Keep(head),
169 }
170 }
171}
172
173impl Engine {
174 /// Runs the `glyph_info` callback for glyph `p` when one is set:
175 /// `None` without a callback, `Some(None)` when it returned no string.
176 pub fn run_glyph_info(&mut self, p: nodes::NodeRef) -> Option<Option<Vec<u8>>> {
177 match self.lua.clone() {
178 Some(h) if h.has_callback("glyph_info") => Some(h.glyph_info(self, p)),
179 _ => None,
180 }
181 }
182}
183
184/// A one-shot callback into the host, run with the engine borrowed.
185pub type EngineHook = Box<dyn FnOnce(&mut Engine)>;
186
187/// The global state of a TeX run.
188pub struct Engine {
189 /// Control-sequence names.
190 pub hash: CsTable,
191 /// The table of equivalents.
192 pub eqtb: Eqtb,
193 /// The save stack and grouping state.
194 pub save: SaveStack,
195 /// The input stack.
196 pub input: InputState,
197 /// Terminal, log and `\write` output.
198 pub printer: Printer,
199 /// Error-handling state.
200 pub err: ErrorState,
201 /// Conditionals.
202 pub cond: CondState,
203 /// Files: `\openin`/`\read`, `\openout`, input file stack.
204 pub files: FileState,
205 /// The file finder.
206 pub kpse: Option<retex_kpath::Shared>,
207 /// The Lua interpreter, when available.
208 pub lua: Option<std::rc::Rc<dyn LuaHost>>,
209
210 /// The current command code (`cur_cmd`).
211 pub cur_cmd: Cmd,
212 /// The current modifier (`cur_chr`).
213 pub cur_chr: u32,
214 /// The current control sequence (`cur_cs`); [`hash::NO_CS`] for
215 /// characters.
216 pub cur_cs: CsId,
217 /// The current token (`cur_tok`).
218 pub cur_tok: Token,
219
220 /// The value most recently scanned (`cur_val`).
221 pub cur_val: i32,
222 /// The glue most recently scanned, when `cur_val_level` is glue.
223 pub cur_glue: glue::Glue,
224 /// The token list most recently scanned, when `cur_val_level` is tokens.
225 pub cur_toks: Option<token::TokenList>,
226 /// The level of `cur_val`.
227 pub cur_val_level: scan::ValLevel,
228
229 /// Well-known control sequences.
230 pub cs: init::KnownCs,
231 /// Whether the run is in IniTeX mode.
232 pub ini_version: bool,
233 /// Shell escape mode (see [`init::Options::shell_escape`]).
234 pub shell_escape: u8,
235 /// The format identification printed in the banner.
236 pub format_ident: String,
237 /// The job name, once known.
238 pub job_name: Option<Vec<u8>>,
239 /// Token saved by `\afterassignment` (`after_token`).
240 pub after_token: Token,
241 /// Number of nested expansions (LuaTeX's `expand_depth_count`).
242 pub expand_depth: u32,
243 /// The current mode (copy of the innermost nest level's mode).
244 pub mode: maincontrol::Mode,
245 /// The mode last shown by `show_cur_cmd_chr`.
246 pub shown_mode: maincontrol::Mode,
247 /// The format name (`\formatname`).
248 pub format_name: String,
249 /// Node memory.
250 pub nodes: nodes::Nodes,
251 /// The semantic nest; the last entry is the current list.
252 pub nest: Vec<maincontrol::ListState>,
253 /// Loaded fonts.
254 pub fonts: fonts::FontTable,
255 /// The `long_state` of the macro being called.
256 pub long_state: Cmd,
257 /// The control sequence being defined (for diagnostics).
258 pub def_ref_cs: CsId,
259 /// The partial token list printed by `runaway`.
260 pub runaway_list: Vec<Token>,
261 /// The control sequence last made by `\csname` (`\lastnamedcs`).
262 pub last_cs_name: CsId,
263 /// Frozen copies made by `\primitive`.
264 pub frozen_prims: std::collections::HashMap<Vec<u8>, CsId>,
265 /// Mark texts.
266 pub marks: typeset::Marks,
267 /// Set when a `\read` line has been consumed.
268 pub input_read_finished: bool,
269 /// Whether the long `\errmessage` help has been shown.
270 pub long_help_seen: bool,
271 /// `\deadcycles`.
272 pub dead_cycles: i32,
273 /// `\insertpenalties`.
274 pub insert_penalties: i32,
275 /// `\badness`.
276 pub last_badness: i32,
277 /// Token lists show a macro parameter character once, not doubled
278 /// (`\luaescapestring`).
279 pub single_mac_param: bool,
280 /// The direction the next box made by `hpack` or `vpackage` gets
281 /// (from `dir`/`bdir` of a box specification), or -1. It is set before
282 /// packing because over- and underfull box reports show it.
283 pub pack_dir: i8,
284 /// `\randomseed`: the seed `\setrandomseed` last gave (its absolute
285 /// value), or the one chosen at startup.
286 pub random_seed: i32,
287 /// The state of the random number generator (METAFONT's, see
288 /// [`retex_arith::random`]) behind `\uniformdeviate`, `\normaldeviate`
289 /// and the `tex` library's randomizers.
290 pub randoms: retex_arith::random::Randoms,
291 /// `\lastxpos`.
292 pub last_x_pos: i32,
293 /// `\lastypos`.
294 pub last_y_pos: i32,
295 /// The magnification frozen by `prepare_mag`.
296 pub mag_set: i32,
297 /// The font dimension located by `find_font_dimen`.
298 pub font_dimen_loc: Option<(u32, u32)>,
299 /// `\showboxdepth` in effect while displaying boxes.
300 pub depth_threshold: i32,
301 /// `\showboxbreadth` in effect while displaying boxes.
302 pub breadth_max: i32,
303 /// The paragraph-ending control sequence (`\partokenname`).
304 pub par_token_cs: CsId,
305 /// Whether `\dump` ended the run.
306 pub dump_requested: bool,
307 /// The job's start time (`\time`, the banner, the PDF creation date).
308 pub start_time: clock::StartTime,
309 /// The page builder.
310 pub page: typeset::PageState,
311 /// Lua functions registered for `\luafunction` (by number).
312 pub lua_functions: std::collections::HashMap<i32, u32>,
313 /// Called to store the format when `\dump` is executed.
314 pub format_hook: Option<EngineHook>,
315 /// Called at the end of the run to finish output files.
316 pub finish_hook: Option<EngineHook>,
317 /// Non-immediate `\write`s waiting for shipout.
318 pub deferred_writes: Vec<(i32, token::TokenList)>,
319 /// Text directions opened in the current box: (group level,
320 /// direction), innermost last (see [`dirs`]).
321 pub text_dirs: Vec<(u16, i8)>,
322 /// Box resources to write after the page or form being written, one
323 /// list per open page or form.
324 pub pending_forms: Vec<Vec<u32>>,
325 /// Objects referred to (`\pdfextension refobj`) in the box resources
326 /// being written, one list per open form.
327 pub form_objects: Vec<Vec<pdfext::PdfObject>>,
328 /// The `text_dirs` of the enclosing boxes.
329 pub outer_text_dirs: Vec<Vec<(u16, i8)>>,
330 /// Contents of files written by `\write`, for the pass driver.
331 pub written_sink: files::WrittenSink,
332 /// State of the typesetting routines.
333 pub ts: typeset::TsState,
334 /// Tokens printed from Lua waiting to be pushed as a token list.
335 pub pending_lua_tokens: Option<Vec<Token>>,
336 /// Default output mode: errors and warnings recorded for the driver
337 /// (`None` in compatibility mode).
338 pub diagnostics: Option<Vec<diag::Diagnostic>>,
339 /// Image resources (`\saveimageresource`), in the order saved (their
340 /// indices are their object numbers).
341 pub images: Vec<image::ImageResource>,
342 /// `\lastsavedimageresourceindex`.
343 pub last_image_index: i32,
344 /// `\lastsavedimageresourcepages`.
345 pub last_image_pages: i32,
346 /// Box resources (`\saveboxresource`), in the order saved (their
347 /// indices are their object numbers).
348 pub box_resources: Vec<image::BoxResource>,
349 /// `\lastsavedboxresourceindex`.
350 pub last_box_index: i32,
351 /// Scratch buffer for macro arguments (reused by `macro_call`).
352 pub arg_scratch: Vec<Token>,
353 /// Scratch buffer for the argument list of a macro call.
354 pub param_scratch: Vec<token::TokenList>,
355 /// The file of each `\write` stream opened (see [`snapshot`]).
356 pub write_paths: Vec<Option<std::path::PathBuf>>,
357 /// The macro at whose expansion [`snapshot_hook`](Self::snapshot_hook)
358 /// runs (LaTeX's `\document`).
359 pub snapshot_cs: Option<CsId>,
360 /// Called once, when `snapshot_cs` is expanded (see [`snapshot`]).
361 pub snapshot_hook: Option<snapshot::Hook>,
362 /// The output file written at the end: name, pages and bytes.
363 pub output_written: Option<(String, u32, u64)>,
364 /// The line number reported while reading Lua output.
365 pub input_line_for_lua: i32,
366 /// Internal tokens made by `\pdfvariable`, by variable name.
367 pub backend_cs: std::collections::HashMap<&'static str, CsId>,
368 /// Nesting depth of local control loops (`tex.runtoks`).
369 pub local_level: u32,
370 /// The output backend (PDF writer), if any.
371 pub backend: Option<Box<dyn shipout::Backend>>,
372 /// The DVI writer (`\outputmode=0`).
373 pub dvi: dvi::DviWriter,
374 /// Hyphenation languages, by `\language` number.
375 pub languages: Vec<Option<retex_hyph::Language>>,
376 /// Document-level PDF data from `\pdfextension`.
377 pub pdf: pdfext::PdfState,
378 /// Current values of the colour stacks during shipout.
379 pub colorstack_values: std::collections::HashMap<i32, Vec<Vec<u8>>>,
380 /// Number of pages shipped out.
381 pub total_pages: u32,
382 /// Muglue values of `\Umath…` spacing parameters.
383 pub math_glues: Vec<glue::Glue>,
384 /// Whether the parameters set by the first formula are initialized
385 /// (see [`mathparams`]).
386 pub math_params_initialized: bool,
387}
388
389impl std::fmt::Debug for Engine {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 f.debug_struct("Engine")
392 .field("cur_cmd", &self.cur_cmd)
393 .field("cur_chr", &self.cur_chr)
394 .finish_non_exhaustive()
395 }
396}