Skip to main content

mathtex_portable_engine_generated/
runtime.rs

1//! Runtime prelude the patcher splices into the generated XeTeX engine as `runtime.rs`.
2
3#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
4// Engine methods spell `self: &mut Self` because codegen strips the `&mut self` shorthand.
5#![allow(clippy::needless_arbitrary_self_type)]
6
7pub(crate) type integer = i32;
8pub(crate) type real = f64;
9pub(crate) type glueratio = f64;
10pub(crate) type boolean = i32;
11pub(crate) type schar = i8;
12pub(crate) type ASCIIcode = integer;
13pub(crate) type eightbits = integer;
14pub(crate) type poolpointer = integer;
15pub(crate) type strnumber = integer;
16pub(crate) type savepointer = integer;
17pub(crate) type packedUTF16code = u16;
18pub(crate) type scaled = integer;
19pub(crate) type nonnegativeinteger = integer;
20pub(crate) type smallnumber = integer;
21pub(crate) type quarterword = integer;
22pub(crate) type halfword = integer;
23pub(crate) type glueord = integer;
24pub(crate) type groupcode = integer;
25pub(crate) type internalfontnumber = integer;
26pub(crate) type fontindex = integer;
27pub(crate) type ninebits = integer;
28pub(crate) type triepointer = integer;
29pub(crate) type trieopcode = integer;
30pub(crate) type hyphpointer = integer;
31pub(crate) type UTF16code = u16;
32pub(crate) type UnicodeScalar = integer;
33pub(crate) type uint16_t = u16;
34pub(crate) type UTF8code = integer;
35pub(crate) type voidpointer = *mut ();
36pub(crate) type string = *mut i8;
37pub(crate) type const_string = *const i8;
38pub(crate) struct PortableFileHandle {
39    name: String,
40    package: Option<String>,
41    format: integer,
42    bytes: Vec<u8>,
43    cursor: usize,
44    eof_after_failed_read: bool,
45    /// Decoding of this file's bytes, `Bytes` for binary inputs such as TFM files.
46    encoding: InputEncoding,
47    /// One unit lookahead for the UTF-16 surrogate decoder, XeTeX's `savedChar`.
48    saved_char: Option<u32>,
49}
50
51/// XeTeX input decoding mode from `xetex.h`, `auto` is resolved at open time and ICU mappings read as bytes.
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub enum InputEncoding {
54    /// Raw bytes, one Unicode scalar per byte (XeTeX `RAW`).
55    #[default]
56    Bytes,
57    /// UTF-8 (XeTeX `UTF8`).
58    Utf8,
59    /// UTF-16 big endian, XeTeX's `UTF16BE`.
60    Utf16Be,
61    /// UTF-16 little endian, XeTeX's `UTF16LE`.
62    Utf16Le,
63}
64pub(crate) type bytefile = NativeFileHandle;
65pub(crate) type unicodefile = NativeFileHandle;
66pub type PortableFontHandle = usize;
67pub(crate) type FontHandle = PortableFontHandle;
68pub(crate) type CFDictionaryRef = voidpointer;
69pub(crate) type NativeFileHandle = *mut PortableFileHandle;
70pub(crate) type alphafile = NativeFileHandle;
71#[allow(clippy::upper_case_acronyms)]
72pub(crate) type UFILE = PortableFileHandle;
73
74pub(crate) const true_0: boolean = 1;
75pub(crate) const false_0: boolean = 0;
76pub(crate) const firstmathfontdimen: integer = 10;
77pub(crate) const native_node_size: integer = 6;
78
79/// The empty string, an absent file extension.
80pub(crate) const POOL_EMPTY_STRING: strnumber = 65626;
81/// The `.tex` extension `\openout` supplies.
82pub(crate) const POOL_TEX_EXTENSION: strnumber = 66180;
83/// The capacity `overflow` names for a line longer than the buffer.
84pub(crate) const POOL_BUFFER_SIZE: strnumber = 65538;
85/// The `confusion` label of `prune_page_top`.
86pub(crate) const POOL_PRUNING: strnumber = 66395;
87/// The prefix `print_err` starts every error message with.
88pub(crate) const POOL_ERROR_PREFIX: strnumber = 65544;
89/// The inter atom spacing table `magic_offset` indexes.
90pub(crate) const POOL_MATH_SPACING: strnumber = 66320;
91// XeTeX layout constants from xetex.web, as tangled with the partoken change file that makes max_command 103.
92/// The null pointer, `min_halfword`.
93pub(crate) const TEX_NULL: halfword = -0x0FFF_FFFF;
94/// Outer vertical mode, internal vertical mode is its negation.
95pub(crate) const VMODE: integer = 1;
96/// Math mode, `vmode + 2 * (max_command + 1)`.
97pub(crate) const MMODE: integer = 209;
98/// Box context of `\setbox0`, smaller contexts are shift amounts of a box being appended.
99pub(crate) const BOX_FLAG: integer = 0o10000000000;
100/// `eq_type` of an undefined control sequence, `max_command + 1`.
101pub(crate) const EQ_TYPE_UNDEFINED_CS: i16 = 104;
102/// `eq_type` of a code table entry, `max_command + 20`.
103pub(crate) const EQ_TYPE_DATA: i16 = 123;
104/// `eq_level` of a definition made outside every group.
105pub(crate) const LEVEL_ONE: i16 = 1;
106/// Command code and catcode of an other character.
107pub(crate) const CMD_OTHER_CHAR: integer = 12;
108/// Command code of the extension primitives, `\Uhostbox` among them.
109pub(crate) const CMD_EXTENSION: i16 = 59;
110/// Number of the first string with pool storage, below it every string is a single character.
111pub(crate) const TOO_BIG_CHAR: strnumber = 65_536;
112/// `align_state` outside every alignment, braces move it up and down from here.
113pub(crate) const NEUTRAL_ALIGN_STATE: integer = 1_000_000;
114/// Math style code of script style, the smaller codes are display and text styles.
115pub(crate) const SCRIPT_STYLE: i32 = 4;
116/// Math size offset of the script size fonts, twice it is the scriptscript size.
117pub(crate) const SCRIPT_SIZE: i32 = 256;
118/// Box glue order of finite glue.
119pub(crate) const GLUE_ORDER_NORMAL: i16 = 0;
120/// Box glue sign of a stretched box.
121pub(crate) const GLUE_SIGN_STRETCHING: i16 = 1;
122/// Command code of `\relax`.
123pub(crate) const CMD_RELAX: integer = 0;
124/// Command code of a right brace.
125pub(crate) const CMD_RIGHT_BRACE: integer = 2;
126/// Command code of a space.
127pub(crate) const CMD_SPACER: integer = 10;
128/// Command code of `\par`.
129pub(crate) const CMD_PAR_END: integer = 13;
130/// Command code of `\end` and `\dump`.
131pub(crate) const CMD_STOP: integer = 14;
132/// Last command code that is not an assignment, `prefixed_command` runs every command above it.
133pub(crate) const CMD_MAX_NON_PREFIXED: integer = 71;
134/// Input state of a token list level, every other state reads a file line.
135pub(crate) const TOKEN_LIST_STATE: quarterword = 0;
136/// Input state at the start of a file line, XeTeX's `new_line`.
137pub(crate) const NEW_LINE_STATE: quarterword = 33;
138/// `read_open` state of a `\read` stream that is not open.
139pub(crate) const READ_CLOSED: eightbits = 2;
140/// `read_open` state of a `\read` stream opened but not yet read.
141pub(crate) const READ_JUST_OPEN: eightbits = 1;
142/// Number of `\write` and `\read` streams, higher stream numbers reach the terminal.
143pub(crate) const STREAM_COUNT: usize = 16;
144/// `font_area` marker of a font loaded through the OpenType or Graphite shaper.
145pub(crate) const OTGR_FONT_FLAG: integer = 0xFFFE;
146/// Token type of a macro argument, the first token type.
147pub(crate) const TOKEN_TYPE_PARAMETER: quarterword = 0;
148/// Token type of a single backed up token, the types below it are arguments and templates.
149pub(crate) const TOKEN_TYPE_BACKED_UP: quarterword = 3;
150/// Token type of a backed up character, XeTeX's `backed_up_char`.
151pub(crate) const TOKEN_TYPE_BACKED_UP_CHAR: quarterword = 4;
152/// Token type of an inserted token list.
153pub(crate) const TOKEN_TYPE_INSERTED: quarterword = 5;
154/// Token type of a macro body, the types from here on map their tokens to the invocation.
155pub(crate) const TOKEN_TYPE_MACRO: quarterword = 6;
156/// Token type of a `\write` token list.
157pub(crate) const TOKEN_TYPE_WRITE_TEXT: quarterword = 18;
158/// Node type of an hlist box.
159pub(crate) const HLIST_NODE: i32 = 0;
160/// Node type of a vlist box.
161pub(crate) const VLIST_NODE: i32 = 1;
162/// Node type of a rule.
163pub(crate) const RULE_NODE: i32 = 2;
164/// Node type of an insertion.
165pub(crate) const INS_NODE: i32 = 3;
166/// Node type of a mark.
167pub(crate) const MARK_NODE: i32 = 4;
168/// Node type of a `\vadjust`.
169pub(crate) const ADJUST_NODE: i32 = 5;
170/// Node type of a ligature.
171pub(crate) const LIGATURE_NODE: i32 = 6;
172/// Node type of a discretionary.
173pub(crate) const DISC_NODE: i32 = 7;
174/// Node type of a whatsit.
175pub(crate) const WHATSIT_NODE: i32 = 8;
176/// Node type of a math on or off node.
177pub(crate) const MATH_NODE: i32 = 9;
178/// Node type of glue.
179pub(crate) const GLUE_NODE: i32 = 10;
180/// Node type of a kern.
181pub(crate) const KERN_NODE: i32 = 11;
182/// Node type of a penalty.
183pub(crate) const PENALTY_NODE: i32 = 12;
184/// Node type of an unset alignment box.
185pub(crate) const UNSET_NODE: i32 = 13;
186/// Node type of a math style change.
187pub(crate) const STYLE_NODE: i32 = 14;
188/// Node type of a `\mathchoice`.
189pub(crate) const CHOICE_NODE: i32 = 15;
190/// Noad type of an ordinary atom, the first noad type.
191pub(crate) const ORD_NOAD: i32 = 16;
192/// Noad type of a generalized fraction.
193pub(crate) const FRACTION_NOAD: i32 = 25;
194/// Noad type of a `\vcenter`, the last noad type with a nucleus.
195pub(crate) const VCENTER_NOAD: i32 = 29;
196/// Noad type of a `\left`.
197pub(crate) const LEFT_NOAD: i32 = 30;
198/// Noad type of a `\right`, the last noad type.
199pub(crate) const RIGHT_NOAD: i32 = 31;
200/// Whatsit subtype of an `\openout`.
201pub(crate) const OPEN_NODE: i32 = 0;
202/// Whatsit subtype of a `\write`.
203pub(crate) const WRITE_NODE: i32 = 1;
204/// Whatsit subtype of a `\closeout`.
205pub(crate) const CLOSE_NODE: i32 = 2;
206/// Whatsit subtype of a native word.
207pub(crate) const NATIVE_WORD_NODE: i32 = 40;
208/// Whatsit subtype of a native word ending in a hyphenation point.
209pub(crate) const NATIVE_WORD_NODE_AT: i32 = 41;
210/// Whatsit subtype of a single native glyph.
211pub(crate) const GLYPH_NODE: i32 = 42;
212/// Math type of a noad field holding a box.
213pub(crate) const SUB_BOX: i32 = 2;
214/// `\splittopskip` among the glue parameters.
215pub(crate) const SPLIT_TOP_SKIP_CODE: smallnumber = 10;
216/// `\vsplit` among the box codes, which index `disc_ptr`.
217pub(crate) const VSPLIT_CODE: usize = 3;
218/// eqtb location of the current font.
219pub(crate) const CUR_FONT_LOC: i64 = 1_206_823;
220/// First eqtb location of the math fonts, family 0 at text size.
221pub(crate) const MATH_FONT_BASE: i64 = 1_206_824;
222/// First eqtb location of the per code point `\catcode` table.
223pub(crate) const CAT_CODE_BASE: usize = 1_207_592;
224/// First eqtb location of the `\lccode` table.
225pub(crate) const LC_CODE_BASE: usize = 2_321_704;
226/// First eqtb location of the `\sfcode` table.
227pub(crate) const SF_CODE_BASE: usize = 4_549_928;
228/// First eqtb location of the `\mathcode` table.
229pub(crate) const MATH_CODE_BASE: usize = 5_664_040;
230/// First eqtb location of the character substitution table.
231pub(crate) const CHAR_SUB_CODE_BASE: usize = 6_778_152;
232/// First eqtb location of the integer parameters.
233pub(crate) const INT_BASE: usize = 7_892_264;
234/// First eqtb location of the per code point `\delcode` table.
235pub(crate) const DEL_CODE_BASE: usize = 7_892_607;
236/// First eqtb location of the dimension parameters, one past the `\delcode` table.
237pub(crate) const DIMEN_BASE: usize = 9_006_719;
238/// eqtb location of `\endlinechar`.
239pub(crate) const EQTB_END_LINE_CHAR_LOC: i64 = 7_892_312;
240/// eqtb location of `\tracinglostchars`, `int_base + tracing_lost_chars_code`.
241pub(crate) const EQTB_TRACING_LOST_CHARS_LOC: i64 = 7_892_299;
242/// eqtb location of `\XeTeXuseglyphmetrics`, `int_base + eTeX_state_code + XeTeX_use_glyph_metrics_code`.
243pub(crate) const EQTB_USE_GLYPH_METRICS_LOC: i64 = 7_892_342;
244/// First selector that prints into memory rather than to the terminal or log.
245pub(crate) const SELECTOR_PSEUDO: eightbits = 20;
246/// Last `eqtb` location, the `hash_extra` control sequence meanings follow it.
247pub(crate) const EQTB_SIZE: usize = 9_006_997;
248/// Font dimension number of the quad.
249pub(crate) const QUAD_CODE: integer = 6;
250/// Font dimension number of the x height.
251pub(crate) const X_HEIGHT_CODE: integer = 5;
252/// Font dimension number XeTeX gives native fonts for the cap height.
253pub(crate) const CAP_HEIGHT_CODE: integer = 8;
254
255#[derive(Debug)]
256pub(crate) struct EngineAbort {
257    status: integer,
258}
259
260/// A surfaced TeX error or sandbox rejection, carrying the message the host reports.
261#[derive(Debug)]
262pub(crate) struct EngineError {
263    pub(crate) message: String,
264    pub(crate) kind: PortableErrorKind,
265}
266
267/// What ended a run with an error.
268#[derive(Clone, Copy, Debug, PartialEq, Eq)]
269pub enum PortableErrorKind {
270    /// A TeX error, such as an undefined control sequence.
271    Tex,
272    /// The sandbox rejected a command, such as a math shift that leaves the wrapper math.
273    Sandbox,
274    /// The sandbox work budget ran out.
275    Budget,
276}
277
278/// The error that ended the last run and where the input stood when it did.
279#[derive(Clone, Debug, PartialEq, Eq)]
280pub struct PortableError {
281    pub message: String,
282    pub kind: PortableErrorKind,
283    /// TeX's `line`, the 1 based line of the innermost input file, 0 before any line was read.
284    pub line: integer,
285    /// Span of the token read last, `None` while source tracking is off.
286    pub span: Option<PortableSourceSpan>,
287}
288
289/// The non `Ok` outcomes, kept as a `Result` error so `?` threads them through the generated bodies.
290#[derive(Debug)]
291pub(crate) enum EngineBreak {
292    Abort(EngineAbort),
293    Error(EngineError),
294}
295
296impl From<EngineAbort> for EngineBreak {
297    fn from(abort: EngineAbort) -> Self {
298        EngineBreak::Abort(abort)
299    }
300}
301
302/// Default sandbox budget of commands and expansions, [`PortableTexEngine::set_sandbox_op_budget`] changes it.
303pub const SANDBOX_OP_BUDGET: u64 = 2_000_000;
304
305/// Construct frames a render may push before source tracking stops recording enclosing constructs.
306const SRC_STACK_CAP: usize = 1 << 20;
307
308/// Request mode of a `\filesize` probe, which reads a resource only to measure it and may find nothing.
309pub const FILE_SIZE_PROBE_MODE: &str = "size";
310
311/// Sandbox message for a fragment that leaves or reenters the wrapper math.
312const MATH_BREAKOUT_MESSAGE: &str = "math shift ($) is not allowed inside a math expression";
313
314/// Result of engine code that can stop the run, generated bodies shadow `Result` so this alias names it.
315pub(crate) type EngineFlow<T> = core::result::Result<T, EngineBreak>;
316pub(crate) const nullptr: voidpointer = core::ptr::null_mut::<()>();
317pub(crate) const nil: voidpointer = core::ptr::null_mut::<()>();
318pub(crate) const maxint: integer = i32::MAX;
319pub(crate) const mintrieop: integer = 0;
320pub(crate) const trieopsize: i64 = 35111;
321pub(crate) const negtrieopsize: i64 = -35111;
322pub(crate) const maxtrieop: i64 = 65535;
323pub(crate) const hashoffset: integer = 514;
324pub(crate) const DIR_SEP: integer = b'/' as integer;
325pub(crate) const resource_format_tex_input: integer = 26;
326pub(crate) const resource_format_tfm: integer = 3;
327pub(crate) const resource_format_format_image: integer = 10;
328pub(crate) const resource_format_config: integer = 8;
329pub(crate) const resource_format_font_map: integer = 11;
330pub(crate) const resource_format_encoding: integer = 44;
331pub(crate) const resource_format_font: integer = 47;
332pub(crate) const FOPEN_RBIN_MODE: [i8; 3] = [b'r' as i8, b'b' as i8, 0];
333
334#[derive(Clone, Debug, PartialEq, Eq)]
335pub struct PortableSourceSpan {
336    pub name: String,
337    pub start: u32,
338    pub end: u32,
339    /// Provenance role, the IR `SourceRole` discriminant: 0 primary, 1 macro expansion.
340    pub role: u8,
341}
342
343/// Interned source span id, 0 is none and real spans start at 1, indexing `src_spans` off by one.
344pub(crate) type SrcId = u32;
345
346/// One recorded span, byte offsets into the input file named by string number `name`, hashed whole for interning.
347#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
348pub(crate) struct RawSpan {
349    pub name: strnumber,
350    pub start: u32,
351    pub end: u32,
352    pub role: u8,
353}
354
355/// `default_fn` for the `node_src` paged shadow: an unstamped node has span id 0.
356fn node_src_default(_i: usize) -> u32 {
357    0
358}
359
360/// Live byte range of a `node_src` word, the whole `u32`.
361fn node_src_sig(_i: usize) -> (usize, usize) {
362    (0, 4)
363}
364
365/// An enclosing construct frame, a macro call or math group linked to its parent, pending until its group closes.
366#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
367pub(crate) struct SrcStackCell {
368    /// Interned span of the finished construct, 0 while pending or empty.
369    pub span: SrcId,
370    /// Parent frame index counted from 1, 0 for the root.
371    pub parent: u32,
372    /// Pending group: input byte offset of the enclosing command's start.
373    pub start: u32,
374    /// Pending group: string number of the source file the offsets are in.
375    pub name: strnumber,
376    /// True while a group frame is open (its end offset is not yet known).
377    pub pending: bool,
378}
379
380/// `default_fn` for the `node_stack` paged shadow: an unstamped node has no enclosing frame (`0`).
381fn node_stack_default(_i: usize) -> u32 {
382    0
383}
384
385/// Live byte range of a `node_stack` word, the whole `u32`.
386fn node_stack_sig(_i: usize) -> (usize, usize) {
387    (0, 4)
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq)]
391pub enum ResourceKind {
392    TexInput,
393    Package,
394    Class,
395    FontDefinition,
396    PackageSupport,
397    Font,
398    Encoding,
399    Map,
400    Config,
401    FormatImage,
402    Asset,
403    Other(integer),
404}
405
406#[derive(Clone, Debug, PartialEq, Eq)]
407pub struct ResourceRequest<'a> {
408    pub name: &'a str,
409    pub kind: ResourceKind,
410    pub package: Option<&'a str>,
411    pub format: integer,
412    pub mode: &'a str,
413    pub source: Option<PortableSourceSpan>,
414}
415
416#[derive(Clone, Debug, PartialEq, Eq)]
417pub struct PortableResourceRequestRecord {
418    pub name: String,
419    pub kind: ResourceKind,
420    pub package: Option<String>,
421    pub format: integer,
422    pub mode: String,
423    pub source: Option<PortableSourceSpan>,
424    pub byte_len: Option<u32>,
425}
426
427#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
428pub struct PortableFontMetrics {
429    pub ascent: i32,
430    pub descent: i32,
431    pub xheight: i32,
432    pub capheight: i32,
433    pub slant: i32,
434}
435
436#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
437pub struct PortableNativeGlyph {
438    pub glyph_id: u16,
439    pub x: i32,
440    pub y: i32,
441    pub advance: i32,
442    pub cluster_start: u32,
443    pub cluster_end: u32,
444    /// Input byte span of the characters that shaped this glyph, `0..0` when unmapped.
445    pub src_start: u32,
446    pub src_end: u32,
447}
448
449#[derive(Clone, Debug, Default, PartialEq, Eq)]
450pub struct PortableNativeTextMetrics {
451    pub width: i32,
452    pub height: i32,
453    pub depth: i32,
454    pub glyphs: Vec<PortableNativeGlyph>,
455}
456
457#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
458pub struct PortableNativeGlyphMetrics {
459    pub width: i32,
460    pub height: i32,
461    pub depth: i32,
462}
463
464/// A glyph's advance and control box in points, y up, as XeTeX's `getGlyphWidth` and `getGlyphBounds` give them.
465#[derive(Clone, Copy, Debug, Default, PartialEq)]
466pub struct PortableGlyphBounds {
467    /// Horizontal advance.
468    pub advance: f32,
469    /// Left edge of the control box.
470    pub x_min: f32,
471    /// Bottom edge of the control box.
472    pub y_min: f32,
473    /// Right edge of the control box.
474    pub x_max: f32,
475    /// Top edge of the control box.
476    pub y_max: f32,
477}
478
479#[derive(Clone, Debug, Default, PartialEq, Eq)]
480struct PortableNativeGlyphInfo {
481    glyphs: Vec<PortableNativeGlyph>,
482}
483
484/// A larger OpenType MATH glyph variant, its advance in scaled points and `-1` when there is none.
485#[derive(Clone, Copy, Debug, PartialEq, Eq)]
486pub struct PortableMathVariant {
487    pub glyph: i32,
488    pub advance: i32,
489}
490
491/// One part of an OpenType MATH glyph assembly in scaled points, as `hb_ot_math_glyph_part_t`.
492#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
493pub struct PortableMathAssemblyPart {
494    pub glyph: i32,
495    pub start_connector: i32,
496    pub end_connector: i32,
497    pub full_advance: i32,
498    pub extender: bool,
499}
500
501/// A corner of an OpenType `MathKernInfo` record (mirrors `hb_ot_math_kern_t`).
502#[derive(Clone, Copy, Debug, PartialEq, Eq)]
503pub enum PortableMathKernCorner {
504    TopRight,
505    TopLeft,
506    BottomRight,
507    BottomLeft,
508}
509
510/// OpenType MATH glyph assembly passed to the engine as an opaque pointer, freed by `free_ot_assembly`.
511#[derive(Clone, Debug, Default, PartialEq, Eq)]
512pub struct GlyphAssembly {
513    pub parts: Vec<PortableMathAssemblyPart>,
514}
515
516pub trait FontPlatform {
517    fn resolve_font_handle(&mut self, _name: &[i32], _size: i32) -> Option<PortableFontHandle> {
518        None
519    }
520
521    fn release_font_handle(&mut self, _font: PortableFontHandle, _type_flag: i32) {}
522
523    /// The native font table as `(handle, spec, size)`, which lets a loaded format image rebind its font handles.
524    fn font_table(&self) -> Vec<(PortableFontHandle, String, i32)> {
525        Vec::new()
526    }
527
528    /// Reloads each font of a [`Self::font_table`] snapshot at its original handle, false when one fails.
529    fn restore_font_table(&mut self, _table: &[(PortableFontHandle, String, i32)]) -> bool {
530        true
531    }
532
533    fn font_metrics(&mut self, _font: PortableFontHandle) -> PortableFontMetrics {
534        PortableFontMetrics::default()
535    }
536
537    fn opentype_font_metrics(&mut self, font: PortableFontHandle) -> PortableFontMetrics {
538        self.font_metrics(font)
539    }
540
541    fn is_opentype_math_font(&mut self, _font: PortableFontHandle) -> bool {
542        false
543    }
544
545    /// Whether `font` is shaped by the OpenType shaper, XeTeX's `usingOpenType`.
546    fn using_opentype(&mut self, _font: PortableFontHandle) -> bool {
547        false
548    }
549
550    fn math_symbol_parameter(&mut self, _font: PortableFontHandle, _parameter: i32) -> i32 {
551        0
552    }
553
554    fn math_extension_parameter(&mut self, _font: PortableFontHandle, _parameter: i32) -> i32 {
555        0
556    }
557
558    fn opentype_math_constant(&mut self, _font: PortableFontHandle, _constant: i32) -> i32 {
559        0
560    }
561
562    fn opentype_math_accent_position(&mut self, _font: PortableFontHandle, _glyph: i32) -> i32 {
563        0
564    }
565
566    /// OpenType MATH italic correction for a glyph, in scaled points.
567    fn math_glyph_italic_correction(&mut self, _font: PortableFontHandle, _glyph: i32) -> i32 {
568        0
569    }
570
571    /// The `index`th larger MATH variant of `glyph`, horizontal or vertical, `None` when there is none.
572    fn math_glyph_variant(
573        &mut self,
574        _font: PortableFontHandle,
575        _glyph: i32,
576        _index: u16,
577        _horizontal: bool,
578    ) -> Option<PortableMathVariant> {
579        None
580    }
581
582    /// The MATH assembly parts for `glyph` in scaled points, empty when it has none.
583    fn math_glyph_assembly(
584        &mut self,
585        _font: PortableFontHandle,
586        _glyph: i32,
587        _horizontal: bool,
588    ) -> Vec<PortableMathAssemblyPart> {
589        Vec::new()
590    }
591
592    /// The MATH minimum connector overlap for assembly parts, in scaled points.
593    fn math_min_connector_overlap(&mut self, _font: PortableFontHandle) -> i32 {
594        0
595    }
596
597    /// One MATH `MathKernInfo` corner at `correction_height`, both in font design units.
598    fn math_kern_at(
599        &mut self,
600        _font: PortableFontHandle,
601        _glyph: i32,
602        _corner: PortableMathKernCorner,
603        _correction_height: i32,
604    ) -> i32 {
605        0
606    }
607
608    /// Converts points to font design units for `font`, XeTeX's `pointsToUnits`.
609    fn math_points_to_units(&mut self, _font: PortableFontHandle, _points: f32) -> f32 {
610        0.0
611    }
612
613    /// Converts font design units to scaled points for `font`, XeTeX's `D2Fix(unitsToPoints(...))`.
614    fn math_units_to_scaled(&mut self, _font: PortableFontHandle, _units: i32) -> i32 {
615        0
616    }
617
618    /// The point size at which `font` was loaded (`getPointSize`).
619    fn math_point_size(&mut self, _font: PortableFontHandle) -> f32 {
620        0.0
621    }
622
623    fn map_char_to_glyph(&mut self, _font: PortableFontHandle, _codepoint: i32) -> i32 {
624        0
625    }
626
627    fn map_glyph_to_index(&mut self, _font: PortableFontHandle, _name: &str) -> i32 {
628        0
629    }
630
631    /// OpenType layout query of `\XeTeXcountglyphs` and the `\XeTeXOT` primitives, `what` is the XeTeX_ext code.
632    fn ot_font_get(
633        &mut self,
634        _font: PortableFontHandle,
635        _what: i32,
636        _param1: i32,
637        _param2: i32,
638        _param3: i32,
639    ) -> i32 {
640        0
641    }
642
643    /// The `\font` spec a handle was loaded from, such as `[latinmodern-math.otf]:script=math;ssty=1`.
644    fn font_spec(&self, _font: PortableFontHandle) -> Option<String> {
645        None
646    }
647
648    /// The key the host's font loader gave the face behind a handle.
649    fn font_key(&self, _font: PortableFontHandle) -> Option<u64> {
650        None
651    }
652
653    fn shape_native_text(
654        &mut self,
655        _font: PortableFontHandle,
656        _text: &[u16],
657        _use_glyph_metrics: bool,
658    ) -> PortableNativeTextMetrics {
659        PortableNativeTextMetrics::default()
660    }
661
662    fn measure_native_glyph(
663        &mut self,
664        _font: PortableFontHandle,
665        _glyph: u16,
666        _use_glyph_metrics: bool,
667    ) -> PortableNativeGlyphMetrics {
668        PortableNativeGlyphMetrics::default()
669    }
670
671    /// Advance and control box of `glyph` at the font's size, all zero for a glyph without an outline.
672    fn glyph_bounds(&mut self, _font: PortableFontHandle, _glyph: u16) -> PortableGlyphBounds {
673        PortableGlyphBounds::default()
674    }
675
676    /// Smallest and largest character codes the font maps to a glyph, as XeTeX's `getFontCharRange`.
677    fn char_code_range(&mut self, _font: PortableFontHandle) -> Option<(i32, i32)> {
678        None
679    }
680}
681
682#[derive(Default)]
683pub struct EmptyFontPlatform;
684
685impl FontPlatform for EmptyFontPlatform {}
686
687pub trait ResourceProvider {
688    fn read(&mut self, request: ResourceRequest<'_>) -> Option<Vec<u8>>;
689}
690
691#[derive(Default)]
692pub struct EmptyResourceProvider;
693
694impl ResourceProvider for EmptyResourceProvider {
695    fn read(&mut self, _request: ResourceRequest<'_>) -> Option<Vec<u8>> {
696        None
697    }
698}
699
700#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
701pub struct PortableClock {
702    pub seconds: integer,
703    pub micros: integer,
704}
705
706#[derive(Clone, Copy, Debug, PartialEq, Eq)]
707pub struct PortableLinebreakRequest<'a> {
708    pub font: integer,
709    pub locale: integer,
710    pub text: &'a [uint16_t],
711}
712
713// Style context for a host box request, display math collapses to Text to match cursize.
714#[derive(Clone, Copy, Debug, PartialEq, Eq)]
715pub enum PortableHostBoxStyle {
716    Text,
717    Script,
718    ScriptScript,
719}
720
721#[derive(Clone, Copy, Debug, PartialEq, Eq)]
722pub struct PortableHostBoxRequest {
723    pub token: integer,
724    pub style: PortableHostBoxStyle,
725    pub font_size: scaled,
726}
727
728// Glyph offsets are scaled points from the box baseline origin, y positive downward.
729#[derive(Clone, Copy, Debug, PartialEq, Eq)]
730pub struct PortableHostBoxGlyph {
731    pub glyph: u32,
732    pub x: scaled,
733    pub y: scaled,
734}
735
736#[derive(Clone, Debug, PartialEq)]
737pub struct PortableHostBoxRun {
738    /// The key the host's font loader gave the run's face.
739    pub font_key: u64,
740    pub font_size: scaled,
741    pub glyphs: Vec<PortableHostBoxGlyph>,
742}
743
744// Rule origin is its bottom left corner with y positive downward, height extends upward.
745#[derive(Clone, Copy, Debug, PartialEq, Eq)]
746pub struct PortableHostBoxRule {
747    pub x: scaled,
748    pub y: scaled,
749    pub width: scaled,
750    pub height: scaled,
751}
752
753#[derive(Clone, Debug, PartialEq)]
754pub struct PortableHostBox {
755    pub width: scaled,
756    pub height: scaled,
757    pub depth: scaled,
758    pub runs: Vec<PortableHostBoxRun>,
759    pub rules: Vec<PortableHostBoxRule>,
760}
761
762// \Uhostbox is extension cmd 59 with this chr, upstream extension codes stop at 46.
763pub(crate) const HOST_BOX_EXTENSION_CODE: i32 = 91;
764// Marker whatsit subtypes: pending carries the opaque token id, resolved carries a record index.
765pub(crate) const HOST_BOX_PENDING_SUBTYPE: i32 = 91;
766pub(crate) const HOST_BOX_RESOLVED_SUBTYPE: i32 = 92;
767/// Words in a host box marker whatsit, laid out like a glyph node.
768pub(crate) const HOST_BOX_MARKER_SIZE: i32 = 5;
769
770pub trait PortablePlatform {
771    fn clock(&mut self) -> PortableClock {
772        PortableClock::default()
773    }
774
775    fn linebreak_start(&mut self, _request: PortableLinebreakRequest<'_>) {}
776
777    fn linebreak_next(&mut self) -> Option<integer> {
778        None
779    }
780
781    fn host_box(&mut self, _request: PortableHostBoxRequest) -> Option<PortableHostBox> {
782        None
783    }
784}
785
786#[derive(Default)]
787pub struct EmptyPlatform;
788
789impl PortablePlatform for EmptyPlatform {}
790
791impl PortableFileHandle {
792    fn new(name: String, package: Option<String>, format: integer, bytes: Vec<u8>) -> Self {
793        Self {
794            name,
795            package,
796            format,
797            bytes,
798            cursor: 0,
799            eof_after_failed_read: false,
800            encoding: InputEncoding::Bytes,
801            saved_char: None,
802        }
803    }
804
805    fn read_byte(&mut self) -> Option<u8> {
806        let Some(byte) = self.bytes.get(self.cursor).copied() else {
807            self.eof_after_failed_read = true;
808            return None;
809        };
810        self.cursor += 1;
811        Some(byte)
812    }
813
814    fn has_remaining(&self) -> bool {
815        self.cursor < self.bytes.len() || self.saved_char.is_some()
816    }
817
818    /// Byte offset where the next scalar starts, a held back UTF-16 unit began two bytes before the cursor.
819    fn next_scalar_offset(&self) -> usize {
820        if self.saved_char.is_some() {
821            self.cursor.saturating_sub(2)
822        } else {
823            self.cursor
824        }
825    }
826
827    fn is_eof(&self) -> bool {
828        self.eof_after_failed_read
829    }
830
831    /// XeTeX `u_open_in` auto detection: a BOM or leading zero byte picks UTF-16, else UTF-8, skipping a BOM.
832    fn resolve_text_encoding_auto(&mut self) {
833        let b1 = self.bytes.first().copied();
834        let b2 = self.bytes.get(1).copied();
835        match (b1, b2) {
836            (Some(0xFE), Some(0xFF)) => {
837                self.encoding = InputEncoding::Utf16Be;
838                self.cursor = 2;
839            }
840            (Some(0xFF), Some(0xFE)) => {
841                self.encoding = InputEncoding::Utf16Le;
842                self.cursor = 2;
843            }
844            (Some(0x00), Some(b2)) if b2 != 0 => {
845                self.encoding = InputEncoding::Utf16Be;
846            }
847            (Some(b1), Some(0x00)) if b1 != 0 => {
848                self.encoding = InputEncoding::Utf16Le;
849            }
850            (Some(0xEF), Some(0xBB)) if self.bytes.get(2).copied() == Some(0xBF) => {
851                self.encoding = InputEncoding::Utf8;
852                self.cursor = 3;
853            }
854            _ => {
855                self.encoding = InputEncoding::Utf8;
856            }
857        }
858    }
859
860    /// Next Unicode scalar, `None` at the end of input, XeTeX's `get_uni_c`.
861    fn next_input_scalar(&mut self) -> Option<u32> {
862        // savedChar lookahead (only set by the UTF-16 decoders).
863        if let Some(saved) = self.saved_char.take() {
864            return Some(saved);
865        }
866        match self.encoding {
867            InputEncoding::Bytes => self.read_byte().map(u32::from),
868            InputEncoding::Utf8 => self.next_utf8_scalar(),
869            InputEncoding::Utf16Be => self.next_utf16_scalar(true),
870            InputEncoding::Utf16Le => self.next_utf16_scalar(false),
871        }
872    }
873
874    /// UTF-8 branch of `get_uni_c`: a bad continuation byte is pushed back and read as U+FFFD.
875    fn next_utf8_scalar(&mut self) -> Option<u32> {
876        // offsetsFromUTF8[extraBytes].
877        const OFFSETS: [u32; 6] = [
878            0x0000_0000,
879            0x0000_3080,
880            0x000E_2080,
881            0x03C8_2080,
882            0xFA08_2080,
883            0x8208_2080,
884        ];
885        let lead = self.read_byte()?;
886        // bytesFromUTF8[lead]: extra continuation bytes (0..=5).
887        let extra: usize = match lead {
888            0x00..=0xBF => 0,
889            0xC0..=0xDF => 1,
890            0xE0..=0xEF => 2,
891            0xF0..=0xF7 => 3,
892            0xF8..=0xFB => 4,
893            0xFC..=0xFF => 5,
894        };
895        // Assemble in a wider type to mirror C's `int rval` (continuations <<6).
896        let mut rval: i64 = i64::from(lead);
897        // C `switch(extraBytes)` falls through cases 3, 2 and 1, cases 4 and 5 return the bad result at once.
898        if extra >= 4 {
899            // Lead bytes 0xF8 to 0xFF start no valid sequence.
900            return Some(0xFFFD);
901        }
902        for _ in 0..extra {
903            match self.read_byte() {
904                Some(c) if (0x80..0xC0).contains(&c) => {
905                    rval = (rval << 6) + i64::from(c);
906                }
907                Some(c) => {
908                    // C `bad_utf8` pushes the offending byte back with `UNGETC`.
909                    self.cursor -= 1;
910                    let _ = c;
911                    return Some(0xFFFD);
912                }
913                None => {
914                    // End of input inside a sequence is `bad_utf8` with nothing to push back.
915                    return Some(0xFFFD);
916                }
917            }
918        }
919        rval -= i64::from(OFFSETS[extra]);
920        if !(0..=0x10_FFFF).contains(&rval) {
921            return Some(0xFFFD);
922        }
923        Some(rval as u32)
924    }
925
926    /// UTF-16 branch of `get_uni_c`: an unpaired surrogate reads as U+FFFD, a stray unit after one is kept.
927    fn next_utf16_scalar(&mut self, big_endian: bool) -> Option<u32> {
928        let unit = self.read_utf16_unit(big_endian)?;
929        if (0xD800..=0xDBFF).contains(&unit) {
930            match self.read_utf16_unit(big_endian) {
931                Some(lo) if (0xDC00..=0xDFFF).contains(&lo) => {
932                    Some(0x10000 + (unit - 0xD800) * 0x400 + (lo - 0xDC00))
933                }
934                Some(lo) => {
935                    self.saved_char = Some(lo);
936                    Some(0xFFFD)
937                }
938                None => Some(0xFFFD),
939            }
940        } else if (0xDC00..=0xDFFF).contains(&unit) {
941            Some(0xFFFD)
942        } else {
943            Some(unit)
944        }
945    }
946
947    /// One UTF-16 code unit, `None` only at end of input, a missing second byte reads as 0 as C's `GETC` does.
948    fn read_utf16_unit(&mut self, big_endian: bool) -> Option<u32> {
949        let first = self.read_byte()?;
950        let second = self.read_byte().unwrap_or(0);
951        let unit = if big_endian {
952            (u32::from(first) << 8) + u32::from(second)
953        } else {
954            u32::from(first) + (u32::from(second) << 8)
955        };
956        Some(unit)
957    }
958}
959
960// Mirrors the slice methods generated code calls on storage fields, which are raw pointers here.
961#[allow(clippy::wrong_self_convention)]
962pub(crate) trait StatePtrCompat<T> {
963    fn as_mut_ptr(self) -> *mut T;
964}
965
966impl<T> StatePtrCompat<T> for *mut T {
967    fn as_mut_ptr(self) -> *mut T {
968        self
969    }
970}
971
972#[derive(Copy, Clone)]
973#[repr(C)]
974pub(crate) union twohalves {
975    pub v: TwoHalvesPair,
976    pub u: TwoHalvesBytes,
977}
978
979impl Default for twohalves {
980    fn default() -> Self {
981        Self {
982            v: TwoHalvesPair::default(),
983        }
984    }
985}
986
987#[derive(Copy, Clone, Default)]
988#[repr(C)]
989pub(crate) struct TwoHalvesBytes {
990    pub B1: i16,
991    pub B0: i16,
992}
993
994#[derive(Copy, Clone, Default)]
995#[repr(C)]
996pub(crate) struct TwoHalvesPair {
997    pub LH: halfword,
998    pub RH: halfword,
999}
1000
1001#[derive(Copy, Clone, Default)]
1002#[repr(C)]
1003pub(crate) struct fourquarters {
1004    pub u: FourQuarterBytes,
1005}
1006
1007// XeTeX quarterwords are 16 bit, `u16` keeps a memory word at 8 bytes and `FourQuarterCastPass` casts accesses.
1008#[derive(Copy, Clone, Default)]
1009#[repr(C)]
1010pub(crate) struct FourQuarterBytes {
1011    pub B3: u16,
1012    pub B2: u16,
1013    pub B1: u16,
1014    pub B0: u16,
1015}
1016
1017pub(crate) type C2RustUnnamed_2 = FourQuarterBytes;
1018
1019#[derive(Copy, Clone)]
1020#[repr(C)]
1021pub(crate) union memoryword {
1022    pub gr: glueratio,
1023    pub hh: twohalves,
1024    pub u: MemoryInt,
1025    pub v: MemoryQuarters,
1026    /// All eight bytes, XeTeX's native glyph info pointer lives here and this engine keeps it zero.
1027    pub raw: u64,
1028}
1029
1030// Images copy words byte for byte, so a word is the same 8 bytes on every target.
1031const _: () = assert!(core::mem::size_of::<memoryword>() == 8);
1032
1033impl Default for memoryword {
1034    fn default() -> Self {
1035        Self { raw: 0 }
1036    }
1037}
1038
1039/// Whether word 5 of a native word node holds XeTeX's glyph info pointer, this engine keeps it zero.
1040pub(crate) fn native_glyph_word_is_null(word: &memoryword) -> bool {
1041    // Sound because every view of the union is plain data.
1042    let raw = unsafe { word.raw };
1043    debug_assert_eq!(raw, 0, "a native word node's glyph info word is nonzero");
1044    raw == 0
1045}
1046
1047// XeTeX packs the quarters and the integer at offset 0 of an 8 byte memory word, as this layout does.
1048#[derive(Copy, Clone, Default)]
1049#[repr(C)]
1050pub(crate) struct MemoryQuarters {
1051    pub QQQQ: fourquarters,
1052}
1053
1054#[derive(Copy, Clone, Default)]
1055#[repr(C)]
1056pub(crate) struct MemoryInt {
1057    pub CINT: integer,
1058}
1059
1060#[derive(Copy, Clone)]
1061#[repr(C)]
1062pub(crate) union fmemoryword {
1063    pub u: FontMemoryInt,
1064    pub v: FontMemoryQuarters,
1065}
1066
1067// Zeroes all eight bytes through the widest view, format images copy words byte for byte.
1068impl Default for fmemoryword {
1069    fn default() -> Self {
1070        Self {
1071            v: FontMemoryQuarters::default(),
1072        }
1073    }
1074}
1075
1076#[derive(Copy, Clone, Default)]
1077#[repr(C)]
1078pub(crate) struct FontMemoryQuarters {
1079    pub QQQQ: fourquarters,
1080}
1081
1082#[derive(Copy, Clone, Default)]
1083#[repr(C)]
1084pub(crate) struct FontMemoryInt {
1085    pub CINT: integer,
1086}
1087
1088#[derive(Copy, Clone, Default)]
1089#[repr(C)]
1090pub(crate) struct liststaterecord {
1091    pub modefield: i16,
1092    pub headfield: halfword,
1093    pub tailfield: halfword,
1094    pub eTeXauxfield: halfword,
1095    pub pgfield: integer,
1096    pub mlfield: integer,
1097    pub auxfield: memoryword,
1098}
1099
1100#[derive(Copy, Clone, Default)]
1101#[repr(C)]
1102pub(crate) struct instaterecord {
1103    pub statefield: quarterword,
1104    pub indexfield: quarterword,
1105    pub startfield: halfword,
1106    pub locfield: halfword,
1107    pub limitfield: halfword,
1108    pub namefield: halfword,
1109    /// Span this input level inherits, saved and restored with the whole record by input stack pushes and pops.
1110    pub spanfield: SrcId,
1111}
1112
1113#[derive(Copy, Clone, Default)]
1114#[repr(C)]
1115pub(crate) struct ResourceSearchState {
1116    pub make_tex_discard_errors: boolean,
1117}
1118
1119// `eqtb` and `hash` hold a slot per code point in each code table, so both fault pages in from their defaults.
1120const PAGE_BITS: usize = 12;
1121const PAGE_LEN: usize = 1 << PAGE_BITS;
1122
1123/// Live byte range of the `eqtb` word at `i`, the integer and delimiter code regions only use `.u.CINT`.
1124fn eqtb_sig_range(i: usize) -> (usize, usize) {
1125    if (INT_BASE..=EQTB_SIZE).contains(&i) {
1126        (0, 4)
1127    } else {
1128        (0, 8)
1129    }
1130}
1131
1132/// `hash` words are `two_halves`, all eight bytes are live.
1133fn hash_sig_range(_i: usize) -> (usize, usize) {
1134    (0, 8)
1135}
1136
1137/// Compares two values over bytes `off..off + len`, the dead bytes of a word may hold residue.
1138fn paged_bytes_eq<T>(a: &T, b: &T, off: usize, len: usize) -> bool {
1139    // Sound because `off + len` stays within `size_of::<T>()`, a read only byte view of two plain values.
1140    unsafe {
1141        let pa = (a as *const T as *const u8).add(off);
1142        let pb = (b as *const T as *const u8).add(off);
1143        core::slice::from_raw_parts(pa, len) == core::slice::from_raw_parts(pb, len)
1144    }
1145}
1146
1147/// An array over absolute indices `base..end` stored as lazily faulted pages.
1148pub(crate) struct PagedArray<T: Copy + Default> {
1149    base: usize,
1150    end: usize,
1151    /// Page `n` covers `base + n * PAGE_LEN`, an absent page reads as its defaults.
1152    pages: Vec<Option<Box<[T]>>>,
1153    /// Value at an absolute index whose page is absent.
1154    default_fn: fn(usize) -> T,
1155    /// Live byte range `(off, len)` of the value at an absolute index, the rest never counts as a change.
1156    sig_range: fn(usize) -> (usize, usize),
1157}
1158
1159impl<T: Copy + Default> PagedArray<T> {
1160    fn new(
1161        base: usize,
1162        end: usize,
1163        default_fn: fn(usize) -> T,
1164        sig_range: fn(usize) -> (usize, usize),
1165    ) -> Self {
1166        let npages = (end - base).div_ceil(PAGE_LEN);
1167        PagedArray {
1168            base,
1169            end,
1170            pages: (0..npages).map(|_| None).collect(),
1171            default_fn,
1172            sig_range,
1173        }
1174    }
1175
1176    /// Pointer to the value at `abs`, faulting its page in, `abs` must lie in `base..end`.
1177    #[inline]
1178    fn ptr(&mut self, abs: usize) -> *mut T {
1179        let rel = abs - self.base;
1180        let pg = rel >> PAGE_BITS;
1181        let off = rel & (PAGE_LEN - 1);
1182        if self.pages[pg].is_none() {
1183            let page_base = self.base + pg * PAGE_LEN;
1184            let f = self.default_fn;
1185            let page = (0..PAGE_LEN).map(|j| f(page_base + j)).collect::<Vec<_>>();
1186            self.pages[pg] = Some(page.into_boxed_slice());
1187        }
1188        // Sound because the page was just made present and `off < PAGE_LEN`.
1189        unsafe {
1190            self.pages[pg]
1191                .as_mut()
1192                .unwrap_unchecked()
1193                .as_mut_ptr()
1194                .add(off)
1195        }
1196    }
1197
1198    /// Frees every faulted page whose live bytes still equal its defaults.
1199    fn compact(&mut self) {
1200        let f = self.default_fn;
1201        for pg in 0..self.pages.len() {
1202            let page_base = self.base + pg * PAGE_LEN;
1203            let is_default = match &self.pages[pg] {
1204                None => continue,
1205                Some(page) => (0..PAGE_LEN).all(|j| {
1206                    let (off, len) = (self.sig_range)(page_base + j);
1207                    paged_bytes_eq(&page[j], &f(page_base + j), off, len)
1208                }),
1209            };
1210            if is_default {
1211                self.pages[pg] = None;
1212            }
1213        }
1214    }
1215
1216    fn resident_bytes(&self) -> usize {
1217        let elt = core::mem::size_of::<T>();
1218        self.pages.iter().filter(|p| p.is_some()).count() * PAGE_LEN * elt
1219            + self.pages.capacity() * core::mem::size_of::<Option<Box<[T]>>>()
1220    }
1221
1222    /// Writes `v` at `abs`, ignoring indices outside the array and leaving an absent page absent for its default.
1223    #[inline]
1224    fn set(&mut self, abs: usize, v: T)
1225    where
1226        T: PartialEq,
1227    {
1228        if abs < self.base || abs >= self.end {
1229            return;
1230        }
1231        if self.pages[(abs - self.base) >> PAGE_BITS].is_none() && v == (self.default_fn)(abs) {
1232            return;
1233        }
1234        // Sound because `abs` lies in `base..end`.
1235        unsafe {
1236            *self.ptr(abs) = v;
1237        }
1238    }
1239
1240    /// Reads the value at `abs` without faulting its page, so the read only snapshot path can use it.
1241    #[inline]
1242    fn get_copy(&self, abs: usize) -> T {
1243        if abs < self.base || abs >= self.end {
1244            return (self.default_fn)(abs);
1245        }
1246        let rel = abs - self.base;
1247        match self.pages.get(rel >> PAGE_BITS).and_then(|p| p.as_ref()) {
1248            Some(page) => page[rel & (PAGE_LEN - 1)],
1249            None => (self.default_fn)(abs),
1250        }
1251    }
1252}
1253
1254impl<T: Copy + Default> Clone for PagedArray<T> {
1255    fn clone(&self) -> Self {
1256        PagedArray {
1257            base: self.base,
1258            end: self.end,
1259            pages: self.pages.clone(),
1260            default_fn: self.default_fn,
1261            sig_range: self.sig_range,
1262        }
1263    }
1264}
1265
1266// An empty array never consults its default, allocation and decoding install the real one.
1267impl<T: Copy + Default> Default for PagedArray<T> {
1268    fn default() -> Self {
1269        PagedArray::new(0, 0, |_| T::default(), |_| (0, core::mem::size_of::<T>()))
1270    }
1271}
1272
1273/// A `Copy` handle to a [`PagedArray`] that generated code indexes like the raw `eqtb` and `hash` pointers.
1274pub(crate) struct PagedView<T: Copy + Default>(*mut PagedArray<T>);
1275
1276impl<T: Copy + Default> Clone for PagedView<T> {
1277    fn clone(&self) -> Self {
1278        *self
1279    }
1280}
1281impl<T: Copy + Default> Copy for PagedView<T> {}
1282
1283impl<T: Copy + Default> PagedView<T> {
1284    pub(crate) fn new(arr: &mut PagedArray<T>) -> Self {
1285        PagedView(arr as *mut PagedArray<T>)
1286    }
1287    #[inline]
1288    pub(crate) fn as_mut_ptr(self) -> Self {
1289        self
1290    }
1291    /// Pointer to the absolute index `count`, generated code computes the whole index before one offset.
1292    #[inline]
1293    pub(crate) fn offset(self, count: isize) -> *mut T {
1294        // Sound because the view is rebound to live storage before generated code runs, as raw array pointers are.
1295        unsafe { (*self.0).ptr(count as usize) }
1296    }
1297}
1298
1299/// `eqtb` word XeTeX's `initialize` leaves at `i`, the code tables have per table defaults.
1300fn eqtb_codepoint_default(i: usize) -> memoryword {
1301    // Start from a zeroed word so compaction compares no indeterminate bytes.
1302    let mut w: memoryword = unsafe { core::mem::zeroed() };
1303    if (CAT_CODE_BASE..INT_BASE).contains(&i) {
1304        let equiv = if i < LC_CODE_BASE {
1305            CMD_OTHER_CHAR
1306        } else if (SF_CODE_BASE..MATH_CODE_BASE).contains(&i) {
1307            1000
1308        } else if (MATH_CODE_BASE..CHAR_SUB_CODE_BASE).contains(&i) {
1309            (i - MATH_CODE_BASE) as i32
1310        } else {
1311            0
1312        };
1313        w.hh.u.B0 = EQ_TYPE_DATA;
1314        w.hh.u.B1 = LEVEL_ONE;
1315        w.hh.v.RH = equiv;
1316    } else if (DEL_CODE_BASE..DIMEN_BASE).contains(&i) {
1317        w.u.CINT = -1;
1318    } else if !(CAT_CODE_BASE..=EQTB_SIZE).contains(&i) {
1319        // Control sequence meanings, below the code tables and in the `hash_extra` region, start undefined.
1320        w.hh.u.B0 = EQ_TYPE_UNDEFINED_CS;
1321        w.hh.v.RH = TEX_NULL;
1322    }
1323    w
1324}
1325
1326/// `hash` word for an untouched slot.
1327fn hash_codepoint_default(_i: usize) -> twohalves {
1328    twohalves::default()
1329}
1330
1331/// Declares the plain data engine state and its field wise format image codec from one field list.
1332macro_rules! portable_state {
1333    ($($(#[$meta:meta])* $vis:vis $field:ident: $ty:ty,)*) => {
1334        /// Plain data engine state, copied bitwise, every heap owning array lives in [`PortableTexHeap`].
1335        #[derive(Clone, Copy)]
1336        pub(crate) struct PortableTexState {
1337            $($(#[$meta])* $vis $field: $ty,)*
1338        }
1339
1340        impl PortableTexState {
1341            fn encode_fields(&self, out: &mut Vec<u8>) {
1342                $(<$ty as ImageCodec>::encode(core::slice::from_ref(&self.$field), out);)*
1343            }
1344
1345            /// Decodes every field in declaration order into `state`, which must be writable.
1346            unsafe fn decode_fields(
1347                state: *mut Self,
1348                input: &mut ImageReader<'_>,
1349            ) -> Result<(), FormatImageError> {
1350                $(
1351                    <$ty as ImageCodec>::decode(core::ptr::addr_of_mut!((*state).$field), 1, input)
1352                        .map_err(|error| error.in_field(stringify!($field)))?;
1353                )*
1354                Ok(())
1355            }
1356        }
1357    };
1358}
1359
1360portable_state! {
1361    /// Set once `initialize_format_state` completes, every run entry point refuses a state without it.
1362    initialized: bool,
1363    /// Source tracking master switch, every hook returns at once while it is false.
1364    source_tracking: bool,
1365    /// Span of the command currently executing, latched at the `main_control` dispatch boundary.
1366    cmd_span: SrcId,
1367    /// Call site span carried from `macro_call` to the macro body `begin_token_list`.
1368    pending_call_span: SrcId,
1369    /// Buffer offset of the token being lexed, captured at the top of the `get_next` loop.
1370    src_token_start: integer,
1371    /// String number of the primary input's name.
1372    src_primary_name: strnumber,
1373    /// `macro_call` scratch: input byte offset of the invoking control sequence.
1374    src_call_start: u32,
1375    /// `macro_call` scratch: name field of the level the macro was read from.
1376    src_call_name: strnumber,
1377    /// `macro_call` scratch: state field of that level, 0 for a token list.
1378    src_call_state: integer,
1379    /// `macro_call` scratch: token type of that level, captured before argument scanning.
1380    src_call_index: integer,
1381    /// `macro_call` scratch: inherited span fallback for a nested call.
1382    src_call_span: SrcId,
1383    /// `macro_call` scratch: span of the last token consumed while scanning the arguments.
1384    src_call_argspan: SrcId,
1385    /// Span of the latest control sequence lexed from the primary input buffer.
1386    src_user_cmd_span: SrcId,
1387    /// Innermost in fragment command replayed from an argument level token list.
1388    src_anchor_cmd: SrcId,
1389    /// `macro_call` scratch: `src_user_cmd_span` captured before the macro reads its arguments.
1390    src_call_user_span: SrcId,
1391    /// Origin span of the token held in `cur_tok`, restamped by `back_input`.
1392    src_tok_span: SrcId,
1393    /// 1 based index of the live enclosing construct frame in `src_stack_cells`, 0 for none.
1394    cur_stack_head: u32,
1395    /// Group open span popped at the latest math group close, consumed later in the same arm.
1396    src_grp_closing: SrcId,
1397    /// Set once `src_stack_cells` reaches [`SRC_STACK_CAP`], construct frames stop being pushed or popped.
1398    src_stack_frozen: bool,
1399    /// Next host box record id, ids are never reused so a marker kept from an earlier fragment finds no record.
1400    host_box_next_record: integer,
1401    pub LRproblems: integer,
1402    pub LRptr: halfword,
1403    pub OKtointerrupt: boolean,
1404    pub terminal_output: NativeFileHandle,
1405    pub activenodesize: smallnumber,
1406    pub activewidth: [scaled; 7],
1407    pub actuallooseness: integer,
1408    pub adjusttail: halfword,
1409    pub aftertoken: halfword,
1410    pub alignptr: halfword,
1411    pub alignstate: integer,
1412    pub areadelimiter: poolpointer,
1413    pub aritherror: boolean,
1414    pub avail: halfword,
1415    pub background: [scaled; 7],
1416    pub baseptr: integer,
1417    pub bchar: halfword,
1418    pub bcharlabel: *mut fontindex,
1419    pub bestbet: halfword,
1420    pub bestheightplusdepth: scaled,
1421    pub bestline: halfword,
1422    pub bestplace: [halfword; 4],
1423    pub bestplglue: [scaled; 4],
1424    pub bestplline: [halfword; 4],
1425    pub bestplshort: [scaled; 4],
1426    pub breadthmax: integer,
1427    pub breakwidth: [scaled; 7],
1428    pub buffer: *mut UnicodeScalar,
1429    pub bufsize: integer,
1430    pub c: quarterword,
1431    pub cancelboundary: boolean,
1432    pub charbase: *mut integer,
1433    pub condptr: halfword,
1434    pub cscount: integer,
1435    pub curactivewidth: [scaled; 7],
1436    pub curalign: halfword,
1437    pub curarea: strnumber,
1438    pub curboundary: integer,
1439    pub curbox: halfword,
1440    pub curc: integer,
1441    pub curchr: halfword,
1442    pub curcmd: eightbits,
1443    pub curcs: halfword,
1444    pub curdir: smallnumber,
1445    pub curext: strnumber,
1446    pub curf: internalfontnumber,
1447    pub curgroup: groupcode,
1448    pub curhead: halfword,
1449    pub curi: fourquarters,
1450    pub curif: smallnumber,
1451    pub curinput: instaterecord,
1452    pub curl: halfword,
1453    pub curlang: eightbits,
1454    pub curlevel: quarterword,
1455    pub curlist: liststaterecord,
1456    pub curloop: halfword,
1457    pub curmark: [halfword; 5],
1458    pub curmlist: halfword,
1459    pub curmu: scaled,
1460    pub curname: strnumber,
1461    pub curorder: glueord,
1462    pub curp: halfword,
1463    pub curprehead: halfword,
1464    pub curpretail: halfword,
1465    pub curptr: halfword,
1466    pub curq: halfword,
1467    pub curr: halfword,
1468    pub curs: integer,
1469    pub cursize: integer,
1470    pub curspan: halfword,
1471    pub curstyle: smallnumber,
1472    pub curtail: halfword,
1473    pub curtok: halfword,
1474    pub curval: integer,
1475    pub curval1: integer,
1476    pub curvallevel: eightbits,
1477    pub deadcycles: integer,
1478    pub defref: halfword,
1479    pub deletionsallowed: boolean,
1480    pub depthbase: *mut integer,
1481    pub depththreshold: integer,
1482    pub dig: [eightbits; 23],
1483    pub discptr: [halfword; 4],
1484    pub discwidth: scaled,
1485    pub doingleaders: boolean,
1486    pub doingspecial: boolean,
1487    pub dolastlinefit: boolean,
1488    pub downptr: halfword,
1489    pub dvibufsize: integer,
1490    pub dvigone: integer,
1491    pub dvilimit: integer,
1492    pub dvioffset: integer,
1493    pub dviptr: integer,
1494    pub dynused: integer,
1495    pub eTeXmode: eightbits,
1496    pub easyline: halfword,
1497    pub editline: integer,
1498    pub editnamelength: integer,
1499    pub editnamestart: poolpointer,
1500    pub eightbitp: i32,
1501    pub emptyfield: twohalves,
1502    pub eofseen: *mut boolean,
1503    pub epochseconds: integer,
1504    pub eqtbtop: halfword,
1505    pub errorcount: schar,
1506    pub errorline: integer,
1507    pub expanddepth: integer,
1508    pub expanddepthcount: integer,
1509    pub extdelimiter: poolpointer,
1510    pub extenbase: *mut integer,
1511    pub f: internalfontnumber,
1512    pub falsebchar: halfword,
1513    pub fewestdemerits: integer,
1514    pub filelineerrorstylep: i32,
1515    pub filenamequotechar: UTF16code,
1516    pub fileoffset: integer,
1517    pub fillwidth: [scaled; 3],
1518    pub finalpass: boolean,
1519    pub first: integer,
1520    pub firstcount: integer,
1521    pub firstindent: scaled,
1522    pub firstp: halfword,
1523    pub firstwidth: scaled,
1524    pub fmemptr: fontindex,
1525    pub fontarea: *mut strnumber,
1526    pub fontbc: *mut UTF16code,
1527    pub fontbchar: *mut ninebits,
1528    pub fontcheck: *mut fourquarters,
1529    pub fontdsize: *mut scaled,
1530    pub fontec: *mut UTF16code,
1531    pub fontfalsebchar: *mut ninebits,
1532    pub fontflags: *mut i8,
1533    pub fontglue: *mut halfword,
1534    pub fontinfo: *mut fmemoryword,
1535    pub fontinshortdisplay: integer,
1536    pub fontlayoutengine: *mut voidpointer,
1537    pub fontletterspace: *mut scaled,
1538    pub fontmapping: *mut voidpointer,
1539    pub fontmax: integer,
1540    pub fontmemsize: integer,
1541    pub fontname: *mut strnumber,
1542    pub fontparams: *mut fontindex,
1543    pub fontptr: internalfontnumber,
1544    pub fontsize: *mut scaled,
1545    pub fontused: *mut boolean,
1546    pub forceeof: boolean,
1547    pub formatident: strnumber,
1548    pub fullsourcefilenamestack: *mut strnumber,
1549    pub g: halfword,
1550    pub globalprevp: halfword,
1551    pub grpstack: *mut savepointer,
1552    pub ha: halfword,
1553    pub halfbuf: integer,
1554    pub halferrorline: integer,
1555    pub haltingonerrorp: boolean,
1556    pub haltonerrorp: i32,
1557    pub hash: PagedView<twohalves>,
1558    pub hashextra: halfword,
1559    pub hashhigh: halfword,
1560    pub hashused: halfword,
1561    pub hb: halfword,
1562    pub hc: [integer; 4099],
1563    pub heightbase: *mut integer,
1564    pub helpline: [strnumber; 6],
1565    pub helpptr: eightbits,
1566    pub hf: internalfontnumber,
1567    pub himemmin: halfword,
1568    pub history: eightbits,
1569    pub hliststack: [halfword; 513],
1570    pub hliststacklevel: i16,
1571    pub hn: smallnumber,
1572    pub hu: [integer; 4097],
1573    pub hyf: [eightbits; 4097],
1574    pub hyfbchar: halfword,
1575    pub hyfchar: integer,
1576    pub hyfdistance: [smallnumber; 35112],
1577    pub hyfnext: [trieopcode; 35112],
1578    pub hyfnum: [smallnumber; 35112],
1579    pub hyphcount: integer,
1580    pub hyphenchar: *mut integer,
1581    pub hyphenpassed: smallnumber,
1582    pub hyphindex: triepointer,
1583    pub hyphlink: *mut hyphpointer,
1584    pub hyphlist: *mut halfword,
1585    pub hyphnext: integer,
1586    pub hyphsize: integer,
1587    pub hyphstart: triepointer,
1588    pub hyphword: *mut strnumber,
1589    pub iflimit: eightbits,
1590    pub ifline: integer,
1591    pub ifstack: *mut halfword,
1592    pub initcurlang: eightbits,
1593    pub initlft: boolean,
1594    pub initlhyf: integer,
1595    pub initlig: boolean,
1596    pub initlist: halfword,
1597    pub initpoolptr: poolpointer,
1598    pub initrhyf: integer,
1599    pub initstrptr: strnumber,
1600    pub iniversion: boolean,
1601    pub inopen: integer,
1602    pub inputfile: *mut unicodefile,
1603    pub inputptr: integer,
1604    pub inputstack: *mut instaterecord,
1605    pub insdisc: boolean,
1606    pub insertpenalties: integer,
1607    pub insertsrcspecialauto: boolean,
1608    pub insertsrcspecialeverymath: boolean,
1609    pub insertsrcspecialeverypar: boolean,
1610    pub insertsrcspecialeveryvbox: boolean,
1611    pub interaction: eightbits,
1612    pub interactionoption: eightbits,
1613    pub interrupt: integer,
1614    pub ishyph: boolean,
1615    pub isincsname: boolean,
1616    pub italicbase: *mut integer,
1617    pub jobname: strnumber,
1618    pub jrandom: eightbits,
1619    pub justbox: halfword,
1620    pub kernbase: *mut integer,
1621    pub resource_search_state: ResourceSearchState,
1622    pub l: eightbits,
1623    pub last: integer,
1624    pub lastbadness: integer,
1625    pub lastbop: integer,
1626    pub lastglue: halfword,
1627    pub lastkern: scaled,
1628    pub lastleftmostchar: halfword,
1629    pub lastlinefill: halfword,
1630    pub lastnodetype: integer,
1631    pub lastpenalty: integer,
1632    pub lastrightmostchar: halfword,
1633    pub lastspecialline: halfword,
1634    pub lfthit: boolean,
1635    pub lhyf: integer,
1636    pub ligaturepresent: boolean,
1637    pub ligkernbase: *mut integer,
1638    pub ligstack: halfword,
1639    pub line: integer,
1640    pub linediff: integer,
1641    pub linestack: *mut integer,
1642    pub loadedfontdesignsize: scaled,
1643    pub loadedfontflags: i8,
1644    pub loadedfontletterspace: scaled,
1645    pub loadedfontmapping: voidpointer,
1646    pub logfile: alphafile,
1647    pub logopened: boolean,
1648    pub lomemmax: halfword,
1649    pub longhelpseen: boolean,
1650    pub longstate: eightbits,
1651    pub magicoffset: integer,
1652    pub magset: integer,
1653    pub mainf: internalfontnumber,
1654    pub mainh: halfword,
1655    pub maini: fourquarters,
1656    pub mainj: fourquarters,
1657    pub maink: fontindex,
1658    pub mainp: halfword,
1659    pub mainpp: halfword,
1660    pub mainppp: halfword,
1661    pub mains: integer,
1662    pub mappedtext: *mut UTF16code,
1663    pub maxbufstack: integer,
1664    pub maxh: scaled,
1665    pub maxhyphchar: integer,
1666    pub maxinopen: integer,
1667    pub maxinstack: integer,
1668    pub maxneststack: integer,
1669    pub maxopused: trieopcode,
1670    pub maxparamstack: integer,
1671    pub maxprintline: integer,
1672    pub maxpush: integer,
1673    pub maxreghelpline: strnumber,
1674    pub maxregnum: halfword,
1675    pub maxsavestack: integer,
1676    pub maxstrings: integer,
1677    pub maxv: scaled,
1678    pub membot: integer,
1679    pub memend: halfword,
1680    pub memmax: integer,
1681    pub memmin: integer,
1682    pub memtop: integer,
1683    pub microseconds: integer,
1684    pub minimaldemerits: [integer; 4],
1685    pub minimumdemerits: integer,
1686    pub mlistpenalties: boolean,
1687    pub mltexenabledp: boolean,
1688    pub mltexp: boolean,
1689    pub nameinprogress: boolean,
1690    pub namelength: integer,
1691    pub nameoffile: *mut UTF8code,
1692    pub nativefonttypeflag: integer,
1693    pub nativelen: integer,
1694    pub nativetext: *mut UTF16code,
1695    pub nativetextsize: integer,
1696    pub nest: *mut liststaterecord,
1697    pub nestptr: integer,
1698    pub nestsize: integer,
1699    pub nonewcontrolsequence: boolean,
1700    pub noshrinkerroryet: boolean,
1701    pub nullcharacter: fourquarters,
1702    pub nulldelimiter: fourquarters,
1703    pub oldselectorignorederr: eightbits,
1704    pub oldsetting: eightbits,
1705    pub openparens: integer,
1706    pub opstart: [integer; 256],
1707    pub outputactive: boolean,
1708    pub outputcanend: boolean,
1709    pub packbeginline: integer,
1710    pub pagecontents: eightbits,
1711    pub pagemaxdepth: scaled,
1712    pub pagesofar: [scaled; 8],
1713    pub pagetail: halfword,
1714    pub parambase: *mut integer,
1715    pub paramptr: integer,
1716    pub paramsize: integer,
1717    pub paramstack: *mut halfword,
1718    pub parloc: halfword,
1719    pub partoken: halfword,
1720    pub passive: halfword,
1721    pub passnumber: halfword,
1722    pub pdflastxpos: integer,
1723    pub pdflastypos: integer,
1724    pub poolptr: poolpointer,
1725    pub poolsize: integer,
1726    pub preadjusttail: halfword,
1727    pub prevclass: integer,
1728    pub prim: [twohalves; 2101],
1729    pub primused: halfword,
1730    pub printednode: halfword,
1731    pub pseudofiles: halfword,
1732    pub pstack: [halfword; 9],
1733    pub quotedfilename: boolean,
1734    pub radix: smallnumber,
1735    pub randoms: [integer; 55],
1736    pub randomseed: scaled,
1737    pub readfile: [unicodefile; 16],
1738    pub readopen: [eightbits; 17],
1739    pub readyalready: integer,
1740    pub restrictedshell: i32,
1741    pub rhyf: integer,
1742    pub rightptr: halfword,
1743    pub rover: halfword,
1744    pub rthit: boolean,
1745    pub sachain: halfword,
1746    pub salevel: quarterword,
1747    pub sanull: memoryword,
1748    pub saroot: [halfword; 8],
1749    pub savearitherror: boolean,
1750    pub savenativelen: integer,
1751    pub saveptr: integer,
1752    pub savesize: integer,
1753    pub savestack: *mut memoryword,
1754    pub scannerstatus: eightbits,
1755    pub secondindent: scaled,
1756    pub secondpass: boolean,
1757    pub secondwidth: scaled,
1758    pub selector: eightbits,
1759    pub setboxallowed: boolean,
1760    pub shellenabledp: i32,
1761    pub shownmode: i16,
1762    pub skewchar: *mut integer,
1763    pub skipline: integer,
1764    pub sourcefilenamestack: *mut strnumber,
1765    pub spaceclass: integer,
1766    pub speclog: [integer; 29],
1767    pub stacksize: integer,
1768    pub stopatspace: boolean,
1769    pub stringvacancies: integer,
1770    pub strpool: *mut packedUTF16code,
1771    pub strptr: strnumber,
1772    pub strstart: *mut poolpointer,
1773    pub tally: integer,
1774    pub tempptr: halfword,
1775    pub termin: unicodefile,
1776    pub termoffset: integer,
1777    pub texremainder: scaled,
1778    pub tfmfile: bytefile,
1779    pub tfmtemp: i32,
1780    pub threshold: integer,
1781    pub totalpages: integer,
1782    pub totalshrink: [scaled; 4],
1783    pub totalstretch: [scaled; 4],
1784    pub trickbuf: [UnicodeScalar; 256],
1785    pub trickcount: integer,
1786    pub triec: *mut packedUTF16code,
1787    pub triehash: *mut triepointer,
1788    pub triel: *mut triepointer,
1789    pub triemax: triepointer,
1790    pub triemin: [triepointer; 65536],
1791    pub trienotready: boolean,
1792    pub trieo: *mut trieopcode,
1793    pub trieoplang: [eightbits; 35112],
1794    pub trieopptr: integer,
1795    pub trieopval: [trieopcode; 35112],
1796    pub trieptr: triepointer,
1797    pub trier: *mut triepointer,
1798    pub triesize: integer,
1799    pub trietaken: *mut boolean,
1800    pub trietrc: *mut quarterword,
1801    pub trietrl: *mut triepointer,
1802    pub trietro: *mut triepointer,
1803    pub trieused: [trieopcode; 256],
1804    pub twotothe: [integer; 31],
1805    pub useerrhelp: boolean,
1806    pub varused: integer,
1807    pub warningindex: halfword,
1808    pub widthbase: *mut integer,
1809    pub writefile: [alphafile; 16],
1810    pub writeloc: halfword,
1811    pub writeopen: [boolean; 18],
1812    pub xtxligaturepresent: boolean,
1813    pub zeqtb: PagedView<memoryword>,
1814    pub zmem: *mut memoryword,
1815    pub zzzaa: [quarterword; 1114734],
1816    pub zzzab: [integer; 70223],
1817}
1818
1819/// Declares [`PortableTexHeap`] and its allocation, pointer refresh and image codec from one field list.
1820macro_rules! portable_heap {
1821    (
1822        sized { $($sized:ident: $sized_ty:ty [$bound:ident] => $sized_ptr:ident,)* }
1823        trie_scratch { $($scratch:ident: $scratch_ty:ty => $scratch_ptr:ident,)* }
1824        transient { $($(#[$transient_meta:meta])* $transient:ident: $transient_ty:ty,)* }
1825    ) => {
1826        /// Heap owning engine arrays, `Clone` deep copies them so no two states ever share a buffer.
1827        #[derive(Clone, Default)]
1828        pub(crate) struct PortableTexHeap {
1829            /// Main memory, indexed from `memmin`.
1830            mem: Vec<memoryword>,
1831            /// Paged `hash`, from absolute index `hashoffset` through `eqtbtop`.
1832            hash_paged: PagedArray<twohalves>,
1833            /// Paged `eqtb`, from absolute index 0 through `eqtbtop`.
1834            eqtb_paged: PagedArray<memoryword>,
1835            /// Platform font handles per font number, stored as integers in images.
1836            fontlayoutengine_storage: Vec<voidpointer>,
1837            /// Font mapping handles per font number, always null in this engine.
1838            fontmapping_storage: Vec<voidpointer>,
1839            /// Native text run buffer, grown by `ensure_nativetext_capacity` to `nativetextsize`.
1840            nativetext_storage: Vec<UTF16code>,
1841            $($sized: Vec<$sized_ty>,)*
1842            $($scratch: Vec<$scratch_ty>,)*
1843            $($(#[$transient_meta])* $transient: $transient_ty,)*
1844        }
1845
1846        /// Allocates every array at the sizes already stored in `state`.
1847        fn allocate_heap_arrays(state: &PortableTexState, heap: &mut PortableTexHeap) {
1848            heap.mem = zeroed_vec(slots(state.memtop - state.memmin));
1849            heap.eqtb_paged = PagedArray::new(
1850                0,
1851                slots(state.eqtbtop),
1852                eqtb_codepoint_default,
1853                eqtb_sig_range,
1854            );
1855            heap.hash_paged = PagedArray::new(
1856                hashoffset as usize,
1857                slots(state.eqtbtop),
1858                hash_codepoint_default,
1859                hash_sig_range,
1860            );
1861            heap.fontlayoutengine_storage = vec![nullptr; slots(state.fontmax)];
1862            heap.fontmapping_storage = vec![nullptr; slots(state.fontmax)];
1863            heap.nativetext_storage = Vec::new();
1864            $(heap.$sized = zeroed_vec(slots(state.$bound));)*
1865            $(heap.$scratch = zeroed_vec(slots(state.triesize));)*
1866        }
1867
1868        /// Points every raw array field of `state` at its storage in `heap`.
1869        fn refresh_runtime_pointers(state: &mut PortableTexState, heap: &mut PortableTexHeap) {
1870            state.zmem = pointer_or_null(&mut heap.mem).wrapping_offset(-(state.memmin as isize));
1871            state.hash = PagedView::new(&mut heap.hash_paged);
1872            state.zeqtb = PagedView::new(&mut heap.eqtb_paged);
1873            state.fontlayoutengine = pointer_or_null(&mut heap.fontlayoutengine_storage);
1874            state.fontmapping = pointer_or_null(&mut heap.fontmapping_storage);
1875            state.nativetext = pointer_or_null(&mut heap.nativetext_storage);
1876            $(state.$sized_ptr = pointer_or_null(&mut heap.$sized);)*
1877            $(state.$scratch_ptr = pointer_or_null(&mut heap.$scratch);)*
1878        }
1879
1880        /// Frees the trie builder scratch, only valid once the trie is packed.
1881        fn free_trie_scratch(heap: &mut PortableTexHeap) {
1882            $(heap.$scratch = Vec::new();)*
1883        }
1884
1885        /// Bytes allocated for every engine array, the dominant resident footprint.
1886        fn heap_array_bytes(heap: &PortableTexHeap) -> usize {
1887            let mut total = vec_bytes(&heap.mem)
1888                + heap.eqtb_paged.resident_bytes()
1889                + heap.hash_paged.resident_bytes()
1890                + vec_bytes(&heap.fontlayoutengine_storage)
1891                + vec_bytes(&heap.fontmapping_storage)
1892                + vec_bytes(&heap.nativetext_storage);
1893            $(total += vec_bytes(&heap.$sized);)*
1894            $(total += vec_bytes(&heap.$scratch);)*
1895            total
1896        }
1897
1898        fn encode_heap(state: &PortableTexState, heap: &PortableTexHeap, out: &mut Vec<u8>) {
1899            // Only the live low and high regions of `mem` are stored, the free gap between them is not.
1900            let lo_len = heap.mem.len().min(slots(state.lomemmax - state.memmin));
1901            let hi_start = usize::try_from(state.himemmin - state.memmin).map_or(0, |start| start.min(heap.mem.len()));
1902            let hi_len = heap.mem.len().min(slots(state.memend - state.memmin)).saturating_sub(hi_start);
1903            write_vec_ranges(out, &heap.mem, &[(0, lo_len), (hi_start, hi_len)]);
1904            write_paged(out, &heap.eqtb_paged);
1905            write_paged(out, &heap.hash_paged);
1906            write_handles(out, &heap.fontlayoutengine_storage);
1907            write_handles(out, &heap.fontmapping_storage);
1908            write_vec_ranges(out, &heap.nativetext_storage, &[(0, heap.nativetext_storage.len())]);
1909            $(write_vec_used(out, &heap.$sized);)*
1910            $(write_vec_used(out, &heap.$scratch);)*
1911        }
1912
1913        /// Decodes the arrays of an image whose `state` already passed [`validate_state`].
1914        fn decode_heap(
1915            state: &PortableTexState,
1916            input: &mut ImageReader<'_>,
1917        ) -> Result<PortableTexHeap, FormatImageError> {
1918            let live = |bound: integer| if state.initialized { slots(bound) } else { 0 };
1919            let eqtb_end = live(state.eqtbtop);
1920            let hash_base = if state.initialized { hashoffset as usize } else { 0 };
1921            let scratch_len = live(state.triesize);
1922            // Packing the trie frees its builder scratch, an unpacked trie still needs it.
1923            let scratch_ok = |len: usize| len == scratch_len || (len == 0 && state.trienotready == false_0);
1924            Ok(PortableTexHeap {
1925                mem: read_vec(input, "mem", |len| len == live(state.memtop - state.memmin))?,
1926                eqtb_paged: read_paged(input, "eqtb", 0, eqtb_end, eqtb_codepoint_default, eqtb_sig_range)?,
1927                hash_paged: read_paged(input, "hash", hash_base, eqtb_end, hash_codepoint_default, hash_sig_range)?,
1928                fontlayoutengine_storage: read_handles(input, "fontlayoutengine", live(state.fontmax))?,
1929                fontmapping_storage: read_handles(input, "fontmapping", live(state.fontmax))?,
1930                nativetext_storage: read_vec(input, "nativetext", |len| len == state.nativetextsize as usize)?,
1931                $($sized: read_vec(input, stringify!($sized), |len| len == live(state.$bound))?,)*
1932                $($scratch: read_vec(input, stringify!($scratch), scratch_ok)?,)*
1933                $($transient: Default::default(),)*
1934            })
1935        }
1936    };
1937}
1938
1939portable_heap! {
1940    sized {
1941        buffer_storage: UnicodeScalar [bufsize] => buffer,
1942        nest_storage: liststaterecord [nestsize] => nest,
1943        savestack_storage: memoryword [savesize] => savestack,
1944        inputstack_storage: instaterecord [stacksize] => inputstack,
1945        inputfile_storage: unicodefile [maxinopen] => inputfile,
1946        eofseen_storage: boolean [maxinopen] => eofseen,
1947        linestack_storage: integer [maxinopen] => linestack,
1948        grpstack_storage: savepointer [maxinopen] => grpstack,
1949        ifstack_storage: halfword [maxinopen] => ifstack,
1950        sourcefilenamestack_storage: strnumber [maxinopen] => sourcefilenamestack,
1951        fullsourcefilenamestack_storage: strnumber [maxinopen] => fullsourcefilenamestack,
1952        paramstack_storage: halfword [paramsize] => paramstack,
1953        hyphword_storage: strnumber [hyphsize] => hyphword,
1954        hyphlist_storage: halfword [hyphsize] => hyphlist,
1955        hyphlink_storage: hyphpointer [hyphsize] => hyphlink,
1956        strstart_storage: poolpointer [maxstrings] => strstart,
1957        strpool_storage: packedUTF16code [poolsize] => strpool,
1958        fontinfo_storage: fmemoryword [fontmemsize] => fontinfo,
1959        bcharlabel_storage: fontindex [fontmax] => bcharlabel,
1960        charbase_storage: integer [fontmax] => charbase,
1961        widthbase_storage: integer [fontmax] => widthbase,
1962        heightbase_storage: integer [fontmax] => heightbase,
1963        depthbase_storage: integer [fontmax] => depthbase,
1964        italicbase_storage: integer [fontmax] => italicbase,
1965        ligkernbase_storage: integer [fontmax] => ligkernbase,
1966        kernbase_storage: integer [fontmax] => kernbase,
1967        extenbase_storage: integer [fontmax] => extenbase,
1968        parambase_storage: integer [fontmax] => parambase,
1969        fontarea_storage: strnumber [fontmax] => fontarea,
1970        fontname_storage: strnumber [fontmax] => fontname,
1971        fontbc_storage: UTF16code [fontmax] => fontbc,
1972        fontec_storage: UTF16code [fontmax] => fontec,
1973        fontbchar_storage: ninebits [fontmax] => fontbchar,
1974        fontfalsebchar_storage: ninebits [fontmax] => fontfalsebchar,
1975        fontcheck_storage: fourquarters [fontmax] => fontcheck,
1976        fontdsize_storage: scaled [fontmax] => fontdsize,
1977        fontsize_storage: scaled [fontmax] => fontsize,
1978        fontflags_storage: i8 [fontmax] => fontflags,
1979        fontglue_storage: halfword [fontmax] => fontglue,
1980        fontletterspace_storage: scaled [fontmax] => fontletterspace,
1981        fontparams_storage: fontindex [fontmax] => fontparams,
1982        fontused_storage: boolean [fontmax] => fontused,
1983        hyphenchar_storage: integer [fontmax] => hyphenchar,
1984        skewchar_storage: integer [fontmax] => skewchar,
1985        trietrc_storage: quarterword [triesize] => trietrc,
1986        trietrl_storage: triepointer [triesize] => trietrl,
1987        trietro_storage: triepointer [triesize] => trietro,
1988    }
1989    trie_scratch {
1990        triec_storage: packedUTF16code => triec,
1991        triehash_storage: triepointer => triehash,
1992        triel_storage: triepointer => triel,
1993        trieo_storage: trieopcode => trieo,
1994        trier_storage: triepointer => trier,
1995        trietaken_storage: boolean => trietaken,
1996    }
1997    transient {
1998        /// Source tracking intern table, `SrcId` minus 1 indexes it in first touch order.
1999        src_spans: Vec<RawSpan>,
2000        /// Source tracking dedup map, equal spans share one `SrcId`.
2001        src_dedup: std::collections::HashMap<RawSpan, SrcId>,
2002        /// Paged shadow of `mem` holding the `SrcId` stamped on the node at each address.
2003        node_src: PagedArray<u32>,
2004        /// Per UTF-16 unit source ids of the native run being collected, consumed when it is shaped.
2005        src_native_offsets: Vec<SrcId>,
2006        /// Arena of parent linked enclosing construct frames, addressed 1 based by `cur_stack_head`.
2007        src_stack_cells: Vec<SrcStackCell>,
2008        /// Opening token spans of the live math groups and `\left` constructs.
2009        src_grp_stack: Vec<SrcId>,
2010        /// Paged shadow of `mem` holding the `cur_stack_head` live when each node was allocated.
2011        node_stack: PagedArray<u32>,
2012        /// Input byte range of the scalar in each buffer slot, the limit slot ends past the line break.
2013        src_buf_bytes: Vec<(u32, u32)>,
2014    }
2015}
2016
2017// Engine array sizes, a web2c `array[0..N]` gets N+1 slots, see `slots`.
2018const MEM_TOP: integer = 999_999;
2019const HASH_EXTRA: integer = 600_000;
2020const BUF_SIZE: integer = 200_000;
2021const NEST_SIZE: integer = 1_000;
2022const MAX_IN_OPEN: integer = 15;
2023const PARAM_SIZE: integer = 20_000;
2024const SAVE_SIZE: integer = 200_000;
2025const STACK_SIZE: integer = 10_000;
2026const POOL_SIZE: integer = 6_250_000;
2027const MAX_STRINGS: integer = 500_000;
2028const FONT_MEM_SIZE: integer = 1_000_000;
2029const FONT_MAX: integer = 500;
2030const TRIE_SIZE: integer = 1_100_000;
2031const HYPH_SIZE: integer = 8_191;
2032
2033/// Slot count of a web2c `array[0..bound]`, zero for a negative bound.
2034fn slots(bound: integer) -> usize {
2035    usize::try_from(bound).map_or(0, |bound| bound + 1)
2036}
2037
2038fn vec_bytes<T>(v: &Vec<T>) -> usize {
2039    v.capacity() * core::mem::size_of::<T>()
2040}
2041
2042/// Magic of a serialized format image, followed by the little endian image layout version.
2043const PORTABLE_FORMAT_MAGIC: [u8; 6] = *b"MTXfmt";
2044
2045/// Image layout version, 7 dropped pointer width from the tag and the pointer view of memory words.
2046const PORTABLE_FORMAT_VERSION: u16 = 7;
2047
2048/// Hash of the generated sources and layout constants, so an image from another build is refused.
2049const BUILD_FINGERPRINT: u64 = 0xe96abe755e6fab55;
2050
2051/// Validation tag bytes: magic, layout version, byte order and build fingerprint.
2052const VALIDATION_TAG_LEN: usize = 17;
2053
2054/// Header bytes: validation tag, body length and body checksum.
2055const HEADER_LEN: usize = VALIDATION_TAG_LEN + 16;
2056
2057/// Byte order of the plain values an image copies, the only target property an image depends on.
2058const fn byte_order() -> u8 {
2059    if cfg!(target_endian = "big") {
2060        1
2061    } else {
2062        0
2063    }
2064}
2065
2066fn validation_tag() -> [u8; VALIDATION_TAG_LEN] {
2067    let mut tag = [0u8; VALIDATION_TAG_LEN];
2068    tag[..6].copy_from_slice(&PORTABLE_FORMAT_MAGIC);
2069    tag[6..8].copy_from_slice(&PORTABLE_FORMAT_VERSION.to_le_bytes());
2070    tag[8] = byte_order();
2071    tag[9..].copy_from_slice(&BUILD_FINGERPRINT.to_le_bytes());
2072    tag
2073}
2074
2075/// Word wise multiplicative checksum of an image body, it detects corruption, not tampering.
2076fn image_checksum(bytes: &[u8]) -> u64 {
2077    const K: u64 = 0x9E37_79B9_7F4A_7C15;
2078    let mut hash = bytes.len() as u64;
2079    let mut chunks = bytes.chunks_exact(8);
2080    for chunk in &mut chunks {
2081        let mut word = [0u8; 8];
2082        word.copy_from_slice(chunk);
2083        hash = (hash.rotate_left(5) ^ u64::from_le_bytes(word)).wrapping_mul(K);
2084    }
2085    for &byte in chunks.remainder() {
2086        hash = (hash.rotate_left(5) ^ u64::from(byte)).wrapping_mul(K);
2087    }
2088    hash
2089}
2090
2091/// Why a serialized format image was rejected.
2092#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2093pub enum FormatImageError {
2094    /// The bytes do not start with the format image magic.
2095    NotAnImage,
2096    /// The image was written with another image layout version.
2097    Version(u16),
2098    /// The image was written on a target with the other byte order.
2099    ByteOrder,
2100    /// The image was written by another engine build.
2101    OtherBuild,
2102    /// The image ends before a part its header or body declares.
2103    Truncated,
2104    /// Bytes remain after the last part.
2105    TrailingBytes,
2106    /// The body checksum does not match the header.
2107    ChecksumMismatch,
2108    /// A state field holds a value its type cannot hold.
2109    InvalidField(&'static str),
2110    /// Decoded sizes disagree with each other or with the layout this build allocates.
2111    Geometry(&'static str),
2112}
2113
2114impl FormatImageError {
2115    fn in_field(self, field: &'static str) -> Self {
2116        match self {
2117            Self::InvalidField(_) => Self::InvalidField(field),
2118            other => other,
2119        }
2120    }
2121}
2122
2123impl core::fmt::Display for FormatImageError {
2124    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2125        match self {
2126            Self::NotAnImage => f.write_str("not a format image"),
2127            Self::Version(found) => write!(
2128                f,
2129                "format image layout version {found}, this build reads {PORTABLE_FORMAT_VERSION}"
2130            ),
2131            Self::ByteOrder => f.write_str("format image is from a target with the other byte order"),
2132            Self::OtherBuild => f.write_str("format image is from another engine build"),
2133            Self::Truncated => f.write_str("format image is truncated"),
2134            Self::TrailingBytes => f.write_str("format image has trailing bytes"),
2135            Self::ChecksumMismatch => f.write_str("format image checksum mismatch"),
2136            Self::InvalidField(field) => write!(f, "format image field `{field}` holds an invalid value"),
2137            Self::Geometry(what) => write!(f, "format image `{what}` has an inconsistent size"),
2138        }
2139    }
2140}
2141
2142impl std::error::Error for FormatImageError {}
2143
2144/// Cursor over image bytes whose reads fail instead of running past the end.
2145struct ImageReader<'a> {
2146    bytes: &'a [u8],
2147    cursor: usize,
2148}
2149
2150impl<'a> ImageReader<'a> {
2151    fn new(bytes: &'a [u8]) -> Self {
2152        Self { bytes, cursor: 0 }
2153    }
2154
2155    fn remaining(&self) -> usize {
2156        self.bytes.len() - self.cursor
2157    }
2158
2159    fn take(&mut self, len: usize) -> Result<&'a [u8], FormatImageError> {
2160        let end = self
2161            .cursor
2162            .checked_add(len)
2163            .filter(|&end| end <= self.bytes.len())
2164            .ok_or(FormatImageError::Truncated)?;
2165        let slice = &self.bytes[self.cursor..end];
2166        self.cursor = end;
2167        Ok(slice)
2168    }
2169
2170    fn u32(&mut self) -> Result<u32, FormatImageError> {
2171        let mut word = [0u8; 4];
2172        word.copy_from_slice(self.take(4)?);
2173        Ok(u32::from_le_bytes(word))
2174    }
2175
2176    fn u64(&mut self) -> Result<u64, FormatImageError> {
2177        let mut word = [0u8; 8];
2178        word.copy_from_slice(self.take(8)?);
2179        Ok(u64::from_le_bytes(word))
2180    }
2181
2182    fn usize(&mut self) -> Result<usize, FormatImageError> {
2183        usize::try_from(self.u64()?).map_err(|_| FormatImageError::Truncated)
2184    }
2185}
2186
2187/// Field wise image encoding, implemented only for types valid as all zero bytes.
2188trait ImageCodec: Sized {
2189    /// Encoded bytes per value.
2190    const ENCODED_SIZE: usize;
2191
2192    /// Whether the value is all zero, used to drop the zero tail of an array.
2193    fn is_zero(&self) -> bool;
2194
2195    /// Appends the encoding of every value in `items`.
2196    fn encode(items: &[Self], out: &mut Vec<u8>);
2197
2198    /// Decodes `count` values into `dst`, which must be valid for `count` writes.
2199    unsafe fn decode(
2200        dst: *mut Self,
2201        count: usize,
2202        input: &mut ImageReader<'_>,
2203    ) -> Result<(), FormatImageError>;
2204}
2205
2206/// Bytes of plain values, the caller guarantees `T` has no padding and no uninitialized bytes.
2207unsafe fn plain_bytes<T>(items: &[T]) -> &[u8] {
2208    core::slice::from_raw_parts(items.as_ptr().cast::<u8>(), core::mem::size_of_val(items))
2209}
2210
2211macro_rules! plain_image_codec {
2212    ($($ty:ty),* $(,)?) => {$(
2213        impl ImageCodec for $ty {
2214            const ENCODED_SIZE: usize = core::mem::size_of::<$ty>();
2215
2216            fn is_zero(&self) -> bool {
2217                unsafe { plain_bytes(core::slice::from_ref(self)) }.iter().all(|&byte| byte == 0)
2218            }
2219
2220            fn encode(items: &[Self], out: &mut Vec<u8>) {
2221                out.extend_from_slice(unsafe { plain_bytes(items) });
2222            }
2223
2224            unsafe fn decode(
2225                dst: *mut Self,
2226                count: usize,
2227                input: &mut ImageReader<'_>,
2228            ) -> Result<(), FormatImageError> {
2229                let len = count
2230                    .checked_mul(Self::ENCODED_SIZE)
2231                    .ok_or(FormatImageError::Truncated)?;
2232                let bytes = input.take(len)?;
2233                core::ptr::copy_nonoverlapping(bytes.as_ptr(), dst.cast::<u8>(), len);
2234                Ok(())
2235            }
2236        }
2237    )*};
2238}
2239
2240// No padding and every byte initialized, the word unions are zero filled by their `Default`.
2241plain_image_codec!(
2242    i8,
2243    i16,
2244    u16,
2245    i32,
2246    u32,
2247    u64,
2248    memoryword,
2249    fmemoryword,
2250    twohalves,
2251    fourquarters,
2252    instaterecord,
2253    ResourceSearchState,
2254);
2255
2256impl ImageCodec for bool {
2257    const ENCODED_SIZE: usize = 1;
2258
2259    fn is_zero(&self) -> bool {
2260        !*self
2261    }
2262
2263    fn encode(items: &[Self], out: &mut Vec<u8>) {
2264        out.extend(items.iter().map(|&value| u8::from(value)));
2265    }
2266
2267    unsafe fn decode(
2268        dst: *mut Self,
2269        count: usize,
2270        input: &mut ImageReader<'_>,
2271    ) -> Result<(), FormatImageError> {
2272        for (index, &byte) in input.take(count)?.iter().enumerate() {
2273            let value = match byte {
2274                0 => false,
2275                1 => true,
2276                _ => return Err(FormatImageError::InvalidField("bool")),
2277            };
2278            dst.add(index).write(value);
2279        }
2280        Ok(())
2281    }
2282}
2283
2284// Raw pointers are process local, an image stores nothing for them and decodes them as null.
2285impl<T> ImageCodec for *mut T {
2286    const ENCODED_SIZE: usize = 0;
2287
2288    fn is_zero(&self) -> bool {
2289        true
2290    }
2291
2292    fn encode(_items: &[Self], _out: &mut Vec<u8>) {}
2293
2294    unsafe fn decode(
2295        dst: *mut Self,
2296        count: usize,
2297        _input: &mut ImageReader<'_>,
2298    ) -> Result<(), FormatImageError> {
2299        for index in 0..count {
2300            dst.add(index).write(core::ptr::null_mut());
2301        }
2302        Ok(())
2303    }
2304}
2305
2306// Views are rebound by `refresh_runtime_pointers` after decoding.
2307impl<T: Copy + Default> ImageCodec for PagedView<T> {
2308    const ENCODED_SIZE: usize = 0;
2309
2310    fn is_zero(&self) -> bool {
2311        true
2312    }
2313
2314    fn encode(_items: &[Self], _out: &mut Vec<u8>) {}
2315
2316    unsafe fn decode(
2317        dst: *mut Self,
2318        count: usize,
2319        _input: &mut ImageReader<'_>,
2320    ) -> Result<(), FormatImageError> {
2321        for index in 0..count {
2322            dst.add(index).write(PagedView(core::ptr::null_mut()));
2323        }
2324        Ok(())
2325    }
2326}
2327
2328impl<T: ImageCodec, const N: usize> ImageCodec for [T; N] {
2329    const ENCODED_SIZE: usize = T::ENCODED_SIZE * N;
2330
2331    fn is_zero(&self) -> bool {
2332        self.iter().all(T::is_zero)
2333    }
2334
2335    fn encode(items: &[Self], out: &mut Vec<u8>) {
2336        T::encode(items.as_flattened(), out);
2337    }
2338
2339    unsafe fn decode(
2340        dst: *mut Self,
2341        count: usize,
2342        input: &mut ImageReader<'_>,
2343    ) -> Result<(), FormatImageError> {
2344        let count = count.checked_mul(N).ok_or(FormatImageError::Truncated)?;
2345        T::decode(dst.cast::<T>(), count, input)
2346    }
2347}
2348
2349// Field by field, the record has padding after `modefield`.
2350impl ImageCodec for liststaterecord {
2351    const ENCODED_SIZE: usize = 2 + 5 * 4 + 8;
2352
2353    fn is_zero(&self) -> bool {
2354        self.modefield == 0
2355            && [self.headfield, self.tailfield, self.eTeXauxfield, self.pgfield, self.mlfield]
2356                .iter()
2357                .all(|&field| field == 0)
2358            && self.auxfield.is_zero()
2359    }
2360
2361    fn encode(items: &[Self], out: &mut Vec<u8>) {
2362        for record in items {
2363            i16::encode(&[record.modefield], out);
2364            i32::encode(
2365                &[
2366                    record.headfield,
2367                    record.tailfield,
2368                    record.eTeXauxfield,
2369                    record.pgfield,
2370                    record.mlfield,
2371                ],
2372                out,
2373            );
2374            memoryword::encode(&[record.auxfield], out);
2375        }
2376    }
2377
2378    unsafe fn decode(
2379        dst: *mut Self,
2380        count: usize,
2381        input: &mut ImageReader<'_>,
2382    ) -> Result<(), FormatImageError> {
2383        for index in 0..count {
2384            let record = dst.add(index);
2385            i16::decode(core::ptr::addr_of_mut!((*record).modefield), 1, input)?;
2386            i32::decode(core::ptr::addr_of_mut!((*record).headfield), 1, input)?;
2387            i32::decode(core::ptr::addr_of_mut!((*record).tailfield), 1, input)?;
2388            i32::decode(core::ptr::addr_of_mut!((*record).eTeXauxfield), 1, input)?;
2389            i32::decode(core::ptr::addr_of_mut!((*record).pgfield), 1, input)?;
2390            i32::decode(core::ptr::addr_of_mut!((*record).mlfield), 1, input)?;
2391            memoryword::decode(core::ptr::addr_of_mut!((*record).auxfield), 1, input)?;
2392        }
2393        Ok(())
2394    }
2395}
2396
2397/// Writes the length of `v`, then only the element ranges in `ranges`, the rest decodes as zero.
2398fn write_vec_ranges<T: ImageCodec>(out: &mut Vec<u8>, v: &[T], ranges: &[(usize, usize)]) {
2399    out.extend_from_slice(&(v.len() as u64).to_le_bytes());
2400    let ranges = ranges.iter().filter(|&&(_, len)| len > 0).collect::<Vec<_>>();
2401    out.extend_from_slice(&(ranges.len() as u32).to_le_bytes());
2402    for &&(start, len) in &ranges {
2403        out.extend_from_slice(&(start as u64).to_le_bytes());
2404        out.extend_from_slice(&(len as u64).to_le_bytes());
2405        T::encode(&v[start..start + len], out);
2406    }
2407}
2408
2409/// Writes `v` without its zero tail, most engine arrays are sized far beyond what a format uses.
2410fn write_vec_used<T: ImageCodec>(out: &mut Vec<u8>, v: &[T]) {
2411    let used = v.iter().rposition(|item| !item.is_zero()).map_or(0, |last| last + 1);
2412    write_vec_ranges(out, v, &[(0, used)]);
2413}
2414
2415/// Decodes a vector from [`write_vec_ranges`], refusing a length `accept` rejects before allocating.
2416fn read_vec<T: ImageCodec>(
2417    input: &mut ImageReader<'_>,
2418    what: &'static str,
2419    accept: impl Fn(usize) -> bool,
2420) -> Result<Vec<T>, FormatImageError> {
2421    let full = input.usize()?;
2422    if !accept(full) {
2423        return Err(FormatImageError::Geometry(what));
2424    }
2425    let ranges = input.u32()?;
2426    let mut v = Vec::<T>::with_capacity(full);
2427    // Sound because every `ImageCodec` type is valid as zero bytes, and ranges stay within `full`.
2428    unsafe {
2429        core::ptr::write_bytes(v.as_mut_ptr(), 0, full);
2430        v.set_len(full);
2431        for _ in 0..ranges {
2432            let start = input.usize()?;
2433            let len = input.usize()?;
2434            if start.checked_add(len).is_none_or(|end| end > full) {
2435                return Err(FormatImageError::Geometry(what));
2436            }
2437            T::decode(v.as_mut_ptr().add(start), len, input)?;
2438        }
2439    }
2440    Ok(v)
2441}
2442
2443/// Writes font handles as integers, they index the font platform rather than memory.
2444fn write_handles(out: &mut Vec<u8>, handles: &[voidpointer]) {
2445    let values = handles.iter().map(|&handle| handle as usize as u64).collect::<Vec<_>>();
2446    write_vec_used(out, &values);
2447}
2448
2449fn read_handles(
2450    input: &mut ImageReader<'_>,
2451    what: &'static str,
2452    expected: usize,
2453) -> Result<Vec<voidpointer>, FormatImageError> {
2454    read_vec::<u64>(input, what, |len| len == expected)?
2455        .into_iter()
2456        .map(|value| {
2457            usize::try_from(value)
2458                .map(|handle| handle as voidpointer)
2459                .map_err(|_| FormatImageError::InvalidField(what))
2460        })
2461        .collect()
2462}
2463
2464/// Writes a [`PagedArray`] as its geometry and only its faulted pages.
2465fn write_paged<T: ImageCodec + Copy + Default>(out: &mut Vec<u8>, arr: &PagedArray<T>) {
2466    for value in [arr.base, arr.end] {
2467        out.extend_from_slice(&(value as u64).to_le_bytes());
2468    }
2469    out.extend_from_slice(&(arr.pages.len() as u64).to_le_bytes());
2470    let present = arr.pages.iter().enumerate().filter(|(_, page)| page.is_some()).count();
2471    out.extend_from_slice(&(present as u32).to_le_bytes());
2472    for (index, page) in arr.pages.iter().enumerate() {
2473        if let Some(page) = page {
2474            out.extend_from_slice(&(index as u64).to_le_bytes());
2475            T::encode(page, out);
2476        }
2477    }
2478}
2479
2480/// Decodes a [`write_paged`] array whose geometry must be `base..end`.
2481fn read_paged<T: ImageCodec + Copy + Default>(
2482    input: &mut ImageReader<'_>,
2483    what: &'static str,
2484    base: usize,
2485    end: usize,
2486    default_fn: fn(usize) -> T,
2487    sig_range: fn(usize) -> (usize, usize),
2488) -> Result<PagedArray<T>, FormatImageError> {
2489    let geometry = [input.usize()?, input.usize()?];
2490    if geometry != [base, end] {
2491        return Err(FormatImageError::Geometry(what));
2492    }
2493    let npages = input.usize()?;
2494    if npages != (end - base).div_ceil(PAGE_LEN) {
2495        return Err(FormatImageError::Geometry(what));
2496    }
2497    let mut pages: Vec<Option<Box<[T]>>> = (0..npages).map(|_| None).collect();
2498    for _ in 0..input.u32()? {
2499        let slot = pages
2500            .get_mut(input.usize()?)
2501            .ok_or(FormatImageError::Geometry(what))?;
2502        let mut page = vec![T::default(); PAGE_LEN];
2503        // Sound because `page` holds exactly `PAGE_LEN` values.
2504        unsafe { T::decode(page.as_mut_ptr(), PAGE_LEN, input)? };
2505        *slot = Some(page.into_boxed_slice());
2506    }
2507    Ok(PagedArray {
2508        base,
2509        end,
2510        pages,
2511        default_fn,
2512        sig_range,
2513    })
2514}
2515
2516/// Cross checks the decoded scalars that generated code indexes arrays by against this build's layout.
2517fn validate_state(state: &PortableTexState) -> Result<(), FormatImageError> {
2518    let check = |ok: bool, what: &'static str| {
2519        if ok {
2520            Ok(())
2521        } else {
2522            Err(FormatImageError::Geometry(what))
2523        }
2524    };
2525    if !state.initialized {
2526        return Ok(());
2527    }
2528    check(state.memmin == 0 && state.membot == 0, "memmin")?;
2529    check(state.memtop == MEM_TOP && state.memmax == MEM_TOP, "memtop")?;
2530    check(
2531        0 <= state.lomemmax
2532            && state.lomemmax < state.himemmin
2533            && state.himemmin <= state.memend
2534            && state.memend <= state.memmax,
2535        "lomemmax, himemmin and memend",
2536    )?;
2537    check(
2538        state.hashextra == HASH_EXTRA && state.eqtbtop == EQTB_SIZE as halfword + HASH_EXTRA,
2539        "eqtbtop",
2540    )?;
2541    check(
2542        state.bufsize == BUF_SIZE
2543            && state.nestsize == NEST_SIZE
2544            && state.maxinopen == MAX_IN_OPEN
2545            && state.paramsize == PARAM_SIZE
2546            && state.savesize == SAVE_SIZE
2547            && state.stacksize == STACK_SIZE
2548            && state.hyphsize == HYPH_SIZE
2549            && state.triesize == TRIE_SIZE,
2550        "stack sizes",
2551    )?;
2552    check(
2553        state.poolsize == POOL_SIZE && (0..=state.poolsize).contains(&state.poolptr),
2554        "poolptr",
2555    )?;
2556    check(
2557        state.maxstrings == MAX_STRINGS
2558            && state.strptr >= TOO_BIG_CHAR
2559            && state.strptr - TOO_BIG_CHAR <= state.maxstrings,
2560        "strptr",
2561    )?;
2562    check(
2563        state.fontmemsize == FONT_MEM_SIZE && (0..=state.fontmemsize).contains(&state.fmemptr),
2564        "fmemptr",
2565    )?;
2566    check(
2567        state.fontmax == FONT_MAX && (0..=state.fontmax).contains(&state.fontptr),
2568        "fontptr",
2569    )?;
2570    check(state.nativetextsize >= 0, "nativetextsize")
2571}
2572
2573/// Nulls every process local file slot and marks every stream closed, as TeX's `initialize` does.
2574fn close_file_slots(state: &mut PortableTexState) {
2575    state.readfile = [core::ptr::null_mut(); STREAM_COUNT];
2576    state.writefile = [core::ptr::null_mut(); STREAM_COUNT];
2577    state.termin = core::ptr::null_mut();
2578    state.logfile = core::ptr::null_mut();
2579    state.tfmfile = core::ptr::null_mut();
2580    state.terminal_output = core::ptr::null_mut();
2581    state.nameoffile = core::ptr::null_mut();
2582    state.mappedtext = core::ptr::null_mut();
2583    state.readopen = [READ_CLOSED; STREAM_COUNT + 1];
2584    state.writeopen = [false_0; STREAM_COUNT + 2];
2585}
2586
2587fn boxed_state_zeroed() -> Box<PortableTexState> {
2588    let mut state = Box::<PortableTexState>::new_uninit();
2589    // Sound because all zero bytes are a valid value for every plain field of the state.
2590    unsafe {
2591        core::ptr::write_bytes(state.as_mut_ptr(), 0, 1);
2592        state.assume_init()
2593    }
2594}
2595
2596/// Copies `state` straight into a new box, the state is several megabytes and must not cross the stack.
2597fn boxed_state_copy(state: &PortableTexState) -> Box<PortableTexState> {
2598    let mut copy = Box::<PortableTexState>::new_uninit();
2599    // Sound because `PortableTexState` is `Copy`, a bitwise copy is a valid value.
2600    unsafe {
2601        core::ptr::copy_nonoverlapping(state, copy.as_mut_ptr(), 1);
2602        copy.assume_init()
2603    }
2604}
2605
2606/// Sets the array sizes of a fresh INITEX state and allocates its arrays.
2607fn allocate_initial_arrays(state: &mut PortableTexState, heap: &mut PortableTexHeap) {
2608    state.iniversion = true_0;
2609    state.membot = 0;
2610    state.memmin = state.membot;
2611    // Math fragments use a tiny fraction of TeX's worst case memory, the LaTeX format build fits well.
2612    state.memtop = MEM_TOP;
2613    state.memmax = state.memtop;
2614    state.hashextra = HASH_EXTRA;
2615    state.eqtbtop = EQTB_SIZE as halfword + state.hashextra;
2616    state.bufsize = BUF_SIZE;
2617    state.nestsize = NEST_SIZE;
2618    state.maxinopen = MAX_IN_OPEN;
2619    state.paramsize = PARAM_SIZE;
2620    state.savesize = SAVE_SIZE;
2621    state.stacksize = STACK_SIZE;
2622    state.dvibufsize = 16_384;
2623    state.poolsize = POOL_SIZE;
2624    state.maxstrings = MAX_STRINGS;
2625    state.fontmemsize = FONT_MEM_SIZE;
2626    state.fontmax = FONT_MAX;
2627    // The LaTeX `\patterns` need over 700K trie nodes, the builder scratch is freed once packed.
2628    state.triesize = TRIE_SIZE;
2629    state.hyphsize = HYPH_SIZE;
2630    state.primused = 2_100;
2631    state.errorline = 79;
2632    state.halferrorline = 50;
2633    state.maxprintline = 79;
2634    state.expanddepth = 10_000;
2635    allocate_heap_arrays(state, heap);
2636    refresh_runtime_pointers(state, heap);
2637}
2638
2639/// Resets per run input state and compacts arrays so a snapshot keeps only what a format needs.
2640fn seal_as_format_snapshot(state: &mut PortableTexState, heap: &mut PortableTexHeap) {
2641    state.curinput = instaterecord::default();
2642    state.inputptr = 0;
2643    state.inopen = 0;
2644    state.baseptr = 0;
2645    state.scannerstatus = 0;
2646    state.warningindex = 0;
2647    state.defref = TEX_NULL;
2648    state.paramptr = 0;
2649    state.alignstate = NEUTRAL_ALIGN_STATE;
2650    state.first = 0;
2651    state.last = 0;
2652    state.line = 0;
2653    state.openparens = 0;
2654    close_file_slots(state);
2655    heap.inputstack_storage.fill(instaterecord::default());
2656    heap.inputfile_storage.fill(core::ptr::null_mut());
2657    heap.eofseen_storage.fill(false_0);
2658    heap.linestack_storage.fill(0);
2659    heap.grpstack_storage.fill(TEX_NULL);
2660    heap.ifstack_storage.fill(TEX_NULL);
2661    heap.sourcefilenamestack_storage.fill(0);
2662    heap.fullsourcefilenamestack_storage.fill(0);
2663    heap.paramstack_storage.fill(0);
2664    // Pages still equal to their band defaults are dropped, the bulk of the eqtb and hash savings.
2665    heap.eqtb_paged.compact();
2666    heap.hash_paged.compact();
2667    // A packed trie never runs the builder again, an unpacked one still needs its scratch lazily.
2668    if state.trienotready == false_0 {
2669        free_trie_scratch(heap);
2670    }
2671    refresh_runtime_pointers(state, heap);
2672}
2673
2674/// Serializes a state as a format image: header, plain fields, then the arrays.
2675fn encode_image(state: &PortableTexState, heap: &PortableTexHeap) -> Vec<u8> {
2676    let mut out = vec![0u8; HEADER_LEN];
2677    state.encode_fields(&mut out);
2678    encode_heap(state, heap, &mut out);
2679    let body_len = (out.len() - HEADER_LEN) as u64;
2680    let checksum = image_checksum(&out[HEADER_LEN..]);
2681    out[..VALIDATION_TAG_LEN].copy_from_slice(&validation_tag());
2682    out[VALIDATION_TAG_LEN..VALIDATION_TAG_LEN + 8].copy_from_slice(&body_len.to_le_bytes());
2683    out[VALIDATION_TAG_LEN + 8..HEADER_LEN].copy_from_slice(&checksum.to_le_bytes());
2684    out
2685}
2686
2687/// Checks each part of a validation tag in order, so the error names the first that differs.
2688fn check_validation_tag(tag: &[u8]) -> Result<(), FormatImageError> {
2689    let expected = validation_tag();
2690    if tag[..6] != expected[..6] {
2691        return Err(FormatImageError::NotAnImage);
2692    }
2693    let version = u16::from_le_bytes([tag[6], tag[7]]);
2694    if version != PORTABLE_FORMAT_VERSION {
2695        return Err(FormatImageError::Version(version));
2696    }
2697    if tag[8] != expected[8] {
2698        return Err(FormatImageError::ByteOrder);
2699    }
2700    if tag[9..] != expected[9..] {
2701        return Err(FormatImageError::OtherBuild);
2702    }
2703    Ok(())
2704}
2705
2706/// Decodes and fully validates an [`encode_image`] image.
2707fn decode_image(
2708    bytes: &[u8],
2709) -> Result<(Box<PortableTexState>, Box<PortableTexHeap>), FormatImageError> {
2710    let mut header = ImageReader::new(bytes);
2711    check_validation_tag(header.take(VALIDATION_TAG_LEN)?)?;
2712    let body_len = header.usize()?;
2713    let checksum = header.u64()?;
2714    let body = &bytes[HEADER_LEN..];
2715    if body.len() < body_len {
2716        return Err(FormatImageError::Truncated);
2717    }
2718    if body.len() > body_len {
2719        return Err(FormatImageError::TrailingBytes);
2720    }
2721    if image_checksum(body) != checksum {
2722        return Err(FormatImageError::ChecksumMismatch);
2723    }
2724    let mut input = ImageReader::new(body);
2725    let mut state = boxed_state_zeroed();
2726    // Sound because `state` is a valid, uniquely owned state that each field decode overwrites in place.
2727    unsafe { PortableTexState::decode_fields(&mut *state, &mut input)? };
2728    validate_state(&state)?;
2729    // The dense native text buffer must fit the remaining input, which bounds its allocation.
2730    if (state.nativetextsize as usize).saturating_mul(2) > input.remaining() {
2731        return Err(FormatImageError::Geometry("nativetextsize"));
2732    }
2733    close_file_slots(&mut state);
2734    state.cur_stack_head = 0;
2735    let mut heap = Box::new(decode_heap(&state, &mut input)?);
2736    if input.remaining() != 0 {
2737        return Err(FormatImageError::TrailingBytes);
2738    }
2739    refresh_runtime_pointers(&mut state, &mut heap);
2740    Ok((state, heap))
2741}
2742
2743fn zeroed_vec<T>(len: usize) -> Vec<T>
2744where
2745    T: Clone + Default,
2746{
2747    vec![T::default(); len]
2748}
2749
2750fn pointer_or_null<T>(storage: &mut Vec<T>) -> *mut T {
2751    if storage.is_empty() {
2752        core::ptr::null_mut()
2753    } else {
2754        storage.as_mut_ptr()
2755    }
2756}
2757
2758/// XeTeX `right_brace_token + "}"`, opening the braces `\write` wraps its token list in.
2759const WRITE_RIGHT_BRACE_TOKEN: halfword = 4_194_429;
2760/// XeTeX `end_write_token`, the frozen `\endwrite` control sequence.
2761const WRITE_END_TOKEN: halfword = 34_749_089;
2762/// XeTeX `left_brace_token + "{"`, closing the braces `\write` wraps its token list in.
2763const WRITE_LEFT_BRACE_TOKEN: halfword = 2_097_275;
2764
2765/// A sealed engine state, decoded and validated once when loaded, that every engine instance clones.
2766pub struct PortableFormatImage {
2767    state: Box<PortableTexState>,
2768    heap: Box<PortableTexHeap>,
2769}
2770
2771/// An engine node handle, only the engine hands these out.
2772#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2773pub struct PortableNodeHandle(i32);
2774
2775/// What a node in engine memory is.
2776#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2777pub enum PortableNodeKind {
2778    /// An hlist box.
2779    HorizontalBox,
2780    /// A vlist box.
2781    VerticalBox,
2782    /// A rule.
2783    Rule,
2784    /// An insertion.
2785    Insertion,
2786    /// A mark.
2787    Mark,
2788    /// A `\vadjust`.
2789    Adjustment,
2790    /// A ligature, its original characters are the snapshot's list.
2791    Ligature,
2792    /// A discretionary.
2793    Discretionary,
2794    /// An `\openout`, `\write`, `\closeout` or `\special` whatsit.
2795    OutputWhatsit,
2796    /// Any other whatsit.
2797    Whatsit,
2798    /// A math on or off node, its width is `\mathsurround`.
2799    Math,
2800    /// Glue, with a leader box when it is a leader.
2801    Glue,
2802    /// A kern.
2803    Kern,
2804    /// A penalty.
2805    Penalty,
2806    /// An unset box of an alignment still being built.
2807    UnsetBox,
2808    /// A math noad.
2809    Noad(PortableNoadKind),
2810    /// A math style change.
2811    Style,
2812    /// A `\mathchoice`.
2813    Choice,
2814    /// A character of a TFM font.
2815    Character,
2816    /// A native word.
2817    NativeWord,
2818    /// A single native glyph.
2819    NativeGlyph,
2820    /// A resolved host box marker, its character is the host box record id.
2821    HostBoxRef,
2822    /// A node type this engine does not name.
2823    Unknown(i32),
2824}
2825
2826/// The atom type of a math noad.
2827#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2828pub enum PortableNoadKind {
2829    /// `ord_noad`.
2830    Ord,
2831    /// `op_noad`.
2832    Op,
2833    /// `bin_noad`.
2834    Bin,
2835    /// `rel_noad`.
2836    Rel,
2837    /// `open_noad`.
2838    Open,
2839    /// `close_noad`.
2840    Close,
2841    /// `punct_noad`.
2842    Punct,
2843    /// `inner_noad`.
2844    Inner,
2845    /// `radical_noad`.
2846    Radical,
2847    /// `fraction_noad`.
2848    Fraction,
2849    /// `under_noad`.
2850    Under,
2851    /// `over_noad`.
2852    Over,
2853    /// `accent_noad`.
2854    Accent,
2855    /// `vcenter_noad`.
2856    Vcenter,
2857    /// `left_noad`.
2858    Left,
2859    /// `right_noad`.
2860    Right,
2861}
2862
2863/// Noad kinds in type order from [`ORD_NOAD`] to [`RIGHT_NOAD`].
2864const NOAD_KINDS: [PortableNoadKind; 16] = [
2865    PortableNoadKind::Ord,
2866    PortableNoadKind::Op,
2867    PortableNoadKind::Bin,
2868    PortableNoadKind::Rel,
2869    PortableNoadKind::Open,
2870    PortableNoadKind::Close,
2871    PortableNoadKind::Punct,
2872    PortableNoadKind::Inner,
2873    PortableNoadKind::Radical,
2874    PortableNoadKind::Fraction,
2875    PortableNoadKind::Under,
2876    PortableNoadKind::Over,
2877    PortableNoadKind::Accent,
2878    PortableNoadKind::Vcenter,
2879    PortableNoadKind::Left,
2880    PortableNoadKind::Right,
2881];
2882
2883/// A read only copy of one node's fields, dimensions in scaled points.
2884#[derive(Clone, Debug, PartialEq)]
2885pub struct PortableNodeSnapshot {
2886    /// The node.
2887    pub handle: PortableNodeHandle,
2888    /// What the node is.
2889    pub kind: PortableNodeKind,
2890    /// The raw subtype byte, the span count of an unset box.
2891    pub subtype: i32,
2892    /// Where the node came from when source tracking is on.
2893    pub source: Option<PortableSourceSpan>,
2894    /// The next node in its list.
2895    pub link: Option<PortableNodeHandle>,
2896    /// Font of a character, ligature, native word or native glyph.
2897    pub font: i32,
2898    /// Character of a character or ligature, glyph of a native glyph, record id of a host box marker.
2899    pub character: i32,
2900    /// Width, the natural width of glue and the surround of a math node.
2901    pub width: i32,
2902    /// Height.
2903    pub height: i32,
2904    /// Depth.
2905    pub depth: i32,
2906    /// Shift of a box.
2907    pub shift: i32,
2908    /// Contents of a box, or the original characters of a ligature.
2909    pub list: Option<PortableNodeHandle>,
2910    /// Leader box or rule of leader glue.
2911    pub leader: Option<PortableNodeHandle>,
2912    /// Shaped glyphs of a native word or glyph.
2913    pub native_glyphs: Vec<PortableNativeGlyph>,
2914    /// Box glue set ratio from `hpack` or `vpack`.
2915    pub glue_set: f64,
2916    /// Box glue sign: 0 normal, 1 stretching, 2 shrinking.
2917    pub glue_sign: i32,
2918    /// Box glue order that stretches or shrinks.
2919    pub glue_order: i32,
2920    /// Stretch of glue or of an unset box.
2921    pub glue_stretch: i32,
2922    /// Shrink of glue or of an unset box.
2923    pub glue_shrink: i32,
2924    /// Order of the stretch of glue or of an unset box.
2925    pub glue_stretch_order: i32,
2926    /// Order of the shrink of glue or of an unset box.
2927    pub glue_shrink_order: i32,
2928}
2929
2930impl Clone for PortableFormatImage {
2931    fn clone(&self) -> Self {
2932        let (state, heap) = self.instantiate();
2933        Self { state, heap }
2934    }
2935}
2936
2937impl PortableFormatImage {
2938    /// The image of a state before `initialize_format_state`, engines built from it only initialize.
2939    pub fn empty() -> Self {
2940        let mut state = boxed_state_zeroed();
2941        let mut heap = Box::<PortableTexHeap>::default();
2942        refresh_runtime_pointers(&mut state, &mut heap);
2943        Self { state, heap }
2944    }
2945
2946    /// Seals a deep copy of a live engine state.
2947    fn from_engine_state(state: &PortableTexState, heap: &PortableTexHeap) -> Self {
2948        let mut state = boxed_state_copy(state);
2949        let mut heap = Box::new(heap.clone());
2950        seal_as_format_snapshot(&mut state, &mut heap);
2951        Self { state, heap }
2952    }
2953
2954    /// Deep copies the sealed state for a new engine, rebinding its pointers to the copy.
2955    fn instantiate(&self) -> (Box<PortableTexState>, Box<PortableTexHeap>) {
2956        let mut state = boxed_state_copy(&self.state);
2957        let mut heap = Box::new((*self.heap).clone());
2958        refresh_runtime_pointers(&mut state, &mut heap);
2959        (state, heap)
2960    }
2961
2962    /// Returns the validation tag stored at the start of every image this build writes.
2963    #[must_use]
2964    pub fn validation_tag() -> [u8; VALIDATION_TAG_LEN] {
2965        validation_tag()
2966    }
2967
2968    /// Reports whether a serialized image carries this build's validation tag.
2969    #[must_use]
2970    pub fn bytes_match_target(bytes: &[u8]) -> bool {
2971        bytes.get(..VALIDATION_TAG_LEN) == Some(&validation_tag()[..])
2972    }
2973
2974    /// Decodes and validates a static image once, later instantiation only clones the decoded state.
2975    pub fn from_static_bytes(bytes: &'static [u8]) -> Result<Self, FormatImageError> {
2976        Self::from_bytes(bytes)
2977    }
2978
2979    /// Serializes this image, the bytes are deterministic and load in this build on any target of the same byte order.
2980    #[must_use]
2981    pub fn to_bytes(&self) -> Vec<u8> {
2982        encode_image(&self.state, &self.heap)
2983    }
2984
2985    /// Decodes and validates an image produced by [`Self::to_bytes`].
2986    pub fn from_bytes(bytes: &[u8]) -> Result<Self, FormatImageError> {
2987        let (state, heap) = decode_image(bytes)?;
2988        Ok(Self { state, heap })
2989    }
2990
2991    /// Resident bytes of the engine arrays once instantiated from this image.
2992    #[must_use]
2993    pub fn state_array_bytes(&self) -> usize {
2994        heap_array_bytes(&self.heap)
2995    }
2996}
2997
2998/// Size in bytes of one TeX `memory_word`, 8 as in XeTeX.
2999#[must_use]
3000pub fn memory_word_bytes() -> usize {
3001    core::mem::size_of::<memoryword>()
3002}
3003
3004pub struct PortableTexEngine<'resources> {
3005    pub(crate) state: Box<PortableTexState>,
3006    pub(crate) heap: Box<PortableTexHeap>,
3007    pub(crate) resources: Box<dyn ResourceProvider + 'resources>,
3008    pub(crate) fonts: Box<dyn FontPlatform + 'resources>,
3009    pub(crate) platform: Box<dyn PortablePlatform + 'resources>,
3010    pub(crate) nameoffile_storage: Vec<UTF8code>,
3011    native_glyph_infos: std::collections::BTreeMap<i32, PortableNativeGlyphInfo>,
3012    // Host box render payloads by record id, markers in node memory carry only the id.
3013    hostbox_records: std::collections::BTreeMap<integer, PortableHostBox>,
3014    character_protrusions: std::collections::BTreeMap<(integer, u32, integer), integer>,
3015    pub(crate) resource_requests: usize,
3016    pub(crate) resource_request_records: Vec<PortableResourceRequestRecord>,
3017    pub(crate) virtual_files: std::collections::BTreeMap<String, Vec<u8>>,
3018    pub(crate) transcript_bytes: Vec<u8>,
3019    pub(crate) current_input_package_owner: Option<String>,
3020    pub(crate) stripped_page_builds: usize,
3021    pub(crate) stripped_shipouts: usize,
3022    pub(crate) stripped_special_outputs: usize,
3023    pub(crate) stripped_picture_loads: usize,
3024    pub(crate) stripped_source_specials: usize,
3025    pub(crate) stripped_write_whatsit_diagnostics: usize,
3026    pub(crate) stripped_pdf_extensions: usize,
3027    pub(crate) stripped_page_top_prunes: usize,
3028    pub(crate) last_stripped_shipout_box: Option<PortableNodeHandle>,
3029    pub(crate) fragment_capture_enabled: bool,
3030    pub(crate) format_initialization: bool,
3031    pub(crate) captured_fragment_root: Option<PortableNodeHandle>,
3032    pub(crate) last_abort_status: Option<integer>,
3033    /// The most recent surfaced [`EngineError`] with its input position.
3034    pub(crate) last_error: Option<PortableError>,
3035    /// Whether fragment input is sandboxed, math breakouts, file access and runaway loops then end the run.
3036    pub(crate) sandbox: bool,
3037    /// Math nesting depth under the sandbox, the wrapper math is depth 1 and returning to 0 closes it.
3038    pub(crate) sandbox_math_depth: i32,
3039    /// Set once the wrapper math has opened, a later reopening at depth 0 is a breakout.
3040    pub(crate) sandbox_math_opened: bool,
3041    /// Sandbox work consumed this run, one unit per command, macro call and expansion.
3042    pub(crate) sandbox_ops: u64,
3043    /// Sandbox work allowed per run before the fragment is rejected.
3044    pub(crate) sandbox_op_budget: u64,
3045    /// Primary input byte offset of the host wrapper's suffix, only a math shift from there closes the wrapper math.
3046    pub(crate) sandbox_suffix_start: Option<u32>,
3047    /// Bytes of the error message being printed, from `print_err` to the start of `error`.
3048    error_message_capture: Option<Vec<u8>>,
3049    /// The last complete error message, taken when the error surfaces.
3050    captured_error_message: Option<String>,
3051}
3052
3053pub(crate) fn zround(value: real) -> integer {
3054    value.round() as integer
3055}
3056
3057/// Message a run entry point reports for an engine whose state was never initialized.
3058const UNINITIALIZED_STATE_MESSAGE: &str = "engine state is not initialized";
3059
3060impl<'resources> PortableTexEngine<'resources> {
3061    pub fn from_format<R>(format: &PortableFormatImage, resources: R) -> Self
3062    where
3063        R: ResourceProvider + 'resources,
3064    {
3065        let (state, heap) = format.instantiate();
3066        Self {
3067            state,
3068            heap,
3069            resources: Box::new(resources),
3070            fonts: Box::<EmptyFontPlatform>::default(),
3071            platform: Box::<EmptyPlatform>::default(),
3072            nameoffile_storage: Vec::new(),
3073            native_glyph_infos: std::collections::BTreeMap::new(),
3074            hostbox_records: std::collections::BTreeMap::new(),
3075            character_protrusions: std::collections::BTreeMap::new(),
3076            resource_requests: 0,
3077            resource_request_records: Vec::new(),
3078            virtual_files: std::collections::BTreeMap::new(),
3079            transcript_bytes: Vec::new(),
3080            current_input_package_owner: None,
3081            stripped_page_builds: 0,
3082            stripped_shipouts: 0,
3083            stripped_special_outputs: 0,
3084            stripped_picture_loads: 0,
3085            stripped_source_specials: 0,
3086            stripped_write_whatsit_diagnostics: 0,
3087            stripped_pdf_extensions: 0,
3088            stripped_page_top_prunes: 0,
3089            last_stripped_shipout_box: None,
3090            fragment_capture_enabled: false,
3091            format_initialization: false,
3092            captured_fragment_root: None,
3093            last_abort_status: None,
3094            last_error: None,
3095            sandbox: false,
3096            sandbox_math_depth: 0,
3097            sandbox_math_opened: false,
3098            sandbox_ops: 0,
3099            sandbox_op_budget: SANDBOX_OP_BUDGET,
3100            sandbox_suffix_start: None,
3101            error_message_capture: None,
3102            captured_error_message: None,
3103        }
3104    }
3105
3106    pub fn with_font_platform<F>(mut self, fonts: F) -> Self
3107    where
3108        F: FontPlatform + 'resources,
3109    {
3110        self.fonts = Box::new(fonts);
3111        self
3112    }
3113
3114    pub fn with_platform<P>(mut self, platform: P) -> Self
3115    where
3116        P: PortablePlatform + 'resources,
3117    {
3118        self.platform = Box::new(platform);
3119        self
3120    }
3121
3122    /// Runs INITEX initialization, the only entry point that accepts an uninitialized state.
3123    pub fn initialize_format_state(self: &mut Self) -> bool {
3124        self.state.initialized = false;
3125        let completed = self.catch_engine_abort(|engine| unsafe {
3126            let this = engine as *mut PortableTexEngine<'_>;
3127            allocate_initial_arrays(&mut engine.state, &mut engine.heap);
3128            engine.initialize();
3129            engine.state.eTeXmode = 1 as eightbits;
3130            if engine.getstringsstarted()? == 0 {
3131                Self::abort_engine(this, 1 as integer)?;
3132            }
3133            engine.initprim()?;
3134            engine.init_xetex_startup_primitives()?;
3135            engine.register_host_box_primitive()?;
3136            engine.state.initstrptr = engine.state.strptr;
3137            engine.state.initpoolptr = engine.state.poolptr;
3138            // Replaces the `mainbody` assignment `magic_offset := str_start[math_spacing] - 9 * ord_noad`.
3139            let math_spacing = Self::pool_string_index(POOL_MATH_SPACING).unwrap_or_default();
3140            engine.state.magicoffset = *engine.state.strstart.offset(math_spacing) - 9 * ORD_NOAD;
3141            engine.state.alignstate = NEUTRAL_ALIGN_STATE;
3142            Ok(())
3143        });
3144        self.state.initialized = completed;
3145        completed
3146    }
3147
3148    pub(crate) fn ensure_nativetext_capacity(
3149        engine: &mut PortableTexEngine<'_>,
3150        required: integer,
3151    ) {
3152        let required = required.max(0) as usize;
3153        if engine.heap.nativetext_storage.len() < required {
3154            engine.heap.nativetext_storage.resize(required, UTF16code::default());
3155        }
3156        engine.state.nativetext = pointer_or_null(&mut engine.heap.nativetext_storage);
3157    }
3158
3159    unsafe fn init_xetex_startup_primitives(&mut self) -> EngineFlow<()> {
3160        self.state.nonewcontrolsequence = false_0 as boolean;
3161        (&mut *(self as *mut PortableTexEngine<'_>))
3162            .zprimitive(66755 as i64 as strnumber, 59 as i32 as quarterword, 41 as i32)?;
3163        (&mut *(self as *mut PortableTexEngine<'_>))
3164            .zprimitive(66756 as i64 as strnumber, 59 as i32 as quarterword, 42 as i32)?;
3165        (&mut *(self as *mut PortableTexEngine<'_>))
3166            .zprimitive(66757 as i64 as strnumber, 59 as i32 as quarterword, 43 as i32)?;
3167        (&mut *(self as *mut PortableTexEngine<'_>))
3168            .zprimitive(66758 as i64 as strnumber, 59 as i32 as quarterword, 46 as i32)?;
3169        (&mut *(self as *mut PortableTexEngine<'_>))
3170            .zprimitive(
3171                66759 as i64 as strnumber,
3172                73 as i32 as quarterword,
3173                1206306 as i64 as halfword,
3174            )?;
3175        (&mut *(self as *mut PortableTexEngine<'_>))
3176            .zprimitive(66760 as i64 as strnumber, 59 as i32 as quarterword, 23 as i32)?;
3177        (&mut *(self as *mut PortableTexEngine<'_>))
3178            .zprimitive(66816 as i64 as strnumber, 71 as i32 as quarterword, 3 as i32)?;
3179        (&mut *(self as *mut PortableTexEngine<'_>))
3180            .zprimitive(66817 as i64 as strnumber, 71 as i32 as quarterword, 19 as i32)?;
3181        (&mut *(self as *mut PortableTexEngine<'_>))
3182            .zprimitive(66115 as i64 as strnumber, 111 as i32 as quarterword, 5 as i32)?;
3183        (&mut *(self as *mut PortableTexEngine<'_>))
3184            .zprimitive(66818 as i64 as strnumber, 71 as i32 as quarterword, 27 as i32)?;
3185        (&mut *(self as *mut PortableTexEngine<'_>))
3186            .zprimitive(
3187                66819 as i64 as strnumber,
3188                111 as i32 as quarterword,
3189                33 as i32,
3190            )?;
3191        (&mut *(self as *mut PortableTexEngine<'_>))
3192            .zprimitive(66820 as i64 as strnumber, 71 as i32 as quarterword, 28 as i32)?;
3193        (&mut *(self as *mut PortableTexEngine<'_>))
3194            .zprimitive(66821 as i64 as strnumber, 71 as i32 as quarterword, 29 as i32)?;
3195        (&mut *(self as *mut PortableTexEngine<'_>))
3196            .zprimitive(66822 as i64 as strnumber, 71 as i32 as quarterword, 30 as i32)?;
3197        (&mut *(self as *mut PortableTexEngine<'_>))
3198            .zprimitive(66823 as i64 as strnumber, 71 as i32 as quarterword, 31 as i32)?;
3199        (&mut *(self as *mut PortableTexEngine<'_>))
3200            .zprimitive(66824 as i64 as strnumber, 71 as i32 as quarterword, 32 as i32)?;
3201        (&mut *(self as *mut PortableTexEngine<'_>))
3202            .zprimitive(66825 as i64 as strnumber, 71 as i32 as quarterword, 33 as i32)?;
3203        (&mut *(self as *mut PortableTexEngine<'_>))
3204            .zprimitive(66826 as i64 as strnumber, 71 as i32 as quarterword, 34 as i32)?;
3205        (&mut *(self as *mut PortableTexEngine<'_>))
3206            .zprimitive(66827 as i64 as strnumber, 71 as i32 as quarterword, 35 as i32)?;
3207        (&mut *(self as *mut PortableTexEngine<'_>))
3208            .zprimitive(66828 as i64 as strnumber, 71 as i32 as quarterword, 36 as i32)?;
3209        (&mut *(self as *mut PortableTexEngine<'_>))
3210            .zprimitive(66829 as i64 as strnumber, 71 as i32 as quarterword, 37 as i32)?;
3211        (&mut *(self as *mut PortableTexEngine<'_>))
3212            .zprimitive(66830 as i64 as strnumber, 71 as i32 as quarterword, 38 as i32)?;
3213        (&mut *(self as *mut PortableTexEngine<'_>))
3214            .zprimitive(66831 as i64 as strnumber, 71 as i32 as quarterword, 39 as i32)?;
3215        (&mut *(self as *mut PortableTexEngine<'_>))
3216            .zprimitive(66832 as i64 as strnumber, 71 as i32 as quarterword, 40 as i32)?;
3217        (&mut *(self as *mut PortableTexEngine<'_>))
3218            .zprimitive(66833 as i64 as strnumber, 71 as i32 as quarterword, 41 as i32)?;
3219        (&mut *(self as *mut PortableTexEngine<'_>))
3220            .zprimitive(66834 as i64 as strnumber, 71 as i32 as quarterword, 42 as i32)?;
3221        (&mut *(self as *mut PortableTexEngine<'_>))
3222            .zprimitive(
3223                66835 as i64 as strnumber,
3224                111 as i32 as quarterword,
3225                34 as i32,
3226            )?;
3227        (&mut *(self as *mut PortableTexEngine<'_>))
3228            .zprimitive(
3229                66836 as i64 as strnumber,
3230                111 as i32 as quarterword,
3231                35 as i32,
3232            )?;
3233        (&mut *(self as *mut PortableTexEngine<'_>))
3234            .zprimitive(
3235                66837 as i64 as strnumber,
3236                111 as i32 as quarterword,
3237                36 as i32,
3238            )?;
3239        (&mut *(self as *mut PortableTexEngine<'_>))
3240            .zprimitive(66838 as i64 as strnumber, 71 as i32 as quarterword, 43 as i32)?;
3241        (&mut *(self as *mut PortableTexEngine<'_>))
3242            .zprimitive(66839 as i64 as strnumber, 71 as i32 as quarterword, 44 as i32)?;
3243        (&mut *(self as *mut PortableTexEngine<'_>))
3244            .zprimitive(66840 as i64 as strnumber, 71 as i32 as quarterword, 45 as i32)?;
3245        (&mut *(self as *mut PortableTexEngine<'_>))
3246            .zprimitive(66841 as i64 as strnumber, 71 as i32 as quarterword, 46 as i32)?;
3247        (&mut *(self as *mut PortableTexEngine<'_>))
3248            .zprimitive(66842 as i64 as strnumber, 71 as i32 as quarterword, 47 as i32)?;
3249        (&mut *(self as *mut PortableTexEngine<'_>))
3250            .zprimitive(66843 as i64 as strnumber, 71 as i32 as quarterword, 48 as i32)?;
3251        (&mut *(self as *mut PortableTexEngine<'_>))
3252            .zprimitive(66844 as i64 as strnumber, 71 as i32 as quarterword, 49 as i32)?;
3253        (&mut *(self as *mut PortableTexEngine<'_>))
3254            .zprimitive(66845 as i64 as strnumber, 71 as i32 as quarterword, 50 as i32)?;
3255        (&mut *(self as *mut PortableTexEngine<'_>))
3256            .zprimitive(66846 as i64 as strnumber, 71 as i32 as quarterword, 55 as i32)?;
3257        (&mut *(self as *mut PortableTexEngine<'_>))
3258            .zprimitive(
3259                66847 as i64 as strnumber,
3260                111 as i32 as quarterword,
3261                37 as i32,
3262            )?;
3263        (&mut *(self as *mut PortableTexEngine<'_>))
3264            .zprimitive(66848 as i64 as strnumber, 71 as i32 as quarterword, 51 as i32)?;
3265        (&mut *(self as *mut PortableTexEngine<'_>))
3266            .zprimitive(66849 as i64 as strnumber, 71 as i32 as quarterword, 52 as i32)?;
3267        (&mut *(self as *mut PortableTexEngine<'_>))
3268            .zprimitive(66850 as i64 as strnumber, 71 as i32 as quarterword, 53 as i32)?;
3269        (&mut *(self as *mut PortableTexEngine<'_>))
3270            .zprimitive(66851 as i64 as strnumber, 71 as i32 as quarterword, 54 as i32)?;
3271        (&mut *(self as *mut PortableTexEngine<'_>))
3272            .zprimitive(
3273                66861 as i64 as strnumber,
3274                73 as i32 as quarterword,
3275                1206305 as i64 as halfword,
3276            )?;
3277        (&mut *(self as *mut PortableTexEngine<'_>))
3278            .zprimitive(
3279                66862 as i64 as strnumber,
3280                74 as i32 as quarterword,
3281                7892325 as i64 as halfword,
3282            )?;
3283        (&mut *(self as *mut PortableTexEngine<'_>))
3284            .zprimitive(
3285                66863 as i64 as strnumber,
3286                74 as i32 as quarterword,
3287                7892326 as i64 as halfword,
3288            )?;
3289        (&mut *(self as *mut PortableTexEngine<'_>))
3290            .zprimitive(
3291                66864 as i64 as strnumber,
3292                74 as i32 as quarterword,
3293                7892327 as i64 as halfword,
3294            )?;
3295        (&mut *(self as *mut PortableTexEngine<'_>))
3296            .zprimitive(
3297                66865 as i64 as strnumber,
3298                74 as i32 as quarterword,
3299                7892328 as i64 as halfword,
3300            )?;
3301        (&mut *(self as *mut PortableTexEngine<'_>))
3302            .zprimitive(
3303                66866 as i64 as strnumber,
3304                74 as i32 as quarterword,
3305                7892329 as i64 as halfword,
3306            )?;
3307        (&mut *(self as *mut PortableTexEngine<'_>))
3308            .zprimitive(
3309                66867 as i64 as strnumber,
3310                74 as i32 as quarterword,
3311                7892330 as i64 as halfword,
3312            )?;
3313        (&mut *(self as *mut PortableTexEngine<'_>))
3314            .zprimitive(
3315                66868 as i64 as strnumber,
3316                74 as i32 as quarterword,
3317                7892331 as i64 as halfword,
3318            )?;
3319        (&mut *(self as *mut PortableTexEngine<'_>))
3320            .zprimitive(
3321                66869 as i64 as strnumber,
3322                74 as i32 as quarterword,
3323                7892332 as i64 as halfword,
3324            )?;
3325        (&mut *(self as *mut PortableTexEngine<'_>))
3326            .zprimitive(
3327                66870 as i64 as strnumber,
3328                74 as i32 as quarterword,
3329                7892333 as i64 as halfword,
3330            )?;
3331        (&mut *(self as *mut PortableTexEngine<'_>))
3332            .zprimitive(
3333                66871 as i64 as strnumber,
3334                74 as i32 as quarterword,
3335                7892335 as i64 as halfword,
3336            )?;
3337        (&mut *(self as *mut PortableTexEngine<'_>))
3338            .zprimitive(66885 as i64 as strnumber, 71 as i32 as quarterword, 20 as i32)?;
3339        (&mut *(self as *mut PortableTexEngine<'_>))
3340            .zprimitive(66886 as i64 as strnumber, 71 as i32 as quarterword, 21 as i32)?;
3341        (&mut *(self as *mut PortableTexEngine<'_>))
3342            .zprimitive(66887 as i64 as strnumber, 71 as i32 as quarterword, 22 as i32)?;
3343        (&mut *(self as *mut PortableTexEngine<'_>))
3344            .zprimitive(66888 as i64 as strnumber, 71 as i32 as quarterword, 23 as i32)?;
3345        (&mut *(self as *mut PortableTexEngine<'_>))
3346            .zprimitive(66889 as i64 as strnumber, 71 as i32 as quarterword, 24 as i32)?;
3347        (&mut *(self as *mut PortableTexEngine<'_>))
3348            .zprimitive(66890 as i64 as strnumber, 71 as i32 as quarterword, 56 as i32)?;
3349        (&mut *(self as *mut PortableTexEngine<'_>))
3350            .zprimitive(66891 as i64 as strnumber, 71 as i32 as quarterword, 57 as i32)?;
3351        (&mut *(self as *mut PortableTexEngine<'_>))
3352            .zprimitive(66892 as i64 as strnumber, 71 as i32 as quarterword, 58 as i32)?;
3353        (&mut *(self as *mut PortableTexEngine<'_>))
3354            .zprimitive(66893 as i64 as strnumber, 71 as i32 as quarterword, 59 as i32)?;
3355        (&mut *(self as *mut PortableTexEngine<'_>))
3356            .zprimitive(66894 as i64 as strnumber, 71 as i32 as quarterword, 60 as i32)?;
3357        (&mut *(self as *mut PortableTexEngine<'_>))
3358            .zprimitive(66895 as i64 as strnumber, 71 as i32 as quarterword, 61 as i32)?;
3359        (&mut *(self as *mut PortableTexEngine<'_>))
3360            .zprimitive(66896 as i64 as strnumber, 71 as i32 as quarterword, 62 as i32)?;
3361        (&mut *(self as *mut PortableTexEngine<'_>))
3362            .zprimitive(66897 as i64 as strnumber, 19 as i32 as quarterword, 4 as i32)?;
3363        (&mut *(self as *mut PortableTexEngine<'_>))
3364            .zprimitive(66899 as i64 as strnumber, 19 as i32 as quarterword, 5 as i32)?;
3365        (&mut *(self as *mut PortableTexEngine<'_>))
3366            .zprimitive(66900 as i64 as strnumber, 112 as i32 as quarterword, 1 as i32)?;
3367        (&mut *(self as *mut PortableTexEngine<'_>))
3368            .zprimitive(66901 as i64 as strnumber, 112 as i32 as quarterword, 5 as i32)?;
3369        (&mut *(self as *mut PortableTexEngine<'_>))
3370            .zprimitive(66902 as i64 as strnumber, 19 as i32 as quarterword, 6 as i32)?;
3371        (&mut *(self as *mut PortableTexEngine<'_>))
3372            .zprimitive(66906 as i64 as strnumber, 83 as i32 as quarterword, 2 as i32)?;
3373        (&mut *(self as *mut PortableTexEngine<'_>))
3374            .zprimitive(66288 as i64 as strnumber, 49 as i32 as quarterword, 1 as i32)?;
3375        (&mut *(self as *mut PortableTexEngine<'_>))
3376            .zprimitive(
3377                66910 as i64 as strnumber,
3378                74 as i32 as quarterword,
3379                7892334 as i64 as halfword,
3380            )?;
3381        (&mut *(self as *mut PortableTexEngine<'_>))
3382            .zprimitive(
3383                66911 as i64 as strnumber,
3384                74 as i32 as quarterword,
3385                7892339 as i64 as halfword,
3386            )?;
3387        (&mut *(self as *mut PortableTexEngine<'_>))
3388            .zprimitive(
3389                66912 as i64 as strnumber,
3390                74 as i32 as quarterword,
3391                7892341 as i64 as halfword,
3392            )?;
3393        (&mut *(self as *mut PortableTexEngine<'_>))
3394            .zprimitive(
3395                66913 as i64 as strnumber,
3396                74 as i32 as quarterword,
3397                7892342 as i64 as halfword,
3398            )?;
3399        (&mut *(self as *mut PortableTexEngine<'_>))
3400            .zprimitive(
3401                66914 as i64 as strnumber,
3402                74 as i32 as quarterword,
3403                7892343 as i64 as halfword,
3404            )?;
3405        (&mut *(self as *mut PortableTexEngine<'_>))
3406            .zprimitive(
3407                66915 as i64 as strnumber,
3408                74 as i32 as quarterword,
3409                7892340 as i64 as halfword,
3410            )?;
3411        (&mut *(self as *mut PortableTexEngine<'_>))
3412            .zprimitive(
3413                66916 as i64 as strnumber,
3414                74 as i32 as quarterword,
3415                7892344 as i64 as halfword,
3416            )?;
3417        (&mut *(self as *mut PortableTexEngine<'_>))
3418            .zprimitive(
3419                66917 as i64 as strnumber,
3420                74 as i32 as quarterword,
3421                7892347 as i64 as halfword,
3422            )?;
3423        (&mut *(self as *mut PortableTexEngine<'_>))
3424            .zprimitive(
3425                66918 as i64 as strnumber,
3426                74 as i32 as quarterword,
3427                7892348 as i64 as halfword,
3428            )?;
3429        (&mut *(self as *mut PortableTexEngine<'_>))
3430            .zprimitive(
3431                66919 as i64 as strnumber,
3432                74 as i32 as quarterword,
3433                7892349 as i64 as halfword,
3434            )?;
3435        (&mut *(self as *mut PortableTexEngine<'_>))
3436            .zprimitive(
3437                66920 as i64 as strnumber,
3438                74 as i32 as quarterword,
3439                7892350 as i64 as halfword,
3440            )?;
3441        (&mut *(self as *mut PortableTexEngine<'_>))
3442            .zprimitive(66761 as i64 as strnumber, 59 as i32 as quarterword, 44 as i32)?;
3443        (&mut *(self as *mut PortableTexEngine<'_>))
3444            .zprimitive(66762 as i64 as strnumber, 59 as i32 as quarterword, 45 as i32)?;
3445        (&mut *(self as *mut PortableTexEngine<'_>))
3446            .zprimitive(66921 as i64 as strnumber, 33 as i32 as quarterword, 6 as i32)?;
3447        (&mut *(self as *mut PortableTexEngine<'_>))
3448            .zprimitive(66922 as i64 as strnumber, 33 as i32 as quarterword, 7 as i32)?;
3449        (&mut *(self as *mut PortableTexEngine<'_>))
3450            .zprimitive(66923 as i64 as strnumber, 33 as i32 as quarterword, 10 as i32)?;
3451        (&mut *(self as *mut PortableTexEngine<'_>))
3452            .zprimitive(66924 as i64 as strnumber, 33 as i32 as quarterword, 11 as i32)?;
3453        (&mut *(self as *mut PortableTexEngine<'_>))
3454            .zprimitive(66933 as i64 as strnumber, 107 as i32 as quarterword, 2 as i32)?;
3455        (&mut *(self as *mut PortableTexEngine<'_>))
3456            .zprimitive(66935 as i64 as strnumber, 98 as i32 as quarterword, 1 as i32)?;
3457        (&mut *(self as *mut PortableTexEngine<'_>))
3458            .zprimitive(66164 as i64 as strnumber, 105 as i32 as quarterword, 1 as i32)?;
3459        (&mut *(self as *mut PortableTexEngine<'_>))
3460            .zprimitive(
3461                66936 as i64 as strnumber,
3462                108 as i32 as quarterword,
3463                17 as i32,
3464            )?;
3465        (&mut *(self as *mut PortableTexEngine<'_>))
3466            .zprimitive(
3467                66937 as i64 as strnumber,
3468                108 as i32 as quarterword,
3469                18 as i32,
3470            )?;
3471        (&mut *(self as *mut PortableTexEngine<'_>))
3472            .zprimitive(
3473                66938 as i64 as strnumber,
3474                108 as i32 as quarterword,
3475                19 as i32,
3476            )?;
3477        (&mut *(self as *mut PortableTexEngine<'_>))
3478            .zprimitive(
3479                66939 as i64 as strnumber,
3480                108 as i32 as quarterword,
3481                20 as i32,
3482            )?;
3483        (&mut *(self as *mut PortableTexEngine<'_>))
3484            .zprimitive(66623 as i64 as strnumber, 95 as i32 as quarterword, 8 as i32)?;
3485        (&mut *(self as *mut PortableTexEngine<'_>))
3486            .zprimitive(66945 as i64 as strnumber, 71 as i32 as quarterword, 67 as i32)?;
3487        (&mut *(self as *mut PortableTexEngine<'_>))
3488            .zprimitive(66946 as i64 as strnumber, 71 as i32 as quarterword, 68 as i32)?;
3489        (&mut *(self as *mut PortableTexEngine<'_>))
3490            .zprimitive(66947 as i64 as strnumber, 71 as i32 as quarterword, 69 as i32)?;
3491        (&mut *(self as *mut PortableTexEngine<'_>))
3492            .zprimitive(66948 as i64 as strnumber, 71 as i32 as quarterword, 70 as i32)?;
3493        (&mut *(self as *mut PortableTexEngine<'_>))
3494            .zprimitive(66952 as i64 as strnumber, 71 as i32 as quarterword, 25 as i32)?;
3495        (&mut *(self as *mut PortableTexEngine<'_>))
3496            .zprimitive(66953 as i64 as strnumber, 71 as i32 as quarterword, 26 as i32)?;
3497        (&mut *(self as *mut PortableTexEngine<'_>))
3498            .zprimitive(66954 as i64 as strnumber, 71 as i32 as quarterword, 63 as i32)?;
3499        (&mut *(self as *mut PortableTexEngine<'_>))
3500            .zprimitive(66955 as i64 as strnumber, 71 as i32 as quarterword, 64 as i32)?;
3501        (&mut *(self as *mut PortableTexEngine<'_>))
3502            .zprimitive(66956 as i64 as strnumber, 71 as i32 as quarterword, 65 as i32)?;
3503        (&mut *(self as *mut PortableTexEngine<'_>))
3504            .zprimitive(66957 as i64 as strnumber, 71 as i32 as quarterword, 66 as i32)?;
3505        (&mut *(self as *mut PortableTexEngine<'_>))
3506            .zprimitive(66958 as i64 as strnumber, 18 as i32 as quarterword, 5 as i32)?;
3507        (&mut *(self as *mut PortableTexEngine<'_>))
3508            .zprimitive(66959 as i64 as strnumber, 113 as i32 as quarterword, 5 as i32)?;
3509        (&mut *(self as *mut PortableTexEngine<'_>))
3510            .zprimitive(66960 as i64 as strnumber, 113 as i32 as quarterword, 6 as i32)?;
3511        (&mut *(self as *mut PortableTexEngine<'_>))
3512            .zprimitive(66961 as i64 as strnumber, 113 as i32 as quarterword, 7 as i32)?;
3513        (&mut *(self as *mut PortableTexEngine<'_>))
3514            .zprimitive(66962 as i64 as strnumber, 113 as i32 as quarterword, 8 as i32)?;
3515        (&mut *(self as *mut PortableTexEngine<'_>))
3516            .zprimitive(66963 as i64 as strnumber, 113 as i32 as quarterword, 9 as i32)?;
3517        (&mut *(self as *mut PortableTexEngine<'_>))
3518            .zprimitive(66968 as i64 as strnumber, 24 as i32 as quarterword, 2 as i32)?;
3519        (&mut *(self as *mut PortableTexEngine<'_>))
3520            .zprimitive(66969 as i64 as strnumber, 24 as i32 as quarterword, 3 as i32)?;
3521        (&mut *(self as *mut PortableTexEngine<'_>))
3522            .zprimitive(
3523                66970 as i64 as strnumber,
3524                85 as i32 as quarterword,
3525                1206563 as i64 as halfword,
3526            )?;
3527        (&mut *(self as *mut PortableTexEngine<'_>))
3528            .zprimitive(
3529                66971 as i64 as strnumber,
3530                85 as i32 as quarterword,
3531                1206564 as i64 as halfword,
3532            )?;
3533        (&mut *(self as *mut PortableTexEngine<'_>))
3534            .zprimitive(
3535                66972 as i64 as strnumber,
3536                85 as i32 as quarterword,
3537                1206565 as i64 as halfword,
3538            )?;
3539        (&mut *(self as *mut PortableTexEngine<'_>))
3540            .zprimitive(
3541                66973 as i64 as strnumber,
3542                85 as i32 as quarterword,
3543                1206566 as i64 as halfword,
3544            )?;
3545        if *self.state.buffer.offset(self.state.curinput.locfield as isize) == 42 as i32
3546        {
3547            self.state.curinput.locfield += 1;
3548        }
3549        self.state.eTeXmode = 1 as eightbits;
3550        self.state.maxregnum = 32767 as i32 as halfword;
3551        self.state.maxreghelpline = 66965 as i64 as strnumber;
3552        Ok(())
3553    }
3554
3555    pub fn begin_primary_input(self: &mut Self, name: &str, bytes: Vec<u8>) -> bool {
3556        let mut started = false;
3557        let completed = self.run_initialized(|engine| {
3558            started = unsafe { engine.begin_primary_input_raw(name, bytes) }? != 0;
3559            Ok(())
3560        });
3561        completed && started
3562    }
3563
3564    pub fn run_main_control(self: &mut Self) -> bool {
3565        self.run_initialized(|engine| unsafe { engine.maincontrol() })
3566    }
3567
3568    pub fn run_format_initialization(self: &mut Self) -> bool {
3569        self.format_initialization = true;
3570        let completed = self.run_initialized(|engine| unsafe { engine.maincontrol() });
3571        self.format_initialization = false;
3572        completed
3573    }
3574
3575    /// Starts capturing a fragment root and drops the previous fragment's host box records.
3576    pub fn begin_fragment_capture(self: &mut Self) {
3577        self.fragment_capture_enabled = true;
3578        self.captured_fragment_root = None;
3579        // Ids are never reused, so a marker kept in a global box register from a dropped record resolves to none.
3580        self.hostbox_records.clear();
3581    }
3582
3583    pub fn end_fragment_capture(self: &mut Self) {
3584        self.fragment_capture_enabled = false;
3585    }
3586
3587    /// Runs `run` through [`Self::catch_engine_abort`], refusing a state never initialized.
3588    fn run_initialized<F>(self: &mut Self, run: F) -> bool
3589    where
3590        F: FnOnce(&mut Self) -> EngineFlow<()>,
3591    {
3592        if !self.state.initialized {
3593            self.last_abort_status = None;
3594            self.last_error = Some(PortableError {
3595                message: UNINITIALIZED_STATE_MESSAGE.into(),
3596                kind: PortableErrorKind::Tex,
3597                line: 0,
3598                span: None,
3599            });
3600            return false;
3601        }
3602        self.catch_engine_abort(run)
3603    }
3604
3605    fn catch_engine_abort<F>(self: &mut Self, run: F) -> bool
3606    where
3607        F: FnOnce(&mut Self) -> EngineFlow<()>,
3608    {
3609        // Status 0, the normal end of `\end` or `\dump`, completes the run and any other status aborts it.
3610        self.last_abort_status = None;
3611        self.last_error = None;
3612        match run(self) {
3613            Ok(()) => self.last_abort_status.is_none(),
3614            Err(EngineBreak::Abort(EngineAbort { status: 0 })) => {
3615                self.last_abort_status = None;
3616                true
3617            }
3618            Err(EngineBreak::Abort(EngineAbort { status })) => {
3619                self.last_abort_status = Some(status);
3620                false
3621            }
3622            Err(EngineBreak::Error(error)) => {
3623                self.last_error = Some(PortableError {
3624                    message: error.message,
3625                    kind: error.kind,
3626                    line: self.state.line,
3627                    span: self.src_span_by_id(self.state.src_tok_span),
3628                });
3629                false
3630            }
3631        }
3632    }
3633
3634    pub fn snapshot_format(&self) -> PortableFormatImage {
3635        PortableFormatImage::from_engine_state(&self.state, &self.heap)
3636    }
3637
3638    /// Seals this engine's state in place as a format image, avoiding the copy [`Self::snapshot_format`] makes.
3639    #[must_use]
3640    pub fn into_format(self) -> PortableFormatImage {
3641        // Packing the trie forbids later `\patterns`, callers with a final format call `finalize_trie` first.
3642        let mut state = self.state;
3643        let mut heap = self.heap;
3644        seal_as_format_snapshot(&mut state, &mut heap);
3645        PortableFormatImage { state, heap }
3646    }
3647
3648    /// Packs the hyphenation trie as `\dump` would, false when it overflows, only once no more `\patterns` follow.
3649    #[must_use]
3650    pub fn finalize_trie(self: &mut Self) -> bool {
3651        let saved = self.format_initialization;
3652        self.format_initialization = false;
3653        let packed = self.run_initialized(|engine| {
3654            if engine.state.trienotready != 0 && !engine.state.triehash.is_null() {
3655                unsafe { engine.inittrie() }?;
3656            }
3657            Ok(())
3658        });
3659        self.format_initialization = saved;
3660        packed
3661    }
3662
3663    pub fn resource_request_count(&self) -> usize {
3664        self.resource_requests
3665    }
3666
3667    pub fn resource_request_records(&self) -> &[PortableResourceRequestRecord] {
3668        self.resource_request_records.as_slice()
3669    }
3670
3671    pub fn transcript_bytes(&self) -> &[u8] {
3672        self.transcript_bytes.as_slice()
3673    }
3674
3675    /// The first interned span of the primary input, if any.
3676    pub fn primary_input_source_span(&self) -> Option<PortableSourceSpan> {
3677        if !self.state.source_tracking {
3678            return None;
3679        }
3680        let primary = self.state.src_primary_name;
3681        let raw = self
3682            .heap
3683            .src_spans
3684            .iter()
3685            .find(|raw| raw.name == primary)?;
3686        let name = self.pool_string(raw.name)?;
3687        Some(PortableSourceSpan {
3688            name,
3689            start: raw.start,
3690            end: raw.end,
3691            role: raw.role,
3692        })
3693    }
3694
3695    pub fn stripped_page_build_count(&self) -> usize {
3696        self.stripped_page_builds
3697    }
3698
3699    pub fn stripped_shipout_count(&self) -> usize {
3700        self.stripped_shipouts
3701    }
3702
3703    pub fn stripped_special_output_count(&self) -> usize {
3704        self.stripped_special_outputs
3705    }
3706
3707    pub fn stripped_picture_load_count(&self) -> usize {
3708        self.stripped_picture_loads
3709    }
3710
3711    pub fn stripped_source_special_count(&self) -> usize {
3712        self.stripped_source_specials
3713    }
3714
3715    pub fn stripped_write_whatsit_diagnostic_count(&self) -> usize {
3716        self.stripped_write_whatsit_diagnostics
3717    }
3718
3719    pub fn stripped_pdf_extension_count(&self) -> usize {
3720        self.stripped_pdf_extensions
3721    }
3722
3723    pub fn stripped_page_top_prune_count(&self) -> usize {
3724        self.stripped_page_top_prunes
3725    }
3726
3727    pub fn last_stripped_shipout_box(&self) -> Option<PortableNodeHandle> {
3728        self.last_stripped_shipout_box
3729    }
3730
3731    pub fn captured_fragment_root(&self) -> Option<PortableNodeHandle> {
3732        self.captured_fragment_root
3733    }
3734
3735    /// Host box render payload by record id, as referenced by `HostBoxRef` marker nodes.
3736    pub fn host_box_record(&self, index: usize) -> Option<&PortableHostBox> {
3737        self.hostbox_records.get(&integer::try_from(index).ok()?)
3738    }
3739
3740    /// Size in scaled points font number `font` was loaded at.
3741    pub fn font_at_size(&self, font: integer) -> integer {
3742        if font < 0 {
3743            return 0;
3744        }
3745        self.heap
3746            .fontsize_storage
3747            .get(font as usize)
3748            .copied()
3749            .unwrap_or(0)
3750    }
3751
3752    /// The `\font` name of a font number, the XeTeX spec of a native font or the TFM name.
3753    pub fn font_name(&self, font: integer) -> Option<String> {
3754        if font < 0 {
3755            return None;
3756        }
3757        let name = self.heap.fontname_storage.get(font as usize).copied()?;
3758        self.pool_string(name)
3759    }
3760
3761    /// The spec a native font number was loaded with, from the font platform.
3762    pub fn native_font_spec(&self, font: integer) -> Option<String> {
3763        let handle = Self::font_handle_for_number(self, font)?;
3764        self.fonts.font_spec(handle)
3765    }
3766
3767    /// The host font key of a native font number, from the font platform.
3768    pub fn native_font_key(&self, font: integer) -> Option<u64> {
3769        let handle = Self::font_handle_for_number(self, font)?;
3770        self.fonts.font_key(handle)
3771    }
3772
3773    /// The attached font platform's native font table, packaged with a serialized format image.
3774    pub fn native_font_table(&self) -> Vec<(PortableFontHandle, String, i32)> {
3775        self.fonts.font_table()
3776    }
3777
3778    /// Rebinds a loaded image's native fonts from a [`Self::native_font_table`] snapshot, false when one fails.
3779    pub fn restore_native_font_table(
3780        self: &mut Self,
3781        table: &[(PortableFontHandle, String, i32)],
3782    ) -> bool {
3783        self.fonts.restore_font_table(table)
3784    }
3785
3786    pub fn last_abort_status(&self) -> Option<integer> {
3787        self.last_abort_status
3788    }
3789
3790    /// The message of the last run's surfaced TeX error, `None` after a clean run or a bare abort.
3791    pub fn last_error_message(&self) -> Option<&str> {
3792        self.last_error.as_ref().map(|error| error.message.as_str())
3793    }
3794
3795    /// The last run's surfaced error with its kind and input position, `None` after a clean run or a bare abort.
3796    pub fn last_error(&self) -> Option<&PortableError> {
3797        self.last_error.as_ref()
3798    }
3799
3800    /// Switches the fragment sandbox and resets its per run bookkeeping.
3801    pub fn set_sandbox(self: &mut Self, on: bool) {
3802        self.sandbox = on;
3803        self.sandbox_math_depth = 0;
3804        self.sandbox_math_opened = false;
3805        self.sandbox_ops = 0;
3806    }
3807
3808    /// Sets the sandbox work budget, counted in commands, macro calls and expansions per run.
3809    pub fn set_sandbox_op_budget(self: &mut Self, budget: u64) {
3810        self.sandbox_op_budget = budget;
3811    }
3812
3813    /// Marks where the host wrapper's suffix starts in the primary input, as a byte offset.
3814    pub fn set_sandbox_wrapper_suffix(self: &mut Self, start: Option<u32>) {
3815        self.sandbox_suffix_start = start;
3816    }
3817
3818    /// Reads one node's fields, `None` for a handle outside node memory.
3819    pub fn snapshot_node(&self, handle: PortableNodeHandle) -> Option<PortableNodeSnapshot> {
3820        let node = handle.0 as halfword;
3821        let word = self.node_word(node, 0)?;
3822        let raw_kind = unsafe { word.hh.u.B0 as i32 };
3823        let subtype = unsafe { word.hh.u.B1 as i32 };
3824        let kind = self.node_kind(raw_kind, node, subtype);
3825        let mut snapshot = PortableNodeSnapshot {
3826            handle,
3827            kind,
3828            subtype,
3829            source: self.resolve_node_src(node),
3830            link: self.node_link(node),
3831            font: 0,
3832            character: 0,
3833            width: 0,
3834            height: 0,
3835            depth: 0,
3836            shift: 0,
3837            list: None,
3838            leader: None,
3839            native_glyphs: Vec::new(),
3840            glue_set: 0.0,
3841            glue_sign: 0,
3842            glue_order: 0,
3843            glue_stretch: 0,
3844            glue_shrink: 0,
3845            glue_stretch_order: 0,
3846            glue_shrink_order: 0,
3847        };
3848        let scaled_at = |offset: halfword| self.node_scaled(node, offset).unwrap_or_default();
3849        // Byte `B0` of a two halves word is the type field, `B1` the subtype field.
3850        let quarters_at = |at: halfword, offset: halfword| {
3851            self.node_word(at, offset)
3852                .map_or((0, 0), |word| unsafe { (word.hh.u.B0 as i32, word.hh.u.B1 as i32) })
3853        };
3854        match kind {
3855            PortableNodeKind::Character => {
3856                snapshot.font = raw_kind;
3857                snapshot.character = subtype;
3858                (snapshot.width, snapshot.height, snapshot.depth) =
3859                    self.character_metrics(raw_kind, subtype).unwrap_or_default();
3860            }
3861            PortableNodeKind::HorizontalBox | PortableNodeKind::VerticalBox => {
3862                snapshot.width = scaled_at(1);
3863                snapshot.depth = scaled_at(2);
3864                snapshot.height = scaled_at(3);
3865                snapshot.shift = scaled_at(4);
3866                snapshot.list = self.node_field_link(node, 5);
3867                (snapshot.glue_sign, snapshot.glue_order) = quarters_at(node, 5);
3868                snapshot.glue_set = self.node_word(node, 6).map_or(0.0, |word| unsafe { word.gr });
3869            }
3870            PortableNodeKind::UnsetBox => {
3871                // An unset box keeps its total stretch in the glue set word and its shrink in the shift word.
3872                snapshot.width = scaled_at(1);
3873                snapshot.depth = scaled_at(2);
3874                snapshot.height = scaled_at(3);
3875                snapshot.glue_shrink = scaled_at(4);
3876                snapshot.list = self.node_field_link(node, 5);
3877                (snapshot.glue_shrink_order, snapshot.glue_stretch_order) = quarters_at(node, 5);
3878                snapshot.glue_stretch = scaled_at(6);
3879            }
3880            PortableNodeKind::Rule => {
3881                snapshot.width = scaled_at(1);
3882                snapshot.depth = scaled_at(2);
3883                snapshot.height = scaled_at(3);
3884            }
3885            PortableNodeKind::Glue => {
3886                let fields = self.node_word(node, 1);
3887                snapshot.leader = fields.and_then(|word| Self::node_handle_from_raw(unsafe { word.hh.v.RH }));
3888                if let Some(spec) = fields.map(|word| unsafe { word.hh.v.LH }) {
3889                    snapshot.width = self.node_scaled(spec, 1).unwrap_or_default();
3890                    snapshot.glue_stretch = self.node_scaled(spec, 2).unwrap_or_default();
3891                    snapshot.glue_shrink = self.node_scaled(spec, 3).unwrap_or_default();
3892                    (snapshot.glue_stretch_order, snapshot.glue_shrink_order) = quarters_at(spec, 0);
3893                }
3894            }
3895            PortableNodeKind::Kern | PortableNodeKind::Math => snapshot.width = scaled_at(1),
3896            PortableNodeKind::Ligature => {
3897                // The ligature character word sits after the node header and links to the original characters.
3898                (snapshot.font, snapshot.character) = quarters_at(node, 1);
3899                (snapshot.width, snapshot.height, snapshot.depth) = self
3900                    .character_metrics(snapshot.font, snapshot.character)
3901                    .unwrap_or_default();
3902                snapshot.list = self.node_field_link(node, 1);
3903            }
3904            PortableNodeKind::NativeWord | PortableNodeKind::NativeGlyph => {
3905                let native = self.node_word(node, 4);
3906                snapshot.font = native.map_or(0, |word| unsafe { word.v.QQQQ.u.B1 as i32 });
3907                snapshot.character = native.map_or(0, |word| unsafe { word.v.QQQQ.u.B2 as i32 });
3908                snapshot.width = scaled_at(1);
3909                snapshot.depth = scaled_at(2);
3910                snapshot.height = scaled_at(3);
3911                snapshot.native_glyphs = self
3912                    .native_glyph_infos
3913                    .get(&node)
3914                    .map(|info| info.glyphs.clone())
3915                    .unwrap_or_default();
3916            }
3917            PortableNodeKind::HostBoxRef => {
3918                // Host box markers carry their record id in the payload word after the metrics.
3919                snapshot.character = scaled_at(4);
3920                snapshot.width = scaled_at(1);
3921                snapshot.depth = scaled_at(2);
3922                snapshot.height = scaled_at(3);
3923            }
3924            _ => {}
3925        }
3926        Some(snapshot)
3927    }
3928
3929    fn node_kind(&self, raw_kind: i32, node: halfword, subtype: i32) -> PortableNodeKind {
3930        if node >= self.state.himemmin {
3931            return PortableNodeKind::Character;
3932        }
3933        match raw_kind {
3934            HLIST_NODE => PortableNodeKind::HorizontalBox,
3935            VLIST_NODE => PortableNodeKind::VerticalBox,
3936            RULE_NODE => PortableNodeKind::Rule,
3937            INS_NODE => PortableNodeKind::Insertion,
3938            MARK_NODE => PortableNodeKind::Mark,
3939            ADJUST_NODE => PortableNodeKind::Adjustment,
3940            LIGATURE_NODE => PortableNodeKind::Ligature,
3941            DISC_NODE => PortableNodeKind::Discretionary,
3942            WHATSIT_NODE => match subtype {
3943                NATIVE_WORD_NODE | NATIVE_WORD_NODE_AT => PortableNodeKind::NativeWord,
3944                GLYPH_NODE => PortableNodeKind::NativeGlyph,
3945                HOST_BOX_RESOLVED_SUBTYPE => PortableNodeKind::HostBoxRef,
3946                0..=3 => PortableNodeKind::OutputWhatsit,
3947                _ => PortableNodeKind::Whatsit,
3948            },
3949            MATH_NODE => PortableNodeKind::Math,
3950            GLUE_NODE => PortableNodeKind::Glue,
3951            KERN_NODE => PortableNodeKind::Kern,
3952            PENALTY_NODE => PortableNodeKind::Penalty,
3953            UNSET_NODE => PortableNodeKind::UnsetBox,
3954            STYLE_NODE => PortableNodeKind::Style,
3955            CHOICE_NODE => PortableNodeKind::Choice,
3956            ORD_NOAD..=RIGHT_NOAD => PortableNodeKind::Noad(NOAD_KINDS[(raw_kind - ORD_NOAD) as usize]),
3957            other => PortableNodeKind::Unknown(other),
3958        }
3959    }
3960
3961    /// Copies the side table glyphs of node `src` to node `dest`, returning the glyph count.
3962    pub(crate) fn copy_native_glyph_info(
3963        this: &mut Self,
3964        src: halfword,
3965        dest: halfword,
3966    ) -> quarterword {
3967        if let Some(info) = this.native_glyph_infos.get(&src).cloned() {
3968            let glyph_count = info.glyphs.len().min(i32::MAX as usize) as quarterword;
3969            this.native_glyph_infos.insert(dest, info);
3970            glyph_count
3971        } else {
3972            this.native_glyph_infos.remove(&dest);
3973            0
3974        }
3975    }
3976
3977    /// Drops the side table glyphs of a node being freed.
3978    pub(crate) fn release_native_glyph_info(this: &mut Self, node: halfword) {
3979        if !this.native_glyph_infos.is_empty() {
3980            this.native_glyph_infos.remove(&node);
3981        }
3982    }
3983
3984    fn font_handle_for_number(this: &Self, font: integer) -> Option<FontHandle> {
3985        if font < 0 || font > this.state.fontptr {
3986            return None;
3987        }
3988        let handle = *this.heap.fontlayoutengine_storage.get(font as usize)? as FontHandle;
3989        (handle != 0).then_some(handle)
3990    }
3991
3992    unsafe fn node_index_for_pointer(
3993        engine: &PortableTexEngine<'_>,
3994        node: voidpointer,
3995    ) -> Option<halfword> {
3996        if node.is_null() || engine.state.zmem.is_null() {
3997            return None;
3998        }
3999        let base = engine.state.zmem as isize;
4000        let address = node as isize;
4001        let word_size = core::mem::size_of::<memoryword>() as isize;
4002        if word_size == 0 || address < base {
4003            return None;
4004        }
4005        let bytes = address - base;
4006        if bytes % word_size != 0 {
4007            return None;
4008        }
4009        let index = (bytes / word_size) as halfword;
4010        if index < engine.state.memmin || index > engine.state.memmax {
4011            None
4012        } else {
4013            Some(index)
4014        }
4015    }
4016
4017    unsafe fn native_node_font(mem: *mut memoryword, node: halfword) -> integer {
4018        (*mem.offset((node + 4) as isize)).v.QQQQ.u.B1 as integer
4019    }
4020
4021    unsafe fn native_node_text<'a>(mem: *mut memoryword, node: halfword) -> &'a [u16] {
4022        let len = (*mem.offset((node + 4) as isize)).v.QQQQ.u.B2 as usize;
4023        if len == 0 {
4024            return &[];
4025        }
4026        core::slice::from_raw_parts(
4027            mem.offset((node + native_node_size) as isize) as *const memoryword as *const u16,
4028            len,
4029        )
4030    }
4031
4032    unsafe fn write_native_node_metrics(
4033        mem: *mut memoryword,
4034        node: halfword,
4035        width: i32,
4036        height: i32,
4037        depth: i32,
4038    ) {
4039        (*mem.offset((node + 1) as isize)).u.CINT = width;
4040        (*mem.offset((node + 2) as isize)).u.CINT = depth;
4041        (*mem.offset((node + 3) as isize)).u.CINT = height;
4042    }
4043
4044    fn node_word(&self, node: halfword, offset: halfword) -> Option<memoryword> {
4045        if node == TEX_NULL {
4046            return None;
4047        }
4048        let index = node.checked_add(offset)?;
4049        if index > self.state.memmax {
4050            return None;
4051        }
4052        let slot = usize::try_from(index.checked_sub(self.state.memmin)?).ok()?;
4053        self.heap.mem.get(slot).copied()
4054    }
4055
4056    fn node_link(&self, node: halfword) -> Option<PortableNodeHandle> {
4057        let word = self.node_word(node, 0)?;
4058        let link = unsafe { word.hh.v.RH };
4059        Self::node_handle_from_raw(link)
4060    }
4061
4062    fn node_field_link(&self, node: halfword, offset: halfword) -> Option<PortableNodeHandle> {
4063        let word = self.node_word(node, offset)?;
4064        let link = unsafe { word.hh.v.RH };
4065        Self::node_handle_from_raw(link)
4066    }
4067
4068    fn node_scaled(&self, node: halfword, offset: halfword) -> Option<i32> {
4069        let word = self.node_word(node, offset)?;
4070        Some(unsafe { word.u.CINT })
4071    }
4072
4073    // Bounds checked, a stale or foreign handle can put any high memory word here.
4074    fn character_metrics(&self, font: i32, character: i32) -> Option<(i32, i32, i32)> {
4075        if font < 0 || character < 0 || font > self.state.fontptr {
4076            return None;
4077        }
4078        let base = |storage: &[integer]| storage.get(font as usize).copied();
4079        let char_base = base(&self.heap.charbase_storage)?;
4080        let fontinfo = |index: i32| self.heap.fontinfo_storage.get(usize::try_from(index).ok()?).copied();
4081        let scaled_at = |table: i32, index: i32| {
4082            fontinfo(table.checked_add(index)?).map(|word| unsafe { word.u.CINT })
4083        };
4084        let char_info = unsafe { fontinfo(char_base.checked_add(character)?)?.v.QQQQ };
4085        // The height_depth byte packs the height index in its high nibble, as tex.web's char_height.
4086        let height_depth = i32::from(char_info.u.B1);
4087        Some((
4088            scaled_at(base(&self.heap.widthbase_storage)?, i32::from(char_info.u.B0))?,
4089            scaled_at(base(&self.heap.heightbase_storage)?, height_depth / 16)?,
4090            scaled_at(base(&self.heap.depthbase_storage)?, height_depth % 16)?,
4091        ))
4092    }
4093
4094    fn node_handle_from_raw(raw: halfword) -> Option<PortableNodeHandle> {
4095        if raw == TEX_NULL {
4096            None
4097        } else {
4098            Some(PortableNodeHandle(raw))
4099        }
4100    }
4101
4102    unsafe fn current_resource_name(engine: *mut PortableTexEngine<'_>) -> Option<String> {
4103        let engine = engine.as_ref()?;
4104        if engine.state.nameoffile.is_null() || engine.state.namelength <= 0 {
4105            return None;
4106        }
4107
4108        let mut bytes = Vec::with_capacity(engine.state.namelength as usize);
4109        for index in 1..=engine.state.namelength {
4110            let value = *engine.state.nameoffile.offset(index as isize);
4111            if value <= 0 {
4112                continue;
4113            }
4114            bytes.push(value as u8);
4115        }
4116        Some(String::from_utf8_lossy(bytes.as_slice()).into_owned())
4117    }
4118
4119    /// XeTeX `getencodingmodeandinfo` for the name in `nameoffile`, without ICU unknown names read as raw bytes.
4120    pub(crate) unsafe fn get_encoding_mode_and_info(
4121        engine: *mut PortableTexEngine<'_>,
4122        info: *mut integer,
4123    ) -> integer {
4124        if !info.is_null() {
4125            *info = 0;
4126        }
4127        let name = Self::current_resource_name(engine).unwrap_or_default();
4128        let lowered = name.trim().to_ascii_lowercase();
4129        match lowered.as_str() {
4130            "auto" => 0,
4131            "utf8" | "utf-8" => 1,
4132            // XeTeX reads the host endian `utf16` as big endian.
4133            "utf16" | "utf-16" | "utf16be" | "utf-16be" => 2,
4134            "utf16le" | "utf-16le" => 3,
4135            // Without ICU, any other encoding name reads raw bytes.
4136            _ => 4,
4137        }
4138    }
4139
4140    unsafe fn mode_string(mode: const_string) -> String {
4141        if mode.is_null() {
4142            return String::new();
4143        }
4144
4145        let mut bytes = Vec::new();
4146        let mut cursor = mode;
4147        while *cursor != 0 {
4148            bytes.push(*cursor as u8);
4149            cursor = cursor.add(1);
4150        }
4151        String::from_utf8_lossy(bytes.as_slice()).into_owned()
4152    }
4153
4154    // Bounds checked against the pool storage, callers can pass any string number.
4155    fn pool_string(&self, string: strnumber) -> Option<String> {
4156        let index = usize::try_from(Self::pool_string_index(string)?).ok()?;
4157        let start = usize::try_from(*self.heap.strstart_storage.get(index)?).ok()?;
4158        let end = usize::try_from(*self.heap.strstart_storage.get(index.checked_add(1)?)?).ok()?;
4159        let units = self.heap.strpool_storage.get(start..end)?;
4160        Some(
4161            char::decode_utf16(units.iter().copied())
4162                .map(|codepoint| codepoint.unwrap_or(char::REPLACEMENT_CHARACTER))
4163                .collect(),
4164        )
4165    }
4166
4167    pub(crate) fn pool_string_index(string: strnumber) -> Option<isize> {
4168        if string < 0 {
4169            return None;
4170        }
4171        let index = if string >= TOO_BIG_CHAR { string - TOO_BIG_CHAR } else { string };
4172        isize::try_from(index).ok()
4173    }
4174
4175    fn resource_kind(name: &str, format: integer) -> ResourceKind {
4176        match format {
4177            resource_format_tex_input | 0 => Self::tex_resource_kind(name),
4178            resource_format_tfm | resource_format_font => ResourceKind::Font,
4179            resource_format_encoding => ResourceKind::Encoding,
4180            resource_format_font_map => ResourceKind::Map,
4181            resource_format_config => ResourceKind::Config,
4182            resource_format_format_image => ResourceKind::FormatImage,
4183            other => ResourceKind::Other(other),
4184        }
4185    }
4186
4187    fn tex_resource_kind(name: &str) -> ResourceKind {
4188        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4189        let name = name.to_ascii_lowercase();
4190        if name.ends_with(".sty") {
4191            return ResourceKind::Package;
4192        }
4193        if name.ends_with(".cls") {
4194            return ResourceKind::Class;
4195        }
4196        if name.ends_with(".fd") {
4197            return ResourceKind::FontDefinition;
4198        }
4199        if name.ends_with(".clo")
4200            || name.ends_with(".def")
4201            || name.ends_with(".ldf")
4202            || name.ends_with(".cfg")
4203        {
4204            return ResourceKind::PackageSupport;
4205        }
4206        ResourceKind::TexInput
4207    }
4208
4209    fn resource_kind_for_open(
4210        engine: &PortableTexEngine<'_>,
4211        name: &str,
4212        format: integer,
4213    ) -> ResourceKind {
4214        let kind = Self::resource_kind(name, format);
4215        if kind == ResourceKind::TexInput
4216            && Self::active_package_owner(engine).is_some()
4217            && Self::looks_like_package_asset(name)
4218        {
4219            ResourceKind::Asset
4220        } else {
4221            kind
4222        }
4223    }
4224
4225    fn resource_package_owner(
4226        engine: &PortableTexEngine<'_>,
4227        name: &str,
4228        kind: ResourceKind,
4229    ) -> Option<String> {
4230        match kind {
4231            ResourceKind::Package | ResourceKind::Class => Self::resource_stem(name),
4232            ResourceKind::PackageSupport | ResourceKind::FontDefinition | ResourceKind::Asset => {
4233                Self::active_package_owner(engine)
4234            }
4235            _ => None,
4236        }
4237    }
4238
4239    fn active_package_owner(engine: &PortableTexEngine<'_>) -> Option<String> {
4240        engine.current_input_package_owner.clone()
4241    }
4242
4243    fn looks_like_package_asset(name: &str) -> bool {
4244        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4245        let name = name.to_ascii_lowercase();
4246        !(name.ends_with(".tex")
4247            || name.ends_with(".ltx")
4248            || name.ends_with(".sty")
4249            || name.ends_with(".cls")
4250            || name.ends_with(".fd")
4251            || name.ends_with(".clo")
4252            || name.ends_with(".def")
4253            || name.ends_with(".ldf")
4254            || name.ends_with(".cfg"))
4255    }
4256
4257    fn resource_stem(name: &str) -> Option<String> {
4258        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4259        let stem = name.rsplit_once('.').map_or(name, |(stem, _)| stem);
4260        if stem.is_empty() {
4261            None
4262        } else {
4263            Some(stem.to_string())
4264        }
4265    }
4266
4267    fn source_index(value: usize) -> u32 {
4268        value.min(u32::MAX as usize) as u32
4269    }
4270
4271    fn virtual_file_key(name: &str) -> String {
4272        let mut name = name;
4273        while let Some(stripped) = name.strip_prefix("./") {
4274            name = stripped;
4275        }
4276        name.to_string()
4277    }
4278
4279    fn normalized_runtime_resource_name(mut name: &str) -> &str {
4280        while let Some(stripped) = name.strip_prefix("./") {
4281            name = stripped;
4282        }
4283        name
4284    }
4285
4286    // Hand written `EngineFlow` bridge, `Ok(None)` means nothing was interned and the flow pass leaves it alone.
4287    unsafe fn intern_static_pool_string(
4288        engine: &mut PortableTexEngine<'_>,
4289        text: &str,
4290    ) -> EngineFlow<Option<strnumber>> {
4291        if engine.state.strpool.is_null() || engine.state.strstart.is_null() {
4292            return Ok(None);
4293        }
4294
4295        let Ok(needed) = integer::try_from(text.encode_utf16().count()) else {
4296            return Ok(None);
4297        };
4298        let Some(next_pool) = engine.state.poolptr.checked_add(needed) else {
4299            return Ok(None);
4300        };
4301        if next_pool > engine.state.poolsize {
4302            return Ok(None);
4303        }
4304
4305        for unit in text.encode_utf16() {
4306            *engine.state.strpool.offset(engine.state.poolptr as isize) = unit as packedUTF16code;
4307            engine.state.poolptr += 1;
4308        }
4309
4310        Ok(Some(engine.makestring()?))
4311    }
4312
4313    unsafe fn append_text_to_pool(engine: &mut PortableTexEngine<'_>, text: &str) -> boolean {
4314        if engine.state.strpool.is_null() {
4315            return false_0;
4316        }
4317        let needed = match integer::try_from(text.encode_utf16().count()) {
4318            Ok(needed) => needed,
4319            Err(_) => return false_0,
4320        };
4321        let Some(next_pool) = engine.state.poolptr.checked_add(needed) else {
4322            return false_0;
4323        };
4324        if next_pool > engine.state.poolsize {
4325            return false_0;
4326        }
4327        for unit in text.encode_utf16() {
4328            *engine.state.strpool.offset(engine.state.poolptr as isize) = unit as packedUTF16code;
4329            engine.state.poolptr += 1;
4330        }
4331        true_0
4332    }
4333
4334    /// `\filesize` of a TeX input: the provider's byte length, or nothing for a missing file as in pdfTeX.
4335    pub(crate) unsafe fn boundary_get_file_size(
4336        engine: *mut PortableTexEngine<'_>,
4337        string: integer,
4338    ) {
4339        let Some(engine) = engine.as_mut() else {
4340            return;
4341        };
4342        let Some(name) = engine.pool_string(string as strnumber) else {
4343            return;
4344        };
4345        let name = Self::normalized_runtime_resource_name(name.as_str()).to_string();
4346        let kind = Self::resource_kind_for_open(engine, &name, resource_format_tex_input);
4347        let package = Self::resource_package_owner(engine, &name, kind);
4348        let size = match engine.virtual_files.get(&Self::virtual_file_key(&name)) {
4349            Some(bytes) => Some(bytes.len()),
4350            None => engine
4351                .resources
4352                .read(ResourceRequest {
4353                    name: &name,
4354                    kind,
4355                    package: package.as_deref(),
4356                    format: resource_format_tex_input,
4357                    mode: FILE_SIZE_PROBE_MODE,
4358                    source: None,
4359                })
4360                .map(|bytes| bytes.len()),
4361        };
4362        engine.resource_requests = engine.resource_requests.saturating_add(1);
4363        engine.resource_request_records.push(PortableResourceRequestRecord {
4364            name,
4365            kind,
4366            package,
4367            format: resource_format_tex_input,
4368            mode: FILE_SIZE_PROBE_MODE.to_string(),
4369            source: None,
4370            byte_len: size.map(Self::source_index),
4371        });
4372        // An empty expansion is how expl3 and `\IfFileExists` learn a file is missing.
4373        if let Some(size) = size {
4374            Self::append_text_to_pool(engine, size.to_string().as_str());
4375        }
4376    }
4377
4378    pub(crate) unsafe fn load_pool_strings(
4379        engine: &mut PortableTexEngine<'_>,
4380        spare_size: integer,
4381    ) -> EngineFlow<integer> {
4382        if engine.state.strpool.is_null() || engine.state.strstart.is_null()
4383            || spare_size <= 0
4384        {
4385            return Ok(0);
4386        }
4387        let mut used = 0_i32;
4388        let mut last = 0_i32;
4389        for line in include_str!("../pool/xetex.pool").lines() {
4390            if line.starts_with('*') {
4391                break;
4392            }
4393            let bytes = line.as_bytes();
4394            let text = if bytes.len() >= 2 && bytes[0].is_ascii_digit()
4395                && bytes[1].is_ascii_digit()
4396            {
4397                &line[2..]
4398            } else {
4399                line
4400            };
4401            let units = text.encode_utf16().count().min(i32::MAX as usize) as integer;
4402            used = used.saturating_add(units);
4403            if used >= spare_size
4404                || engine.state.poolptr.saturating_add(units) > engine.state.poolsize
4405            {
4406                return Ok(0);
4407            }
4408            for unit in text.encode_utf16() {
4409                *engine.state.strpool.offset(engine.state.poolptr as isize) = unit
4410                    as packedUTF16code;
4411                engine.state.poolptr += 1;
4412            }
4413            last = engine.makestring()?;
4414        }
4415        Ok(last)
4416    }
4417
4418    pub(crate) unsafe fn boundary_open_log_file(
4419        engine: *mut PortableTexEngine<'_>,
4420    ) -> EngineFlow<()> {
4421        let Some(engine) = engine.as_mut() else {
4422            return Ok(());
4423        };
4424        let old_setting = engine.state.selector;
4425        if engine.state.jobname == 0 {
4426            if let Some(jobname) = Self::intern_static_pool_string(engine, "texput")? {
4427                engine.state.jobname = jobname;
4428            }
4429        }
4430        engine.state.logopened = true_0 as boolean;
4431        engine.state.selector = (old_setting as i32 + 2).clamp(0, 21) as eightbits;
4432        Ok(())
4433    }
4434
4435    pub(crate) unsafe fn boundary_jump_out(
4436        engine: *mut PortableTexEngine<'_>,
4437    ) -> EngineFlow<core::convert::Infallible> {
4438        // A history of at most warnings is a normal end with status 0, anything worse aborts with status 1.
4439        let status = if let Some(engine) = engine.as_ref() {
4440            if engine.state.history as i32 <= 1 {
4441                0 as integer
4442            } else {
4443                1 as integer
4444            }
4445        } else {
4446            1 as integer
4447        };
4448        Self::abort_engine(engine, status)
4449    }
4450
4451    pub(crate) unsafe fn boundary_shipout(
4452        engine: *mut PortableTexEngine<'_>,
4453        box_node: halfword,
4454    ) {
4455        if let Some(engine) = engine.as_mut() {
4456            engine.stripped_shipouts = engine.stripped_shipouts.saturating_add(1);
4457            engine.last_stripped_shipout_box = Some(PortableNodeHandle(box_node));
4458        }
4459    }
4460
4461    /// `box_end` hook: takes the first box appended to the outer vertical list as the fragment root.
4462    pub(crate) unsafe fn boundary_capture_fragment_box(
4463        engine: *mut PortableTexEngine<'_>,
4464        box_node: halfword,
4465        mode: integer,
4466        boxcontext: integer,
4467    ) -> boolean {
4468        let Some(engine) = engine.as_mut() else {
4469            return false_0;
4470        };
4471        // Boxes inside `\vbox`, `\vtop` and `\vcenter` are in internal vertical mode, which is negative.
4472        if !engine.fragment_capture_enabled
4473            || engine.captured_fragment_root.is_some()
4474            || mode != VMODE
4475            || boxcontext >= BOX_FLAG
4476        {
4477            return false_0;
4478        }
4479        engine.captured_fragment_root = Some(PortableNodeHandle(box_node));
4480        true_0
4481    }
4482
4483    /// `build_page`: the page builder is stripped, contributions stay on the vertical list.
4484    pub(crate) unsafe fn boundary_build_page(engine: *mut PortableTexEngine<'_>) {
4485        if let Some(engine) = engine.as_mut() {
4486            engine.stripped_page_builds = engine.stripped_page_builds.saturating_add(1);
4487        }
4488    }
4489
4490    /// XeTeX `prune_page_top`: frees or saves what precedes a split's first box, then adds `\splittopskip` glue.
4491    pub(crate) unsafe fn boundary_prune_page_top(
4492        engine: *mut PortableTexEngine<'_>,
4493        mut p: halfword,
4494        saving: boolean,
4495    ) -> EngineFlow<halfword> {
4496        let Some(this) = engine.as_mut() else {
4497            return Ok(p);
4498        };
4499        this.stripped_page_top_prunes = this.stripped_page_top_prunes.saturating_add(1);
4500        let mem = this.state.zmem;
4501        let temp_head = this.state.memtop - 3;
4502        let mut prev_p = temp_head;
4503        (*mem.offset(temp_head as isize)).hh.v.RH = p;
4504        // Last node moved to the split discards, `r` in xetex.web.
4505        let mut last_discard = TEX_NULL;
4506        while p != TEX_NULL {
4507            match (*mem.offset(p as isize)).hh.u.B0 as i32 {
4508                HLIST_NODE | VLIST_NODE | RULE_NODE => {
4509                    let q = (*engine).znewskipparam(SPLIT_TOP_SKIP_CODE)?;
4510                    let this = &mut *engine;
4511                    (*mem.offset(prev_p as isize)).hh.v.RH = q;
4512                    (*mem.offset(q as isize)).hh.v.RH = p;
4513                    // `new_skip_param` leaves the fresh glue spec in `temp_ptr`.
4514                    let spec = this.state.tempptr;
4515                    let width = (*mem.offset((spec + 1) as isize)).u.CINT;
4516                    let height = (*mem.offset((p + 3) as isize)).u.CINT;
4517                    (*mem.offset((spec + 1) as isize)).u.CINT =
4518                        if width > height { width - height } else { 0 };
4519                    p = TEX_NULL;
4520                }
4521                WHATSIT_NODE | MARK_NODE | INS_NODE => {
4522                    prev_p = p;
4523                    p = (*mem.offset(prev_p as isize)).hh.v.RH;
4524                }
4525                GLUE_NODE | KERN_NODE | PENALTY_NODE => {
4526                    let q = p;
4527                    p = (*mem.offset(q as isize)).hh.v.RH;
4528                    (*mem.offset(q as isize)).hh.v.RH = TEX_NULL;
4529                    (*mem.offset(prev_p as isize)).hh.v.RH = p;
4530                    if saving != false_0 {
4531                        let this = &mut *engine;
4532                        if this.state.discptr[VSPLIT_CODE] == TEX_NULL {
4533                            this.state.discptr[VSPLIT_CODE] = q;
4534                        } else {
4535                            (*mem.offset(last_discard as isize)).hh.v.RH = q;
4536                        }
4537                        last_discard = q;
4538                    } else {
4539                        (*engine).zflushnodelist(q)?;
4540                    }
4541                }
4542                _ => match (*engine).zconfusion(POOL_PRUNING)? {},
4543            }
4544        }
4545        Ok((*mem.offset(temp_head as isize)).hh.v.RH)
4546    }
4547
4548    pub(crate) unsafe fn boundary_special_out(
4549        engine: *mut PortableTexEngine<'_>,
4550        node: halfword,
4551    ) -> EngineFlow<()> {
4552        let Some(engine) = engine.as_mut() else {
4553            return Ok(());
4554        };
4555        if node < engine.state.memmin || node > engine.state.memend {
4556            engine.stripped_special_outputs = engine
4557                .stripped_special_outputs
4558                .saturating_add(1);
4559            return Ok(());
4560        }
4561        let mem = engine.state.zmem.as_mut_ptr();
4562        if (*mem.offset(node as isize)).hh.u.B0 as i32 == WHATSIT_NODE {
4563            match (*mem.offset(node as isize)).hh.u.B1 as i32 {
4564                OPEN_NODE => {
4565                    Self::boundary_open_write_whatsit(engine, node);
4566                    return Ok(());
4567                }
4568                WRITE_NODE => {
4569                    Self::boundary_write_whatsit(engine, node)?;
4570                    return Ok(());
4571                }
4572                CLOSE_NODE => {
4573                    Self::boundary_close_write_whatsit(engine, node);
4574                    return Ok(());
4575                }
4576                _ => {}
4577            }
4578        }
4579        engine.stripped_special_outputs = engine
4580            .stripped_special_outputs
4581            .saturating_add(1);
4582        Ok(())
4583    }
4584
4585    unsafe fn boundary_open_write_whatsit(engine: &mut PortableTexEngine<'_>, node: halfword) {
4586        let mem = engine.state.zmem.as_mut_ptr();
4587        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4588        if stream >= STREAM_COUNT {
4589            return;
4590        }
4591
4592        if engine.state.writeopen[stream] != 0 {
4593            Self::boundary_close_write_stream(engine, stream);
4594        }
4595
4596        engine.state.curname = (*mem.offset((node + 1) as isize)).hh.v.RH as strnumber;
4597        engine.state.curarea = (*mem.offset((node + 2) as isize)).hh.v.LH as strnumber;
4598        engine.state.curext = (*mem.offset((node + 2) as isize)).hh.v.RH as strnumber;
4599        if engine.state.curext == POOL_EMPTY_STRING {
4600            engine.state.curext = POOL_TEX_EXTENSION;
4601        }
4602        engine.zpackfilename(engine.state.curname, engine.state.curarea, engine.state.curext);
4603        let Some(name) = Self::current_resource_name(engine as *mut PortableTexEngine<'_>) else {
4604            return;
4605        };
4606        let handle = Box::new(PortableFileHandle::new(
4607            name,
4608            None,
4609            resource_format_tex_input,
4610            Vec::new(),
4611        ));
4612        engine.state.writefile[stream] = Box::into_raw(handle);
4613        engine.state.writeopen[stream] = true_0;
4614    }
4615
4616    unsafe fn boundary_write_whatsit(
4617        engine: &mut PortableTexEngine<'_>,
4618        node: halfword,
4619    ) -> EngineFlow<()> {
4620        let mem = engine.state.zmem.as_mut_ptr();
4621        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4622        if stream >= STREAM_COUNT || engine.state.writeopen[stream] == 0 {
4623            return Ok(());
4624        }
4625        let q = engine.getavail()?;
4626        (*mem.offset(q as isize)).hh.v.LH = WRITE_RIGHT_BRACE_TOKEN;
4627        let r = engine.getavail()?;
4628        (*mem.offset(q as isize)).hh.v.RH = r;
4629        (*mem.offset(r as isize)).hh.v.LH = WRITE_END_TOKEN;
4630        engine.zbegintokenlist(q, TOKEN_TYPE_INSERTED)?;
4631        engine
4632            .zbegintokenlist(
4633                (*mem.offset((node + 1) as isize)).hh.v.RH,
4634                TOKEN_TYPE_WRITE_TEXT,
4635            )?;
4636        let q = engine.getavail()?;
4637        (*mem.offset(q as isize)).hh.v.LH = WRITE_LEFT_BRACE_TOKEN;
4638        engine.zbegintokenlist(q, TOKEN_TYPE_INSERTED)?;
4639        let old_mode = engine.state.curlist.modefield;
4640        engine.state.curlist.modefield = 0;
4641        engine.state.curcs = engine.state.writeloc;
4642        engine.zscantoks(false_0, true_0)?;
4643        engine.state.curlist.modefield = old_mode;
4644        engine.gettoken()?;
4645        if engine.state.curtok != WRITE_END_TOKEN {
4646            while engine.state.curtok != WRITE_END_TOKEN {
4647                engine.gettoken()?;
4648            }
4649        }
4650        engine.endtokenlist()?;
4651        let old_setting = engine.state.selector;
4652        engine.state.selector = stream as eightbits;
4653        engine.ztokenshow(engine.state.defref);
4654        engine.println();
4655        engine.state.selector = old_setting;
4656        engine.zflushlist(engine.state.defref);
4657        Ok(())
4658    }
4659
4660    unsafe fn boundary_close_write_whatsit(engine: &mut PortableTexEngine<'_>, node: halfword) {
4661        let mem = engine.state.zmem.as_mut_ptr();
4662        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4663        if stream < STREAM_COUNT {
4664            Self::boundary_close_write_stream(engine, stream);
4665        }
4666    }
4667
4668    unsafe fn boundary_close_write_stream(engine: &mut PortableTexEngine<'_>, stream: usize) {
4669        if stream >= STREAM_COUNT || engine.state.writeopen[stream] == 0 {
4670            return;
4671        }
4672        let file = engine.state.writefile[stream];
4673        engine.state.writefile[stream] = core::ptr::null_mut();
4674        engine.state.writeopen[stream] = false_0;
4675        if file.is_null() {
4676            return;
4677        }
4678        let handle = Box::from_raw(file);
4679        let key = Self::virtual_file_key(handle.name.as_str());
4680        engine.virtual_files.insert(key, handle.bytes);
4681    }
4682
4683    pub(crate) unsafe fn boundary_load_picture(
4684        engine: *mut PortableTexEngine<'_>,
4685        _is_pdf: boolean,
4686    ) {
4687        if let Some(engine) = engine.as_mut() {
4688            engine.stripped_picture_loads = engine.stripped_picture_loads.saturating_add(1);
4689        }
4690    }
4691
4692    pub(crate) fn record_stripped_source_special(engine: *mut PortableTexEngine<'_>) {
4693        if let Some(engine) = unsafe { engine.as_mut() } {
4694            engine.stripped_source_specials = engine.stripped_source_specials.saturating_add(1);
4695        }
4696    }
4697
4698    pub(crate) fn record_stripped_write_whatsit_diagnostic(engine: *mut PortableTexEngine<'_>) {
4699        if let Some(engine) = unsafe { engine.as_mut() } {
4700            engine.stripped_write_whatsit_diagnostics =
4701                engine.stripped_write_whatsit_diagnostics.saturating_add(1);
4702        }
4703    }
4704
4705    pub(crate) fn record_stripped_pdf_extension(engine: *mut PortableTexEngine<'_>) {
4706        if let Some(engine) = unsafe { engine.as_mut() } {
4707            engine.stripped_pdf_extensions = engine.stripped_pdf_extensions.saturating_add(1);
4708        }
4709    }
4710
4711    /// Only TeX text inputs get XeTeX's `u_open_in` encoding sniff, binary inputs such as TFM files read raw.
4712    fn resolve_input_encoding(handle: &mut PortableFileHandle) {
4713        let is_text = handle.format == resource_format_tex_input || handle.format == 0;
4714        if is_text {
4715            handle.resolve_text_encoding_auto();
4716        } else {
4717            handle.encoding = InputEncoding::Bytes;
4718        }
4719    }
4720
4721    pub(crate) unsafe fn boundary_open_input(
4722        engine: *mut PortableTexEngine<'_>,
4723        file: *mut NativeFileHandle,
4724        format: integer,
4725        mode: const_string,
4726    ) -> boolean {
4727        let Some(engine) = engine.as_mut() else {
4728            if !file.is_null() {
4729                *file = core::ptr::null_mut();
4730            }
4731            return false_0;
4732        };
4733        let Some(name) = Self::current_resource_name(engine as *mut PortableTexEngine<'_>) else {
4734            if !file.is_null() {
4735                *file = core::ptr::null_mut();
4736            }
4737            return false_0;
4738        };
4739        let mode = Self::mode_string(mode);
4740        let request_kind = Self::resource_kind_for_open(engine, name.as_str(), format);
4741        let request_package = Self::resource_package_owner(engine, name.as_str(), request_kind);
4742        let source: Option<PortableSourceSpan> = None;
4743        let request = ResourceRequest {
4744            name: name.as_str(),
4745            kind: request_kind,
4746            package: request_package.as_deref(),
4747            format,
4748            mode: mode.as_str(),
4749            source: source.clone(),
4750        };
4751        engine.resource_requests = engine.resource_requests.saturating_add(1);
4752        let virtual_key = Self::virtual_file_key(name.as_str());
4753        let bytes = if let Some(bytes) = engine.virtual_files.get(&virtual_key) {
4754            bytes.clone()
4755        } else if let Some(bytes) = engine.resources.read(request) {
4756            bytes
4757        } else {
4758            engine.resource_request_records.push(PortableResourceRequestRecord {
4759                name,
4760                kind: request_kind,
4761                package: request_package,
4762                format,
4763                mode,
4764                source,
4765                byte_len: None,
4766            });
4767            if !file.is_null() {
4768                *file = core::ptr::null_mut();
4769            }
4770            return false_0;
4771        };
4772        let byte_len = Self::source_index(bytes.len());
4773        engine.resource_request_records.push(PortableResourceRequestRecord {
4774            name: name.clone(),
4775            kind: request_kind,
4776            package: request_package.clone(),
4777            format,
4778            mode: mode.clone(),
4779            source,
4780            byte_len: Some(byte_len),
4781        });
4782        let mut handle = Box::new(PortableFileHandle::new(
4783            name,
4784            request_package,
4785            format,
4786            bytes,
4787        ));
4788        // Text inputs get XeTeX's `u_open_in` encoding sniff, binary inputs stay raw `Bytes`.
4789        Self::resolve_input_encoding(&mut handle);
4790        if format == resource_format_tfm {
4791            engine.state.tfmtemp = handle.read_byte().map_or(-1, |byte| byte as integer);
4792        }
4793        if !file.is_null() {
4794            *file = Box::into_raw(handle);
4795            return true_0;
4796        }
4797        drop(handle);
4798        false_0
4799    }
4800
4801    unsafe fn begin_primary_input_raw(
4802        self: &mut Self,
4803        name: &str,
4804        bytes: Vec<u8>,
4805    ) -> EngineFlow<boolean> {
4806        if self.state.inputfile.is_null() || self.state.sourcefilenamestack.is_null()
4807            || self.state.fullsourcefilenamestack.is_null()
4808            || self.state.buffer.is_null()
4809        {
4810            return Ok(false_0);
4811        }
4812        let Some(source_name) = Self::intern_static_pool_string(self, name)? else {
4813            return Ok(false_0);
4814        };
4815        self.beginfilereading()?;
4816        let slot = self.state.curinput.indexfield as isize;
4817        let mut handle = Box::new(
4818            PortableFileHandle::new(
4819                name.to_string(),
4820                None,
4821                resource_format_tex_input,
4822                bytes,
4823            ),
4824        );
4825        Self::resolve_input_encoding(&mut handle);
4826        *self.state.inputfile.offset(slot) = Box::into_raw(handle);
4827        self.state.curinput.namefield = source_name as halfword;
4828        *self.state.sourcefilenamestack.offset(slot) = source_name;
4829        *self.state.fullsourcefilenamestack.offset(slot) = source_name;
4830        self.state.curinput.statefield = NEW_LINE_STATE;
4831        self.state.line = 1 as integer;
4832        self.state.src_primary_name = source_name;
4833        self.reset_source_tracking_registers();
4834        if Self::boundary_input_line(
4835            self as *mut PortableTexEngine<'_>,
4836            *self.state.inputfile.offset(slot) as NativeFileHandle,
4837        )? == 0
4838        {
4839            self.endfilereading();
4840            return Ok(false_0);
4841        }
4842        self.firmuptheline()?;
4843        let eqtb = self.state.zeqtb.as_mut_ptr();
4844        let endline_char = (*eqtb.offset(EQTB_END_LINE_CHAR_LOC as isize)).u.CINT;
4845        if !(0..=255).contains(&endline_char) {
4846            self.state.curinput.limitfield -= 1;
4847        } else {
4848            *self.state.buffer.offset(self.state.curinput.limitfield as isize) = endline_char
4849                as UnicodeScalar;
4850        }
4851        self.state.first = (self.state.curinput.limitfield as i32 + 1) as integer;
4852        self.state.curinput.locfield = self.state.curinput.startfield;
4853        Ok(true_0)
4854    }
4855
4856    /// XeTeX `input_line`: decodes one line into `buffer[first..last]` and records each slot's input bytes.
4857    pub(crate) unsafe fn boundary_input_line(
4858        engine: *mut PortableTexEngine<'_>,
4859        file: NativeFileHandle,
4860    ) -> EngineFlow<boolean> {
4861        let Some(engine) = engine.as_mut() else {
4862            return Ok(false_0);
4863        };
4864        if file.is_null() || engine.state.buffer.is_null() {
4865            return Ok(false_0);
4866        }
4867
4868        let handle = &mut *file;
4869        if !handle.has_remaining() {
4870            return Ok(false_0);
4871        }
4872
4873        let first = engine.state.first.max(0) as usize;
4874        let limit = engine.state.bufsize.max(0) as usize;
4875        let mut last = first;
4876        // Byte offset where the line's content stops, before its terminator.
4877        let content_end;
4878        loop {
4879            let start = handle.next_scalar_offset();
4880            let Some(scalar) = handle.next_input_scalar() else {
4881                content_end = handle.cursor;
4882                break;
4883            };
4884            match scalar {
4885                0x0A => {
4886                    content_end = start;
4887                    break;
4888                }
4889                0x0D => {
4890                    content_end = start;
4891                    // A CR swallows a following LF, the peek is undone when the next scalar is not LF.
4892                    let saved_cursor = handle.cursor;
4893                    let saved_lookahead = handle.saved_char;
4894                    let was_eof = handle.eof_after_failed_read;
4895                    if handle.next_input_scalar() != Some(0x0A) {
4896                        handle.cursor = saved_cursor;
4897                        handle.saved_char = saved_lookahead;
4898                        handle.eof_after_failed_read = was_eof;
4899                    }
4900                    break;
4901                }
4902                scalar => {
4903                    if last >= limit {
4904                        // TeX reports the partial line as the error context before giving up.
4905                        engine.state.curinput.locfield = first as halfword;
4906                        engine.state.curinput.limitfield = last as halfword - 1;
4907                        let size = engine.state.bufsize;
4908                        match engine.zoverflow(POOL_BUFFER_SIZE, size)? {}
4909                    }
4910                    *engine.state.buffer.add(last) = scalar as UnicodeScalar;
4911                    Self::record_slot_bytes(engine, last, (Self::source_index(start), 0));
4912                    last += 1;
4913                }
4914            }
4915        }
4916        let decoded_last = last;
4917        let line_break_end = Self::source_index(handle.cursor);
4918
4919        while last > first && *engine.state.buffer.add(last - 1) == b' ' as UnicodeScalar {
4920            last -= 1;
4921        }
4922        if last >= engine.state.maxbufstack.max(0) as usize {
4923            engine.state.maxbufstack = last as integer;
4924        }
4925        // Each slot ends where the next begins, and the limit slot holding `\endlinechar` spans the line break.
4926        let kept_end = if last < decoded_last {
4927            engine.heap.src_buf_bytes[last].0
4928        } else {
4929            Self::source_index(content_end)
4930        };
4931        for slot in first..last {
4932            let end = if slot + 1 < last {
4933                engine.heap.src_buf_bytes[slot + 1].0
4934            } else {
4935                kept_end
4936            };
4937            engine.heap.src_buf_bytes[slot].1 = end;
4938        }
4939        Self::record_slot_bytes(engine, last, (kept_end, line_break_end));
4940
4941        if handle.format == resource_format_tex_input || handle.format == 0 {
4942            engine.current_input_package_owner = handle.package.clone();
4943        }
4944        // A token that straddles the line break is lexed in the refilling `get_next` call, so restart its span.
4945        if engine.state.source_tracking {
4946            engine.state.src_token_start = first as integer;
4947        }
4948        engine.state.last = last as integer;
4949        Ok(true_0)
4950    }
4951
4952    fn record_slot_bytes(engine: &mut PortableTexEngine<'_>, slot: usize, bytes: (u32, u32)) {
4953        let map = &mut engine.heap.src_buf_bytes;
4954        if map.len() <= slot {
4955            map.resize(slot + 1, (0, 0));
4956        }
4957        map[slot] = bytes;
4958    }
4959
4960    pub(crate) unsafe fn boundary_read_byte(_file: NativeFileHandle) -> integer {
4961        if _file.is_null() {
4962            return -1;
4963        }
4964        (&mut *_file).read_byte().map_or(-1, |byte| byte as integer)
4965    }
4966
4967    pub(crate) unsafe fn boundary_end_of_file(_file: NativeFileHandle) -> integer {
4968        if _file.is_null() || (&*_file).is_eof() {
4969            1
4970        } else {
4971            0
4972        }
4973    }
4974
4975    pub(crate) unsafe fn boundary_flush_file(_file: NativeFileHandle) -> integer {
4976        0
4977    }
4978
4979    /// `wlog` and file output: a null handle is the log, which the transcript records.
4980    pub(crate) unsafe fn boundary_write_byte(
4981        engine: *mut PortableTexEngine<'_>,
4982        character: integer,
4983        file: NativeFileHandle,
4984    ) -> integer {
4985        if !file.is_null() {
4986            if let Ok(byte) = u8::try_from(character) {
4987                (*file).bytes.push(byte);
4988            }
4989            return character;
4990        }
4991        if let Some(engine) = engine.as_mut() {
4992            if let Ok(byte) = u8::try_from(character) {
4993                engine.transcript_bytes.push(byte);
4994            }
4995        }
4996        character
4997    }
4998
4999    /// `wterm`: terminal output joins the transcript only until the log opens, the log repeats it after that.
5000    pub(crate) unsafe fn boundary_write_terminal_byte(
5001        engine: *mut PortableTexEngine<'_>,
5002        character: integer,
5003        _file: NativeFileHandle,
5004    ) -> integer {
5005        if let Some(engine) = engine.as_mut() {
5006            if engine.state.logopened == false_0 {
5007                if let Ok(byte) = u8::try_from(character) {
5008                    engine.transcript_bytes.push(byte);
5009                }
5010            }
5011        }
5012        character
5013    }
5014
5015    /// `print_nl` hook: starts capturing an error message once `print_err` has printed its prefix.
5016    pub(crate) fn begin_error_message(this: &mut Self, s: strnumber) {
5017        if s == POOL_ERROR_PREFIX {
5018            this.error_message_capture = Some(Vec::new());
5019        }
5020    }
5021
5022    /// `print_file_line` hook: the prefix `print_err` uses in file line error style.
5023    pub(crate) fn begin_file_line_error_message(this: &mut Self) {
5024        this.error_message_capture = Some(Vec::new());
5025    }
5026
5027    /// `print_raw_char` hook: records a message byte while an error message is being printed.
5028    pub(crate) fn capture_error_byte(this: &mut Self, s: UnicodeScalar) {
5029        if this.state.selector < SELECTOR_PSEUDO {
5030            if let (Some(capture), Ok(byte)) = (this.error_message_capture.as_mut(), u8::try_from(s)) {
5031                capture.push(byte);
5032            }
5033        }
5034    }
5035
5036    /// `error` hook: the message is complete once `error` starts, before it prints its period and context.
5037    pub(crate) fn end_error_message(this: &mut Self) {
5038        if let Some(capture) = this.error_message_capture.take() {
5039            this.captured_error_message = Some(String::from_utf8_lossy(&capture).into_owned());
5040        }
5041    }
5042
5043    pub(crate) unsafe fn boundary_close_file(_file: NativeFileHandle) {
5044        if !_file.is_null() {
5045            drop(Box::from_raw(_file));
5046        }
5047    }
5048
5049    pub(crate) unsafe fn get_seconds_and_micros(
5050        engine: *mut PortableTexEngine<'_>,
5051        seconds: *mut integer,
5052        micros: *mut integer,
5053    ) {
5054        let clock = engine
5055            .as_mut()
5056            .map(|engine| engine.platform.clock())
5057            .unwrap_or_default();
5058        if !seconds.is_null() {
5059            *seconds = clock.seconds;
5060        }
5061        if !micros.is_null() {
5062            *micros = clock.micros;
5063        }
5064    }
5065
5066    pub(crate) unsafe fn linebreak_start(
5067        engine: *mut PortableTexEngine<'_>,
5068        font: integer,
5069        locale: integer,
5070        text: *mut uint16_t,
5071        text_length: integer,
5072    ) {
5073        let Some(engine) = engine.as_mut() else {
5074            return;
5075        };
5076        let text = if text.is_null() || text_length <= 0 {
5077            &[]
5078        } else {
5079            core::slice::from_raw_parts(text as *const uint16_t, text_length as usize)
5080        };
5081        engine
5082            .platform
5083            .linebreak_start(PortableLinebreakRequest { font, locale, text });
5084    }
5085
5086    pub(crate) unsafe fn linebreak_next(engine: *mut PortableTexEngine<'_>) -> integer {
5087        engine
5088            .as_mut()
5089            .and_then(|engine| engine.platform.linebreak_next())
5090            .unwrap_or(-1)
5091    }
5092
5093    // Registers \Uhostbox during format initialization so it dumps with the format eqtb and hash.
5094    pub(crate) unsafe fn register_host_box_primitive(self: &mut Self) -> EngineFlow<()> {
5095        let eqtb = self.state.zeqtb.as_mut_ptr();
5096        let name = b"Uhostbox";
5097        for (index, byte) in name.iter().enumerate() {
5098            *self.state.buffer.add(index) = *byte as UnicodeScalar;
5099        }
5100        let saved = self.state.nonewcontrolsequence;
5101        self.state.nonewcontrolsequence = 0 as boolean;
5102        let cs = (&mut *(self as *mut PortableTexEngine<'_>))
5103            .zidlookup(0 as integer, name.len() as integer)?;
5104        self.state.nonewcontrolsequence = saved;
5105        (*eqtb.offset(cs as isize)).hh.u.B1 = LEVEL_ONE;
5106        (*eqtb.offset(cs as isize)).hh.u.B0 = CMD_EXTENSION;
5107        (*eqtb.offset(cs as isize)).hh.v.RH = HOST_BOX_EXTENSION_CODE as halfword;
5108        Ok(())
5109    }
5110
5111    // A five word marker whatsit laid out like a glyph node, metrics in words 1 to 3 and its payload in word 4.
5112    unsafe fn host_box_marker(
5113        engine: *mut PortableTexEngine<'_>,
5114        subtype: i32,
5115        payload: integer,
5116        width: scaled,
5117        height: scaled,
5118        depth: scaled,
5119    ) -> EngineFlow<halfword> {
5120        let this = &mut *engine;
5121        let marker = this.zgetnode(HOST_BOX_MARKER_SIZE)?;
5122        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5123        (*mem.offset(marker as isize)).hh.u.B0 = WHATSIT_NODE as i16;
5124        (*mem.offset(marker as isize)).hh.u.B1 = subtype as i16;
5125        (*mem.offset(marker as isize)).hh.v.RH = TEX_NULL;
5126        (*mem.offset((marker as i32 + 1 as i32) as isize)).u.CINT = width;
5127        (*mem.offset((marker as i32 + 2 as i32) as isize)).u.CINT = depth;
5128        (*mem.offset((marker as i32 + 3 as i32) as isize)).u.CINT = height;
5129        (*mem.offset((marker as i32 + 4 as i32) as isize)).u.CINT = payload;
5130        Ok(marker)
5131    }
5132
5133    // Asks the host for the box behind `token`, None falls back to a deterministic zero size box.
5134    unsafe fn host_box_build(
5135        engine: *mut PortableTexEngine<'_>,
5136        token: integer,
5137        style: PortableHostBoxStyle,
5138        font_size: scaled,
5139    ) -> EngineFlow<halfword> {
5140        let this = &mut *engine;
5141        let response = this.platform.host_box(PortableHostBoxRequest {
5142            token,
5143            style,
5144            font_size,
5145        });
5146        let hbox = this.newnullbox()?;
5147        let Some(host_box) = response else {
5148            return Ok(hbox);
5149        };
5150        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5151        (*mem.offset((hbox as i32 + 1 as i32) as isize)).u.CINT = host_box.width;
5152        (*mem.offset((hbox as i32 + 2 as i32) as isize)).u.CINT = host_box.depth;
5153        (*mem.offset((hbox as i32 + 3 as i32) as isize)).u.CINT = host_box.height;
5154        let id = this.state.host_box_next_record;
5155        this.state.host_box_next_record = id.saturating_add(1);
5156        let (width, height, depth) = (host_box.width, host_box.height, host_box.depth);
5157        this.hostbox_records.insert(id, host_box);
5158        let marker = Self::host_box_marker(
5159            engine,
5160            HOST_BOX_RESOLVED_SUBTYPE,
5161            id,
5162            width,
5163            height,
5164            depth,
5165        )?;
5166        (*mem.offset((hbox as i32 + 5 as i32) as isize)).hh.v.RH = marker;
5167        Ok(hbox)
5168    }
5169
5170    // \Uhostbox scan site: math defers to the mlist pass for style, other modes resolve now as Text.
5171    pub(crate) unsafe fn host_box_insert(
5172        engine: *mut PortableTexEngine<'_>,
5173        token: integer,
5174    ) -> EngineFlow<()> {
5175        let Some(this) = engine.as_mut() else {
5176            return Ok(());
5177        };
5178        let mode = (this.state.curlist.modefield as i32).abs();
5179        if mode == MMODE {
5180            let marker =
5181                Self::host_box_marker(engine, HOST_BOX_PENDING_SUBTYPE, token, 0, 0, 0)?;
5182            let this = &mut *engine;
5183            let placeholder = this.newnullbox()?;
5184            let noad = this.newnoad()?;
5185            let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5186            (*mem.offset((placeholder as i32 + 5 as i32) as isize)).hh.v.RH = marker;
5187            (*mem.offset((noad as i32 + 1 as i32) as isize)).hh.v.RH = SUB_BOX;
5188            (*mem.offset((noad as i32 + 1 as i32) as isize)).hh.v.LH = placeholder;
5189            (*mem.offset(this.state.curlist.tailfield as isize)).hh.v.RH = noad;
5190            this.state.curlist.tailfield = noad;
5191        } else {
5192            let eqtb = this.state.zeqtb.as_mut_ptr();
5193            let font = (*eqtb.offset(CUR_FONT_LOC as isize)).hh.v.RH as integer;
5194            let font_size = this.font_at_size(font);
5195            let hbox =
5196                Self::host_box_build(engine, token, PortableHostBoxStyle::Text, font_size)?;
5197            // `box_end` appends like `\hbox`, with baselineskip in vertical mode and space factor 1000 in text.
5198            let this = &mut *engine;
5199            this.state.curbox = hbox;
5200            this.zboxend(0)?;
5201        }
5202        Ok(())
5203    }
5204
5205    // mlist pass hook: swap a pending placeholder nucleus for the host's box under curstyle.
5206    pub(crate) unsafe fn host_box_resolve_noad(
5207        engine: *mut PortableTexEngine<'_>,
5208        q: halfword,
5209    ) -> EngineFlow<()> {
5210        let Some(this) = engine.as_mut() else {
5211            return Ok(());
5212        };
5213        if q < 0 || q >= this.state.himemmin {
5214            return Ok(());
5215        }
5216        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5217        // A placeholder can reach the nucleus of any noad with one, through a math group collapse.
5218        let noad_type = (*mem.offset(q as isize)).hh.u.B0 as i32;
5219        if !(ORD_NOAD..=VCENTER_NOAD).contains(&noad_type) || noad_type == FRACTION_NOAD {
5220            return Ok(());
5221        }
5222        let style_code = this.state.curstyle as i32;
5223        Self::host_box_resolve_field(engine, (q as i32 + 1 as i32) as halfword, style_code)
5224    }
5225
5226    // clean_box hook: fields copied out of single atom groups (scripts, fractions) resolve here.
5227    pub(crate) unsafe fn host_box_resolve_field(
5228        engine: *mut PortableTexEngine<'_>,
5229        field: halfword,
5230        style_code: i32,
5231    ) -> EngineFlow<()> {
5232        let Some(this) = engine.as_mut() else {
5233            return Ok(());
5234        };
5235        if field < 0 {
5236            return Ok(());
5237        }
5238        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5239        // The field must hold a box whose list is a pending marker whatsit.
5240        if (*mem.offset(field as isize)).hh.v.RH != SUB_BOX {
5241            return Ok(());
5242        }
5243        let placeholder = (*mem.offset(field as isize)).hh.v.LH;
5244        if placeholder < 0 || placeholder == TEX_NULL {
5245            return Ok(());
5246        }
5247        let marker = (*mem.offset((placeholder as i32 + 5 as i32) as isize)).hh.v.RH;
5248        if marker < 0 || marker == TEX_NULL || marker >= this.state.himemmin {
5249            return Ok(());
5250        }
5251        if (*mem.offset(marker as isize)).hh.u.B0 as i32 != WHATSIT_NODE
5252            || (*mem.offset(marker as isize)).hh.u.B1 as i32 != HOST_BOX_PENDING_SUBTYPE
5253        {
5254            return Ok(());
5255        }
5256        let token = (*mem.offset((marker as i32 + 4 as i32) as isize)).u.CINT;
5257        // Styles map to sizes as `mlist_to_hlist` maps them, display and text styles share the text size.
5258        let size = if style_code < SCRIPT_STYLE {
5259            0
5260        } else {
5261            SCRIPT_SIZE * ((style_code - 2) / 2)
5262        };
5263        let style = match size {
5264            0 => PortableHostBoxStyle::Text,
5265            SCRIPT_SIZE => PortableHostBoxStyle::Script,
5266            _ => PortableHostBoxStyle::ScriptScript,
5267        };
5268        // Size context comes from the family 2 symbol font at the active math size.
5269        let eqtb = this.state.zeqtb.as_mut_ptr();
5270        let font = (*eqtb.offset((MATH_FONT_BASE + 2 + size as i64) as isize))
5271            .hh
5272            .v
5273            .RH as integer;
5274        let font_size = this.font_at_size(font);
5275        let hbox = Self::host_box_build(engine, token, style, font_size)?;
5276        // The placeholder carries the `\hostbox` span, copy it to its replacement box and that box's marker.
5277        Self::src_carry_copy(engine as *mut Self, placeholder, hbox);
5278        let mem: *mut memoryword = (*engine).state.zmem.as_mut_ptr();
5279        let resolved_marker = (*mem.offset((hbox as i32 + 5 as i32) as isize)).hh.v.RH;
5280        if resolved_marker >= 0 && resolved_marker != TEX_NULL {
5281            Self::src_carry_copy(engine as *mut Self, placeholder, resolved_marker);
5282        }
5283        (&mut *engine).zflushnodelist(placeholder)?;
5284        let this = &mut *engine;
5285        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5286        (*mem.offset(field as isize)).hh.v.LH = hbox;
5287        Ok(())
5288    }
5289
5290    pub(crate) unsafe fn abort_engine(
5291        engine: *mut PortableTexEngine<'_>,
5292        status: integer,
5293    ) -> EngineFlow<core::convert::Infallible> {
5294        if let Some(engine) = engine.as_mut() {
5295            engine.last_abort_status = Some(status);
5296        }
5297        Err(EngineBreak::Abort(EngineAbort { status }))
5298    }
5299
5300    /// Stops the run with `message` when the sandbox is on, a no op otherwise.
5301    pub(crate) unsafe fn sandbox_reject(
5302        engine: *mut PortableTexEngine<'_>,
5303        message: &str,
5304    ) -> EngineFlow<()> {
5305        if let Some(engine) = engine.as_ref() {
5306            if engine.sandbox {
5307                return Err(EngineBreak::Error(EngineError {
5308                    message: message.into(),
5309                    kind: PortableErrorKind::Sandbox,
5310                }));
5311            }
5312        }
5313        Ok(())
5314    }
5315
5316    /// `init_math` sandbox hook: math nested in text is allowed, reopening math after the wrapper closed is not.
5317    pub(crate) unsafe fn sandbox_open_math(
5318        engine: *mut PortableTexEngine<'_>,
5319    ) -> EngineFlow<()> {
5320        if let Some(engine) = engine.as_mut() {
5321            if engine.sandbox {
5322                if engine.sandbox_math_depth == 0 && engine.sandbox_math_opened {
5323                    return Err(EngineBreak::Error(EngineError {
5324                        message: MATH_BREAKOUT_MESSAGE.into(),
5325                        kind: PortableErrorKind::Sandbox,
5326                    }));
5327                }
5328                engine.sandbox_math_opened = true;
5329                engine.sandbox_math_depth += 1;
5330            }
5331        }
5332        Ok(())
5333    }
5334
5335    /// `after_math` sandbox hook: leaves one math level, only the wrapper suffix may close the wrapper math.
5336    pub(crate) unsafe fn sandbox_close_math(
5337        engine: *mut PortableTexEngine<'_>,
5338    ) -> EngineFlow<()> {
5339        if let Some(engine) = engine.as_mut() {
5340            if engine.sandbox && engine.sandbox_math_depth > 0 {
5341                if engine.sandbox_math_depth == 1 && !engine.token_is_from_wrapper_suffix() {
5342                    return Err(EngineBreak::Error(EngineError {
5343                        message: MATH_BREAKOUT_MESSAGE.into(),
5344                        kind: PortableErrorKind::Sandbox,
5345                    }));
5346                }
5347                engine.sandbox_math_depth -= 1;
5348            }
5349        }
5350        Ok(())
5351    }
5352
5353    /// Whether the token just read comes from the marked wrapper suffix, true when the host marked none.
5354    fn token_is_from_wrapper_suffix(&self) -> bool {
5355        let Some(suffix) = self.sandbox_suffix_start else {
5356            return true;
5357        };
5358        // A backed up list replays a token its file level already consumed, so the file level tells the origin.
5359        let mut level = self.state.curinput;
5360        let mut below = usize::try_from(self.state.inputptr).unwrap_or(0);
5361        while level.statefield == TOKEN_LIST_STATE
5362            && (level.indexfield == TOKEN_TYPE_BACKED_UP || level.indexfield == TOKEN_TYPE_BACKED_UP_CHAR)
5363        {
5364            let Some(next) = below.checked_sub(1).and_then(|index| self.heap.inputstack_storage.get(index)) else {
5365                return false;
5366            };
5367            level = *next;
5368            below -= 1;
5369        }
5370        if level.statefield == TOKEN_LIST_STATE || level.namefield != self.state.src_primary_name {
5371            return false;
5372        }
5373        // `loc` is one past the last character the file level consumed.
5374        let consumed = (level.locfield - 1).clamp(level.startfield, level.limitfield.max(level.startfield));
5375        usize::try_from(consumed)
5376            .ok()
5377            .and_then(|slot| self.heap.src_buf_bytes.get(slot))
5378            .is_some_and(|&(start, _)| start >= suffix)
5379    }
5380
5381    /// Sandbox budget tick from `main_control`, `macro_call` and `expand`, so expansion loops end too.
5382    pub(crate) unsafe fn sandbox_tick(
5383        engine: *mut PortableTexEngine<'_>,
5384    ) -> EngineFlow<()> {
5385        if let Some(engine) = engine.as_mut() {
5386            if engine.sandbox {
5387                engine.sandbox_ops = engine.sandbox_ops.saturating_add(1);
5388                if engine.sandbox_ops > engine.sandbox_op_budget {
5389                    return Err(EngineBreak::Error(EngineError {
5390                        message: "expression is too complex or did not terminate".into(),
5391                        kind: PortableErrorKind::Budget,
5392                    }));
5393                }
5394            }
5395        }
5396        Ok(())
5397    }
5398
5399    /// `main_control` dispatch hook: after the wrapper math closes only braces, assignments and `\end` may run.
5400    pub(crate) unsafe fn sandbox_check_command(
5401        engine: *mut PortableTexEngine<'_>,
5402    ) -> EngineFlow<()> {
5403        let Some(engine) = engine.as_ref() else {
5404            return Ok(());
5405        };
5406        if !engine.sandbox || !engine.sandbox_math_opened || engine.sandbox_math_depth != 0 {
5407            return Ok(());
5408        }
5409        // LaTeX's `\everymath` leaves an `\aftergroup` assignment that runs once the wrapper math closes.
5410        match engine.state.curcmd as integer {
5411            CMD_RELAX | CMD_RIGHT_BRACE | CMD_SPACER | CMD_PAR_END | CMD_STOP => Ok(()),
5412            cmd if cmd > CMD_MAX_NON_PREFIXED => Ok(()),
5413            _ => Err(EngineBreak::Error(EngineError {
5414                message: MATH_BREAKOUT_MESSAGE.into(),
5415                kind: PortableErrorKind::Sandbox,
5416            })),
5417        }
5418    }
5419
5420    /// `\write` hook: rejects a sandboxed write to a stream open to a file, other streams reach the terminal.
5421    pub(crate) unsafe fn sandbox_check_write_stream(
5422        engine: *mut PortableTexEngine<'_>,
5423    ) -> EngineFlow<()> {
5424        let Some(this) = engine.as_ref() else {
5425            return Ok(());
5426        };
5427        let mem = this.state.zmem;
5428        let stream = (*mem.offset((this.state.curlist.tailfield + 1) as isize)).hh.v.LH;
5429        let to_file = usize::try_from(stream)
5430            .ok()
5431            .and_then(|stream| this.state.writeopen.get(stream))
5432            .is_some_and(|&open| open != false_0);
5433        if to_file {
5434            Self::sandbox_reject(engine, "\\write to a file is not allowed inside a math expression")?;
5435        }
5436        Ok(())
5437    }
5438
5439    /// `error` hook: every TeX error inside a render ends the run with its message instead of recovering.
5440    pub(crate) unsafe fn surface_error(
5441        engine: *mut PortableTexEngine<'_>,
5442    ) -> EngineFlow<()> {
5443        let message = engine
5444            .as_mut()
5445            .and_then(|engine| engine.captured_error_message.take())
5446            .unwrap_or_else(|| "TeX error".into());
5447        Err(EngineBreak::Error(EngineError {
5448            message,
5449            kind: PortableErrorKind::Tex,
5450        }))
5451    }
5452
5453    /// Stops the run with XeTeX's `pdf_error` message, which upstream ends with `succumb`.
5454    pub(crate) unsafe fn fatal_pdf_error(
5455        engine: *mut PortableTexEngine<'_>,
5456        t: strnumber,
5457        p: strnumber,
5458    ) -> EngineFlow<()> {
5459        let message = engine.as_ref().map_or_else(
5460            || "Error".into(),
5461            |engine| {
5462                let text = |string: strnumber| engine.pool_string(string).unwrap_or_default();
5463                if t == 0 {
5464                    format!("Error: {}", text(p))
5465                } else {
5466                    format!("Error ({}): {}", text(t), text(p))
5467                }
5468            },
5469        );
5470        Err(EngineBreak::Error(EngineError {
5471            message,
5472            kind: PortableErrorKind::Tex,
5473        }))
5474    }
5475
5476    // Source tracking hooks, all no ops while `source_tracking` is off.
5477
5478    /// Enables or disables source tracking, clearing every table so the calls around it may come in any order.
5479    pub fn set_source_tracking(self: &mut Self, on: bool) {
5480        self.state.source_tracking = on;
5481        self.reset_source_tracking_registers();
5482        self.heap.src_spans.clear();
5483        self.heap.src_dedup.clear();
5484        let end = if on { self.heap.mem.len() } else { 0 };
5485        self.heap.node_src = PagedArray::new(0, end, node_src_default, node_src_sig);
5486        self.heap.node_stack = PagedArray::new(0, end, node_stack_default, node_stack_sig);
5487    }
5488
5489    /// Clears the per render source tracking registers, shared by the tracking switch and input start.
5490    fn reset_source_tracking_registers(self: &mut Self) {
5491        self.state.cmd_span = 0;
5492        self.state.pending_call_span = 0;
5493        self.state.src_token_start = 0;
5494        self.state.src_call_start = 0;
5495        self.state.src_call_name = 0;
5496        self.state.src_call_state = 0;
5497        self.state.src_call_index = 0;
5498        self.state.src_call_span = 0;
5499        self.state.src_call_argspan = 0;
5500        self.state.src_user_cmd_span = 0;
5501        self.state.src_tok_span = 0;
5502        self.state.src_anchor_cmd = 0;
5503        self.state.src_grp_closing = 0;
5504        self.state.src_call_user_span = 0;
5505        self.state.curinput.spanfield = 0;
5506        self.state.cur_stack_head = 0;
5507        self.state.src_stack_frozen = false;
5508        self.heap.src_grp_stack.clear();
5509        self.heap.src_native_offsets.clear();
5510        self.heap.src_stack_cells.clear();
5511    }
5512
5513    /// Whether source tracking is currently enabled.
5514    pub fn source_tracking_enabled(&self) -> bool {
5515        self.state.source_tracking
5516    }
5517
5518    /// Interns a span, equal spans share their first `SrcId`.
5519    fn intern_span_raw(self: &mut Self, name: strnumber, start: u32, end: u32, role: u8) -> SrcId {
5520        let span = RawSpan { name, start, end, role };
5521        if let Some(&id) = self.heap.src_dedup.get(&span) {
5522            return id;
5523        }
5524        self.heap.src_spans.push(span);
5525        let id = self.heap.src_spans.len() as SrcId;
5526        self.heap.src_dedup.insert(span, id);
5527        id
5528    }
5529
5530    /// Input byte offset of buffer position `loc` on the current line, past the limit is past the line break.
5531    fn src_buf_offset(&self, loc: integer) -> u32 {
5532        let start = self.state.curinput.startfield;
5533        let limit = self.state.curinput.limitfield;
5534        let slot = |index: integer| {
5535            usize::try_from(index)
5536                .ok()
5537                .and_then(|index| self.heap.src_buf_bytes.get(index))
5538                .copied()
5539                .unwrap_or_default()
5540        };
5541        if loc > limit && limit >= start {
5542            slot(limit).1
5543        } else {
5544            slot(loc.max(start)).0
5545        }
5546    }
5547
5548    /// The span stamped on `node`, `None` when it is unstamped or its source name is unreadable.
5549    pub(crate) fn resolve_node_src(&self, node: halfword) -> Option<PortableSourceSpan> {
5550        if !self.state.source_tracking || node < 0 {
5551            return None;
5552        }
5553        self.src_span_by_id(self.heap.node_src.get_copy(node as usize))
5554    }
5555
5556    /// The interned span `id`, `None` for id 0, while tracking is off, or when its source name is unreadable.
5557    fn src_span_by_id(&self, id: SrcId) -> Option<PortableSourceSpan> {
5558        if !self.state.source_tracking || id == 0 {
5559            return None;
5560        }
5561        let raw = *self.heap.src_spans.get((id - 1) as usize)?;
5562        let name = self.pool_string(raw.name)?;
5563        Some(PortableSourceSpan {
5564            name,
5565            start: raw.start,
5566            end: raw.end,
5567            role: raw.role,
5568        })
5569    }
5570
5571    /// `get_next` hook at the top of its loop: marks where the next token starts, after skipped input.
5572    pub(crate) unsafe fn src_mark_token_start(engine: *mut Self) {
5573        let Some(engine) = engine.as_mut() else {
5574            return;
5575        };
5576        if engine.state.source_tracking && engine.state.curinput.statefield != TOKEN_LIST_STATE {
5577            engine.state.src_token_start = engine.state.curinput.locfield as integer;
5578        }
5579    }
5580
5581    /// `get_next` hook at its end: the span of a token just read from a file line.
5582    pub(crate) unsafe fn src_record_buffer_span(engine: *mut Self) {
5583        let Some(engine) = engine.as_mut() else {
5584            return;
5585        };
5586        if !engine.state.source_tracking || engine.state.curinput.statefield == TOKEN_LIST_STATE {
5587            return;
5588        }
5589        // Names up to 19 are the terminal, `\read` streams and pseudo files, their tokens take the inherited span.
5590        if engine.state.curinput.namefield <= 19 {
5591            engine.state.src_tok_span = engine.state.curinput.spanfield;
5592            return;
5593        }
5594        let a = engine.src_buf_offset(engine.state.src_token_start);
5595        let b = engine.src_buf_offset(engine.state.curinput.locfield as integer);
5596        let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
5597        let name = engine.state.curinput.namefield as strnumber;
5598        let id = engine.intern_span_raw(name, lo, hi, 0);
5599        engine.state.curinput.spanfield = id;
5600        engine.state.src_tok_span = id;
5601        // A control sequence read from the primary input outside argument scanning is the command the user typed.
5602        if engine.state.curcs != 0
5603            && name == engine.state.src_primary_name
5604            && engine.state.scannerstatus as i32 == 0
5605        {
5606            engine.state.src_user_cmd_span = id;
5607            // Reading the buffer ends any token list replay and with it the replayed anchor command.
5608            engine.state.src_anchor_cmd = 0;
5609        }
5610    }
5611
5612    /// `get_next` token list hook: records the origin of the token about to be read from the current list.
5613    pub(crate) unsafe fn src_tokenlist_span(engine: *mut Self) {
5614        let Some(engine) = engine.as_mut() else {
5615            return;
5616        };
5617        if !engine.state.source_tracking {
5618            return;
5619        }
5620        // Macro bodies and the lists after them map to their invocation, never to where they were defined.
5621        if engine.state.curinput.indexfield >= TOKEN_TYPE_MACRO {
5622            engine.state.src_tok_span = engine.state.curinput.spanfield;
5623            return;
5624        }
5625        // Parameter, template, backed up and inserted lists replay cells that carry their own origin.
5626        let cell = engine.state.curinput.locfield;
5627        let id = if cell >= 0 {
5628            engine.heap.node_src.get_copy(cell as usize)
5629        } else {
5630            0
5631        };
5632        if id != 0 {
5633            engine.state.curinput.spanfield = id;
5634        }
5635        engine.state.src_tok_span = engine.state.curinput.spanfield;
5636    }
5637
5638    /// `back_input` hook: stamps the backed up cell with the origin frozen when its token was last read.
5639    pub(crate) unsafe fn src_back_input_stamp(engine: *mut Self, p: halfword) {
5640        let Some(engine) = engine.as_mut() else {
5641            return;
5642        };
5643        if !engine.state.source_tracking || p < 0 {
5644            return;
5645        }
5646        let id = engine.state.src_tok_span;
5647        if id != 0 {
5648            engine.heap.node_src.set(p as usize, id);
5649        }
5650    }
5651
5652    /// `main_control` hook after `get_x_token`: latches the command's span before it scans its arguments.
5653    pub(crate) unsafe fn src_latch_cmd_span(engine: *mut Self) {
5654        let Some(engine) = engine.as_mut() else {
5655            return;
5656        };
5657        if engine.state.source_tracking {
5658            engine.state.cmd_span = engine.state.curinput.spanfield;
5659        }
5660    }
5661
5662    /// `get_avail` hook: stamps a one word node with the current input span and construct stack head.
5663    pub(crate) unsafe fn src_stamp_avail(engine: *mut Self, node: halfword) {
5664        let Some(engine) = engine.as_mut() else {
5665            return;
5666        };
5667        if engine.state.source_tracking && node >= 0 {
5668            let id = engine.state.curinput.spanfield;
5669            engine.heap.node_src.set(node as usize, id);
5670            engine.heap.node_stack.set(node as usize, engine.state.cur_stack_head);
5671        }
5672    }
5673
5674    /// `get_node` hook: stamps the whole node with the construct span and stack head, clearing a stale occupant.
5675    pub(crate) unsafe fn src_stamp_node_range(engine: *mut Self, node: halfword, size: integer) {
5676        let Some(engine) = engine.as_mut() else {
5677            return;
5678        };
5679        if !engine.state.source_tracking || node < 0 || size <= 0 {
5680            return;
5681        }
5682        let id = engine.state.cmd_span;
5683        let head = engine.state.cur_stack_head;
5684        let base = node as usize;
5685        for i in 0..size as usize {
5686            engine.heap.node_src.set(base + i, id);
5687            engine.heap.node_stack.set(base + i, head);
5688        }
5689    }
5690
5691    /// `copy_node_list` hook: a copy keeps the span and enclosing constructs of a stamped original.
5692    pub(crate) unsafe fn src_carry_copy(engine: *mut Self, src: halfword, dst: halfword) {
5693        let Some(engine) = engine.as_mut() else {
5694            return;
5695        };
5696        if !engine.state.source_tracking || src < 0 || dst < 0 {
5697            return;
5698        }
5699        let id = engine.heap.node_src.get_copy(src as usize);
5700        if id != 0 {
5701            engine.heap.node_src.set(dst as usize, id);
5702            let head = engine.heap.node_stack.get_copy(src as usize);
5703            engine.heap.node_stack.set(dst as usize, head);
5704        }
5705    }
5706
5707    /// `src_token_copy` hook: a copied token cell keeps the span and enclosing constructs of the cell it copies.
5708    pub(crate) unsafe fn src_carry_token_span(engine: *mut Self, dest: halfword, src: halfword) {
5709        let Some(engine) = engine.as_mut() else {
5710            return;
5711        };
5712        if !engine.state.source_tracking || dest < 0 || src < 0 {
5713            return;
5714        }
5715        let id = engine.heap.node_src.get_copy(src as usize);
5716        if id != 0 {
5717            engine.heap.node_src.set(dest as usize, id);
5718            let head = engine.heap.node_stack.get_copy(src as usize);
5719            engine.heap.node_stack.set(dest as usize, head);
5720        }
5721    }
5722
5723    /// `new_character` hook: a TFM character takes the latched command's span, so `\char98` maps to the command.
5724    pub(crate) unsafe fn src_stamp_char(engine: *mut Self, node: halfword) {
5725        let Some(engine) = engine.as_mut() else {
5726            return;
5727        };
5728        if !engine.state.source_tracking || node < 0 {
5729            return;
5730        }
5731        let id = engine.state.cmd_span;
5732        if id != 0 {
5733            engine.heap.node_src.set(node as usize, id);
5734        }
5735        engine.heap.node_stack.set(node as usize, engine.state.cur_stack_head);
5736    }
5737
5738    /// `mlist_to_hlist` hook per noad: what the noad builds takes the noad's own span.
5739    pub(crate) unsafe fn src_mlist_repoint(engine: *mut Self, q: halfword) {
5740        let Some(engine) = engine.as_mut() else {
5741            return;
5742        };
5743        if !engine.state.source_tracking || q < 0 {
5744            return;
5745        }
5746        let id = engine.heap.node_src.get_copy(q as usize);
5747        if id != 0 {
5748            engine.state.cmd_span = id;
5749        }
5750    }
5751
5752    /// `scan_math` hook: records the span of a one character field so `clean_box` can pass it on.
5753    pub(crate) unsafe fn src_stamp_field(engine: *mut Self, field: halfword) {
5754        let Some(engine) = engine.as_mut() else {
5755            return;
5756        };
5757        if !engine.state.source_tracking || field < 0 {
5758            return;
5759        }
5760        let id = engine.state.curinput.spanfield;
5761        if id != 0 {
5762            engine.heap.node_src.set(field as usize, id);
5763        }
5764    }
5765
5766    /// `clean_box` hook: the noad it builds for a one character field takes the field's span.
5767    pub(crate) unsafe fn src_carry_field(engine: *mut Self, field: halfword, noad: halfword) {
5768        let Some(engine) = engine.as_mut() else {
5769            return;
5770        };
5771        if !engine.state.source_tracking || field < 0 || noad < 0 {
5772            return;
5773        }
5774        let id = engine.heap.node_src.get_copy(field as usize);
5775        let head = engine.heap.node_stack.get_copy(field as usize);
5776        if id != 0 {
5777            // Stamps the whole four word noad, the mlist hook reads its first word and the nucleus its second.
5778            for i in 0..4usize {
5779                engine.heap.node_src.set(noad as usize + i, id);
5780            }
5781        }
5782        if head != 0 {
5783            for i in 0..4usize {
5784                engine.heap.node_stack.set(noad as usize + i, head);
5785            }
5786        }
5787    }
5788
5789    /// `make_ord` hook: a nucleus glyph takes its field's span and enclosing constructs, the noad keeps its own.
5790    pub(crate) unsafe fn src_carry_nucleus(engine: *mut Self, noad: halfword, glyph: halfword) {
5791        let Some(engine) = engine.as_mut() else {
5792            return;
5793        };
5794        if !engine.state.source_tracking || noad < 0 || glyph < 0 {
5795            return;
5796        }
5797        let id = engine.heap.node_src.get_copy(noad as usize + 1);
5798        if id != 0 {
5799            engine.heap.node_src.set(glyph as usize, id);
5800        }
5801        let head = engine.heap.node_stack.get_copy(noad as usize + 1);
5802        engine.heap.node_stack.set(glyph as usize, head);
5803    }
5804
5805    /// `handle_right_brace` hook: a group reduced to one character keeps its span in the field it collapses into.
5806    pub(crate) unsafe fn src_carry_collapse(engine: *mut Self, noad: halfword, field: halfword) {
5807        let Some(engine) = engine.as_mut() else {
5808            return;
5809        };
5810        if !engine.state.source_tracking || noad < 0 || field < 0 {
5811            return;
5812        }
5813        let id = engine.heap.node_src.get_copy(noad as usize);
5814        if id != 0 {
5815            engine.heap.node_src.set(field as usize, id);
5816        }
5817        let head = engine.heap.node_stack.get_copy(noad as usize);
5818        if head != 0 {
5819            engine.heap.node_stack.set(field as usize, head);
5820        }
5821    }
5822
5823    /// `clean_box` entry hook: saves `cmd_span` across recursive cleaning.
5824    pub(crate) unsafe fn src_save_cmd_span(engine: *mut Self) -> u32 {
5825        engine.as_ref().map_or(0, |engine| engine.state.cmd_span)
5826    }
5827
5828    /// `clean_box` exit hook: restores `cmd_span` so what is built after takes the construct's span.
5829    pub(crate) unsafe fn src_restore_cmd_span(engine: *mut Self, saved: u32) {
5830        if let Some(engine) = engine.as_mut() {
5831            if engine.state.source_tracking {
5832                engine.state.cmd_span = saved;
5833            }
5834        }
5835    }
5836
5837    /// Pushes a construct frame as the live top, the stack freezes at head 0 once it holds [`SRC_STACK_CAP`].
5838    fn src_stack_push(self: &mut Self, span: SrcId, start: u32, name: strnumber, pending: bool) {
5839        if self.state.src_stack_frozen {
5840            return;
5841        }
5842        if self.heap.src_stack_cells.len() >= SRC_STACK_CAP {
5843            self.state.src_stack_frozen = true;
5844            self.state.cur_stack_head = 0;
5845            return;
5846        }
5847        self.heap.src_stack_cells.push(SrcStackCell {
5848            span,
5849            parent: self.state.cur_stack_head,
5850            start,
5851            name,
5852            pending,
5853        });
5854        self.state.cur_stack_head = self.heap.src_stack_cells.len() as u32;
5855    }
5856
5857    /// Pops the live top frame, restoring its parent as the head.
5858    fn src_stack_pop(self: &mut Self) {
5859        let head = self.state.cur_stack_head;
5860        if self.state.src_stack_frozen || head == 0 {
5861            return;
5862        }
5863        if let Some(cell) = self.heap.src_stack_cells.get((head - 1) as usize) {
5864            self.state.cur_stack_head = cell.parent;
5865        }
5866    }
5867
5868    /// `end_token_list` hook: pops a macro frame and keeps the furthest reaching argument span for the hull.
5869    pub(crate) unsafe fn src_end_token_list(engine: *mut Self, token_type: i32) {
5870        let Some(engine) = engine.as_mut() else {
5871            return;
5872        };
5873        if !engine.state.source_tracking {
5874            return;
5875        }
5876        let cand = engine.state.curinput.spanfield;
5877        if cand != 0 {
5878            let cand_end = engine
5879                .heap
5880                .src_spans
5881                .get((cand - 1) as usize)
5882                .map(|r| r.end)
5883                .unwrap_or(0);
5884            let cur_end = if engine.state.src_call_argspan != 0 {
5885                engine
5886                    .heap
5887                    .src_spans
5888                    .get((engine.state.src_call_argspan - 1) as usize)
5889                    .map(|r| r.end)
5890                    .unwrap_or(0)
5891            } else {
5892                0
5893            };
5894            if engine.state.src_call_argspan == 0 || cand_end > cur_end {
5895                engine.state.src_call_argspan = cand;
5896            }
5897        }
5898        if token_type == TOKEN_TYPE_MACRO {
5899            engine.src_stack_pop();
5900        }
5901    }
5902
5903    /// `scan_math` hook at a `{`: pushes a pending frame from the command's start, finished at the matching `}`.
5904    pub(crate) unsafe fn src_scan_math_group_open(engine: *mut Self) {
5905        let Some(engine) = engine.as_mut() else {
5906            return;
5907        };
5908        if !engine.state.source_tracking {
5909            return;
5910        }
5911        let cmd = engine.state.cmd_span;
5912        let (start, name) = if cmd != 0 {
5913            match engine.heap.src_spans.get((cmd - 1) as usize) {
5914                Some(raw) => (raw.start, raw.name),
5915                None => (0, 0),
5916            }
5917        } else {
5918            (0, 0)
5919        };
5920        engine.src_stack_push(0, start, name, true);
5921        // The opening brace span, a bare group pulls its fraction's start back to it.
5922        engine.heap.src_grp_stack.push(engine.state.curinput.spanfield);
5923    }
5924
5925    /// `handle_right_brace` hook for a math group: finishes the pending frame at the closing brace and pops it.
5926    pub(crate) unsafe fn src_scan_math_group_close(engine: *mut Self) {
5927        let eptr = engine;
5928        let Some(e) = engine.as_mut() else {
5929            return;
5930        };
5931        if !e.state.source_tracking {
5932            return;
5933        }
5934        // `src_construct_extend_to_loc`, later in the same arm, uses this opening brace span.
5935        e.state.src_grp_closing = e.heap.src_grp_stack.pop().unwrap_or(0);
5936        let grp = e.state.src_grp_closing;
5937        // A generalized fraction built in this group gets the whole group's span.
5938        let aux = e.state.curlist.auxfield.u.CINT;
5939        let frac = if aux > 0 && aux != TEX_NULL { aux } else { -1 };
5940        if !e.state.src_stack_frozen && e.state.cur_stack_head != 0 {
5941            let idx = (e.state.cur_stack_head - 1) as usize;
5942            if let Some(cell) = e.heap.src_stack_cells.get(idx).copied() {
5943                // Only a pending group frame in the current file is finished, any other top frame is only popped.
5944                if cell.pending
5945                    && cell.name != 0
5946                    && e.state.curinput.statefield != TOKEN_LIST_STATE
5947                    && cell.name == e.state.curinput.namefield as strnumber
5948                {
5949                    let end = e.src_buf_offset(e.state.curinput.locfield as integer);
5950                    let (lo, hi) = if cell.start <= end {
5951                        (cell.start, end)
5952                    } else {
5953                        (end, cell.start)
5954                    };
5955                    let id = e.intern_span_raw(cell.name, lo, hi, 1);
5956                    if let Some(c) = e.heap.src_stack_cells.get_mut(idx) {
5957                        c.span = id;
5958                        c.pending = false;
5959                    }
5960                }
5961                e.state.cur_stack_head = cell.parent;
5962            }
5963        }
5964        if frac >= 0 {
5965            Self::src_construct_extent(eptr, frac, grp);
5966        }
5967    }
5968
5969    /// `math_radical` and `math_ac` hook: pulls the new noad's start back to the command the user typed.
5970    pub(crate) unsafe fn src_construct_anchor(engine: *mut Self) {
5971        let Some(engine) = engine.as_mut() else {
5972            return;
5973        };
5974        if !engine.state.source_tracking {
5975            return;
5976        }
5977        let noad = engine.state.curlist.tailfield as i32;
5978        if noad < 0 {
5979            return;
5980        }
5981        let id = engine.heap.node_src.get_copy(noad as usize);
5982        if id == 0 {
5983            return;
5984        }
5985        let Some(raw) = engine.heap.src_spans.get((id - 1) as usize).copied() else {
5986            return;
5987        };
5988        // A replayed nested construct anchors to its own replayed command, never to the outer user command.
5989        let anchor = (engine.state.src_anchor_cmd != 0)
5990            .then(|| engine.heap.src_spans.get((engine.state.src_anchor_cmd - 1) as usize).copied())
5991            .flatten()
5992            .filter(|u| u.name == raw.name);
5993        let chosen = match anchor {
5994            Some(u) => {
5995                if u.start < raw.start {
5996                    Some(u)
5997                } else {
5998                    None
5999                }
6000            }
6001            None => (engine.state.src_user_cmd_span != 0)
6002                .then(|| engine.heap.src_spans.get((engine.state.src_user_cmd_span - 1) as usize).copied())
6003                .flatten()
6004                .filter(|u| u.name == raw.name && u.start < raw.start),
6005        };
6006        let Some(u) = chosen else {
6007            return;
6008        };
6009        let newid = engine.intern_span_raw(raw.name, u.start, raw.end, raw.role);
6010        engine.heap.node_src.set(noad as usize, newid);
6011    }
6012
6013    /// `handle_right_brace` hook: a construct noad whose nucleus group closed extends its span through the brace.
6014    pub(crate) unsafe fn src_construct_extend_to_loc(engine: *mut Self) {
6015        let e = match engine.as_ref() {
6016            Some(e) => e,
6017            None => return,
6018        };
6019        if !e.state.source_tracking {
6020            return;
6021        }
6022        // Only the noad's own nucleus, its `+1` field, extends it, a closing script field does not.
6023        let field = (*e
6024            .state
6025            .savestack
6026            .offset((e.state.saveptr as i32 + 0 as i32) as isize))
6027            .u
6028            .CINT;
6029        let noad = e.state.curlist.tailfield as i32;
6030        let grp = e.state.src_grp_closing;
6031        if noad < 0 || field != noad + 1 {
6032            return;
6033        }
6034        Self::src_construct_extent(engine, noad, grp);
6035    }
6036
6037    /// Extends a construct noad's span from the earlier of its command and group open to the end read so far.
6038    pub(crate) unsafe fn src_construct_extent(engine: *mut Self, noad: halfword, group_open: SrcId) {
6039        let Some(engine) = engine.as_mut() else {
6040            return;
6041        };
6042        if !engine.state.source_tracking || noad < 0 {
6043            return;
6044        }
6045        let id = engine.heap.node_src.get_copy(noad as usize);
6046        if id == 0 {
6047            return;
6048        }
6049        let Some(raw) = engine.heap.src_spans.get((id - 1) as usize).copied() else {
6050            return;
6051        };
6052        let (end, name) = if engine.state.curinput.statefield != TOKEN_LIST_STATE {
6053            (
6054                engine.src_buf_offset(engine.state.curinput.locfield as integer),
6055                engine.state.curinput.namefield as strnumber,
6056            )
6057        } else {
6058            let sid = engine.state.curinput.spanfield;
6059            if sid == 0 {
6060                return;
6061            }
6062            let Some(braw) = engine.heap.src_spans.get((sid - 1) as usize).copied() else {
6063                return;
6064            };
6065            (braw.end, braw.name)
6066        };
6067        if raw.name != name {
6068            return;
6069        }
6070        let end = end.max(raw.end);
6071        let mut start = raw.start;
6072        if group_open != 0 {
6073            if let Some(g) = engine.heap.src_spans.get((group_open - 1) as usize).copied() {
6074                if g.name == name && g.start < start {
6075                    start = g.start;
6076                }
6077            }
6078        }
6079        if start == raw.start && end == raw.end {
6080            return;
6081        }
6082        let newid = engine.intern_span_raw(name, start, end, raw.role);
6083        engine.heap.node_src.set(noad as usize, newid);
6084    }
6085
6086    /// `math_left_right` hook: `\left` opens a group span that `\right` closes over its delimiters.
6087    pub(crate) unsafe fn src_leftright(engine: *mut Self, p: halfword, t: integer) {
6088        let Some(eng) = engine.as_mut() else {
6089            return;
6090        };
6091        if !eng.state.source_tracking {
6092            return;
6093        }
6094        if t == LEFT_NOAD {
6095            let cmd = eng.state.cmd_span;
6096            eng.heap.src_grp_stack.push(cmd);
6097        } else if t == RIGHT_NOAD {
6098            let open = eng.heap.src_grp_stack.pop().unwrap_or(0);
6099            Self::src_construct_extent(engine, p, open);
6100        }
6101    }
6102
6103    /// Spans of the constructs strictly enclosing a node, innermost first and in the node's own source.
6104    pub fn node_enclosing_spans(&self, handle: PortableNodeHandle) -> Vec<PortableSourceSpan> {
6105        let node = handle.0 as halfword;
6106        let mut out: Vec<PortableSourceSpan> = Vec::new();
6107        if !self.state.source_tracking || node < 0 {
6108            return out;
6109        }
6110        let primary = self.resolve_node_src(node);
6111        let mut head = self.heap.node_stack.get_copy(node as usize);
6112        let mut guard = 0u32;
6113        while head != 0 {
6114            guard += 1;
6115            if guard > 4096 {
6116                break;
6117            }
6118            let Some(cell) = self.heap.src_stack_cells.get((head - 1) as usize) else {
6119                break;
6120            };
6121            let parent = cell.parent;
6122            let span_id = cell.span;
6123            head = parent;
6124            if span_id == 0 {
6125                continue;
6126            }
6127            let Some(raw) = self.heap.src_spans.get((span_id - 1) as usize) else {
6128                continue;
6129            };
6130            let Some(name) = self.pool_string(raw.name) else {
6131                continue;
6132            };
6133            if let Some(p) = primary.as_ref() {
6134                // An enclosing construct contains the node's span and is not equal to it.
6135                if name != p.name || raw.start > p.start || raw.end < p.end {
6136                    continue;
6137                }
6138                if raw.start == p.start && raw.end == p.end {
6139                    continue;
6140                }
6141            }
6142            if let Some(last) = out.last() {
6143                if last.name == name && last.start == raw.start && last.end == raw.end {
6144                    continue;
6145                }
6146            }
6147            out.push(PortableSourceSpan {
6148                name,
6149                start: raw.start,
6150                end: raw.end,
6151                role: 1,
6152            });
6153        }
6154        out
6155    }
6156
6157    /// `begin_token_list` hook: a macro body takes the call site span, other lists keep the inherited span.
6158    pub(crate) unsafe fn src_begin_token_list(engine: *mut Self, t: quarterword) {
6159        let Some(engine) = engine.as_mut() else {
6160            return;
6161        };
6162        if !engine.state.source_tracking {
6163            return;
6164        }
6165        if t == TOKEN_TYPE_MACRO {
6166            let call = engine.state.pending_call_span;
6167            if call != 0 {
6168                engine.state.curinput.spanfield = call;
6169            }
6170            engine.state.pending_call_span = 0;
6171            // The macro frame is popped when its body ends, nodes the body allocates record it.
6172            engine.src_stack_push(call, 0, 0, false);
6173        }
6174    }
6175
6176    /// `\futurelet` hook: saves the first peeked token's origin before the second token read overwrites it.
6177    pub(crate) unsafe fn src_capture_tok_span(engine: *mut Self) -> u32 {
6178        engine.as_ref().map_or(0, |engine| engine.state.src_tok_span)
6179    }
6180
6181    /// Restores the origin [`Self::src_capture_tok_span`] saved, before the first token is backed up.
6182    pub(crate) unsafe fn src_restore_tok_span(engine: *mut Self, saved: u32) {
6183        if let Some(engine) = engine.as_mut() {
6184            if engine.state.source_tracking {
6185                engine.state.src_tok_span = saved;
6186            }
6187        }
6188    }
6189
6190    /// `macro_call` entry hook: records the invoking control sequence for the invocation span.
6191    pub(crate) unsafe fn src_macro_begin(engine: *mut Self) {
6192        let Some(engine) = engine.as_mut() else {
6193            return;
6194        };
6195        if !engine.state.source_tracking {
6196            return;
6197        }
6198        let span = engine.state.curinput.spanfield;
6199        engine.state.src_call_span = span;
6200        engine.state.src_call_state = engine.state.curinput.statefield as integer;
6201        engine.state.src_call_index = engine.state.curinput.indexfield as integer;
6202        engine.state.src_call_name = engine.state.curinput.namefield as strnumber;
6203        engine.state.src_call_argspan = 0;
6204        // The user command before the arguments are read, so a construct among them does not become the anchor.
6205        engine.state.src_call_user_span = engine.state.src_user_cmd_span;
6206        engine.state.src_call_start = if span != 0 {
6207            engine
6208                .heap
6209                .src_spans
6210                .get((span - 1) as usize)
6211                .map(|s| s.start)
6212                .unwrap_or(0)
6213        } else {
6214            0
6215        };
6216        // A macro read from a parameter list of the primary input is typed argument content, the anchor command.
6217        let from_argument_or_template = engine.state.src_call_state == TOKEN_LIST_STATE as integer
6218            && engine.state.src_call_index < TOKEN_TYPE_BACKED_UP as integer;
6219        if from_argument_or_template && span != 0 {
6220            if let Some(raw) = engine.heap.src_spans.get((span - 1) as usize).copied() {
6221                if raw.name == engine.state.src_primary_name {
6222                    engine.state.src_anchor_cmd = span;
6223                }
6224            }
6225        }
6226    }
6227
6228    /// Hull in one source of the invoking control sequence and its argument tokens, 0 when nothing is found.
6229    unsafe fn src_arg_hull(self: &mut Self, n: integer) -> SrcId {
6230        let mut lo = u32::MAX;
6231        let mut hi = 0u32;
6232        let mut found = false;
6233        let mut name: strnumber = self.state.src_primary_name;
6234        if self.state.src_call_span != 0 {
6235            if let Some(raw) = self
6236                .heap
6237                .src_spans
6238                .get((self.state.src_call_span - 1) as usize)
6239            {
6240                name = raw.name;
6241                lo = raw.start;
6242                hi = raw.end;
6243                found = true;
6244            }
6245        }
6246        // Arguments before the command start belong to a definition site elsewhere and are left out.
6247        let cmd_start = if found { lo } else { 0 };
6248        let zmem = self.state.zmem;
6249        let lo_b = self.state.memmin;
6250        let hi_b = self.state.memmax;
6251        for i in 0..n.max(0) {
6252            let mut p = self.state.pstack[i as usize];
6253            let mut guard = 0i32;
6254            while p >= lo_b && p <= hi_b && p != TEX_NULL {
6255                guard += 1;
6256                if guard > 100000 {
6257                    break;
6258                }
6259                let id = self.heap.node_src.get_copy(p as usize);
6260                if id != 0 {
6261                    if let Some(raw) = self.heap.src_spans.get((id - 1) as usize) {
6262                        if raw.name == name && raw.start >= cmd_start {
6263                            if found {
6264                                lo = lo.min(raw.start);
6265                                hi = hi.max(raw.end);
6266                            } else {
6267                                lo = raw.start;
6268                                hi = raw.end;
6269                                found = true;
6270                            }
6271                        }
6272                    }
6273                }
6274                p = (*zmem.offset(p as isize)).hh.v.RH;
6275            }
6276        }
6277        // Reaches to the final argument's closing brace, from the last popped argument span or the current one.
6278        for cand in [self.state.src_call_argspan, self.state.curinput.spanfield] {
6279            if cand == 0 {
6280                continue;
6281            }
6282            if let Some(raw) = self.heap.src_spans.get((cand - 1) as usize) {
6283                if raw.name == name && raw.start >= cmd_start {
6284                    if found {
6285                        lo = lo.min(raw.start);
6286                        hi = hi.max(raw.end);
6287                    } else {
6288                        lo = raw.start;
6289                        hi = raw.end;
6290                        found = true;
6291                    }
6292                }
6293            }
6294        }
6295        if found && hi > lo {
6296            self.intern_span_raw(name, lo, hi, 1)
6297        } else {
6298            0
6299        }
6300    }
6301
6302    /// `macro_call` hook before the body starts: sets the call site span the body inherits.
6303    pub(crate) unsafe fn src_macro_set_pending(engine: *mut Self, n: integer) {
6304        let Some(engine) = engine.as_mut() else {
6305            return;
6306        };
6307        if !engine.state.source_tracking {
6308            return;
6309        }
6310        if engine.state.src_call_index == TOKEN_TYPE_PARAMETER as integer && engine.state.src_call_span == 0 {
6311            // A library helper without a span of its own inherits the enclosing user command's span.
6312            engine.state.pending_call_span = 0;
6313        } else if engine.state.src_call_index == TOKEN_TYPE_PARAMETER as integer {
6314            // An argument replay recovers its own hull from its scanned arguments.
6315            let hull = engine.src_arg_hull(n);
6316            engine.state.pending_call_span = if hull != 0 {
6317                hull
6318            } else {
6319                engine.state.src_call_span
6320            };
6321        } else if engine.state.curinput.statefield != TOKEN_LIST_STATE {
6322            // A call read from the file spans from the user command to the end of its arguments.
6323            let end = engine.src_buf_offset(engine.state.curinput.locfield as integer);
6324            let name = engine.state.curinput.namefield as strnumber;
6325            let mut start = engine.state.src_call_start.min(end);
6326            let user = engine.state.src_call_user_span;
6327            if user != 0 {
6328                if let Some(raw) = engine.heap.src_spans.get((user - 1) as usize) {
6329                    if raw.name == name && raw.start <= start {
6330                        start = raw.start;
6331                    }
6332                }
6333            }
6334            let id = engine.intern_span_raw(name, start, end, 1);
6335            engine.state.pending_call_span = id;
6336        } else if n > 0 {
6337            // A macro body call that took arguments joins the inherited span with its own arguments.
6338            let hull = engine.src_arg_hull(n);
6339            engine.state.pending_call_span = if hull != 0 {
6340                hull
6341            } else {
6342                engine.state.src_call_span
6343            };
6344        } else {
6345            engine.state.pending_call_span = engine.state.src_call_span;
6346        }
6347    }
6348
6349    /// Main loop hook: records a span per UTF-16 unit of the character just added to `nativetext`.
6350    pub(crate) unsafe fn src_native_run_push(engine: *mut Self) {
6351        let Some(engine) = engine.as_mut() else {
6352            return;
6353        };
6354        if !engine.state.source_tracking {
6355            return;
6356        }
6357        let nativelen = engine.state.nativelen.max(0) as usize;
6358        // A supplementary scalar takes two UTF-16 units.
6359        let units = if engine.state.curchr as i64 > 65535 { 2 } else { 1 };
6360        let prev = nativelen.saturating_sub(units);
6361        if prev == 0 {
6362            engine.heap.src_native_offsets.clear();
6363        }
6364        if engine.heap.src_native_offsets.len() > prev {
6365            engine.heap.src_native_offsets.truncate(prev);
6366        }
6367        let id = engine.state.curinput.spanfield;
6368        while engine.heap.src_native_offsets.len() < nativelen {
6369            engine.heap.src_native_offsets.push(id);
6370        }
6371    }
6372
6373    /// Maps each shaped glyph to the input span of the characters in its cluster, using the offsets once.
6374    pub(crate) unsafe fn src_resolve_native_glyphs(
6375        engine: *mut Self,
6376        node: halfword,
6377        text: &[u16],
6378        glyphs: &mut [PortableNativeGlyph],
6379    ) {
6380        let Some(engine) = engine.as_mut() else {
6381            return;
6382        };
6383        if !engine.state.source_tracking {
6384            return;
6385        }
6386        let offsets = core::mem::take(&mut engine.heap.src_native_offsets);
6387        // Only a run whose offsets cover exactly this node's text is mapped.
6388        if text.is_empty() || offsets.len() != text.len() {
6389            return;
6390        }
6391        if node < 0 {
6392            return;
6393        }
6394        // Only characters from the node's own source contribute.
6395        let node_id = engine.heap.node_src.get_copy(node as usize);
6396        if node_id == 0 {
6397            return;
6398        }
6399        let node_name = match engine.heap.src_spans.get((node_id - 1) as usize) {
6400            Some(raw) => raw.name,
6401            None => return,
6402        };
6403        // Clusters are UTF-8 byte offsets into the shaped text, so each byte gets its character's span.
6404        let s = String::from_utf16_lossy(text);
6405        let n = s.len();
6406        let mut byte_src = vec![0u32; n];
6407        let mut u16i = 0usize;
6408        for (b, ch) in s.char_indices() {
6409            let id = offsets.get(u16i).copied().unwrap_or(0);
6410            let upper = (b + ch.len_utf8()).min(n);
6411            for slot in byte_src.iter_mut().take(upper).skip(b) {
6412                *slot = id;
6413            }
6414            u16i += ch.len_utf16();
6415        }
6416        for gi in 0..glyphs.len() {
6417            let cs = (glyphs[gi].cluster_start as usize).min(n);
6418            // Clusters rise left to right, a cluster ends where the next greater one starts.
6419            let mut ce = n;
6420            for g2 in glyphs.iter() {
6421                let c2 = (g2.cluster_start as usize).min(n);
6422                if c2 > cs && c2 < ce {
6423                    ce = c2;
6424                }
6425            }
6426            let mut lo = u32::MAX;
6427            let mut hi = 0u32;
6428            let mut found = false;
6429            for &id in byte_src.iter().take(ce).skip(cs) {
6430                if id == 0 {
6431                    continue;
6432                }
6433                let Some(raw) = engine.heap.src_spans.get((id - 1) as usize) else {
6434                    continue;
6435                };
6436                if raw.name != node_name {
6437                    continue;
6438                }
6439                lo = lo.min(raw.start);
6440                hi = hi.max(raw.end);
6441                found = true;
6442            }
6443            if found && hi > lo {
6444                glyphs[gi].src_start = lo;
6445                glyphs[gi].src_end = hi;
6446            }
6447        }
6448    }
6449
6450    /// Every interned span in display form.
6451    pub fn input_source_spans(&self) -> Vec<PortableSourceSpan> {
6452        self.heap
6453            .src_spans
6454            .iter()
6455            .filter_map(|raw| {
6456                let name = self.pool_string(raw.name)?;
6457                Some(PortableSourceSpan {
6458                    name,
6459                    start: raw.start,
6460                    end: raw.end,
6461                    role: raw.role,
6462                })
6463            })
6464            .collect()
6465    }
6466
6467    pub(crate) unsafe fn resolve_font_handle(
6468        engine: *mut PortableTexEngine<'_>,
6469        name: *mut ASCIIcode,
6470        size: integer,
6471    ) -> FontHandle {
6472        let Some(engine) = engine.as_mut() else {
6473            return 0;
6474        };
6475        let name_len = engine.state.namelength.max(0) as usize;
6476        let name = if name.is_null() || name_len == 0 {
6477            &[]
6478        } else {
6479            core::slice::from_raw_parts(name as *const i32, name_len)
6480        };
6481        engine.fonts.resolve_font_handle(name, size).unwrap_or(0)
6482    }
6483
6484    pub(crate) unsafe fn measure_font_metrics(
6485        engine: *mut PortableTexEngine<'_>,
6486        font: FontHandle,
6487        ascent: *mut integer,
6488        descent: *mut integer,
6489        xheight: *mut integer,
6490        capheight: *mut integer,
6491        slant: *mut integer,
6492    ) {
6493        let metrics = engine
6494            .as_mut()
6495            .map(|engine| engine.fonts.font_metrics(font))
6496            .unwrap_or_default();
6497        if !ascent.is_null() {
6498            *ascent = metrics.ascent;
6499        }
6500        if !descent.is_null() {
6501            *descent = metrics.descent;
6502        }
6503        if !xheight.is_null() {
6504            *xheight = metrics.xheight;
6505        }
6506        if !capheight.is_null() {
6507            *capheight = metrics.capheight;
6508        }
6509        if !slant.is_null() {
6510            *slant = metrics.slant;
6511        }
6512    }
6513
6514    pub(crate) unsafe fn get_native_mathsy_parameter(
6515        engine: *mut PortableTexEngine<'resources>,
6516        font: integer,
6517        param: integer,
6518    ) -> integer {
6519        let Some(engine) = engine.as_mut() else {
6520            return 0;
6521        };
6522        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
6523            return 0;
6524        };
6525        engine.fonts.math_symbol_parameter(font_handle, param)
6526    }
6527
6528    pub(crate) unsafe fn get_native_mathex_parameter(
6529        engine: *mut PortableTexEngine<'resources>,
6530        font: integer,
6531        param: integer,
6532    ) -> integer {
6533        let Some(engine) = engine.as_mut() else {
6534            return 0;
6535        };
6536        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
6537            return 0;
6538        };
6539        engine.fonts.math_extension_parameter(font_handle, param)
6540    }
6541
6542    /// XeTeX `get_ot_math_ital_corr`: a MATH italic correction in scaled points.
6543    pub(crate) unsafe fn get_ot_math_ital_corr(
6544        engine: *mut PortableTexEngine<'resources>,
6545        font: integer,
6546        glyph: integer,
6547    ) -> integer {
6548        let Some(engine) = engine.as_mut() else {
6549            return 0;
6550        };
6551        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
6552            return 0;
6553        };
6554        engine.fonts.math_glyph_italic_correction(font_handle, glyph)
6555    }
6556
6557    /// XeTeX `get_ot_math_variant`: the `v`th variant of `g` with its advance in `adv`, which is `-1` when none.
6558    pub(crate) unsafe fn get_ot_math_variant(
6559        engine: *mut PortableTexEngine<'resources>,
6560        f_0: integer,
6561        g_0: integer,
6562        v: integer,
6563        adv: *mut integer,
6564        horiz: integer,
6565    ) -> integer {
6566        if !adv.is_null() {
6567            *adv = -1;
6568        }
6569        let Some(engine) = engine.as_mut() else {
6570            return g_0;
6571        };
6572        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6573            return g_0;
6574        };
6575        let index = u16::try_from(v).unwrap_or(u16::MAX);
6576        match engine
6577            .fonts
6578            .math_glyph_variant(font_handle, g_0, index, horiz != 0)
6579        {
6580            Some(variant) => {
6581                if !adv.is_null() {
6582                    *adv = variant.advance;
6583                }
6584                variant.glyph
6585            }
6586            None => g_0,
6587        }
6588    }
6589
6590    /// XeTeX `get_ot_assembly_ptr`: the assembly of `g` as an owned pointer, null when there is none.
6591    pub(crate) unsafe fn get_ot_assembly_ptr(
6592        engine: *mut PortableTexEngine<'resources>,
6593        f_0: integer,
6594        g_0: integer,
6595        horiz: integer,
6596    ) -> voidpointer {
6597        let Some(engine) = engine.as_mut() else {
6598            return nullptr;
6599        };
6600        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6601            return nullptr;
6602        };
6603        let parts = engine
6604            .fonts
6605            .math_glyph_assembly(font_handle, g_0, horiz != 0);
6606        if parts.is_empty() {
6607            return nullptr;
6608        }
6609        let assembly = Box::new(GlyphAssembly { parts });
6610        Box::into_raw(assembly) as voidpointer
6611    }
6612
6613    /// XeTeX `ot_min_connector_overlap` for font `f`, in scaled points.
6614    pub(crate) unsafe fn ot_min_connector_overlap(
6615        engine: *mut PortableTexEngine<'resources>,
6616        f_0: integer,
6617    ) -> integer {
6618        let Some(engine) = engine.as_mut() else {
6619            return 0;
6620        };
6621        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6622            return 0;
6623        };
6624        engine.fonts.math_min_connector_overlap(font_handle)
6625    }
6626
6627    /// Control box of glyph `glyph` in native font `f`, all zero when `f` is not a native font.
6628    fn native_glyph_bounds(this: &mut Self, f: integer, glyph: integer) -> PortableGlyphBounds {
6629        match (Self::font_handle_for_number(this, f), u16::try_from(glyph)) {
6630            (Some(handle), Ok(glyph)) => this.fonts.glyph_bounds(handle, glyph),
6631            _ => PortableGlyphBounds::default(),
6632        }
6633    }
6634
6635    /// Control box of the glyph native font `f` maps character `c` to.
6636    fn native_char_bounds(this: &mut Self, f: integer, c: integer) -> PortableGlyphBounds {
6637        let Some(handle) = Self::font_handle_for_number(this, f) else {
6638            return PortableGlyphBounds::default();
6639        };
6640        let glyph = this.fonts.map_char_to_glyph(handle, c);
6641        Self::native_glyph_bounds(this, f, glyph)
6642    }
6643
6644    /// Font dimension `n` of font `f`, zero outside the font memory.
6645    fn font_dimen(this: &Self, f: integer, n: integer) -> scaled {
6646        usize::try_from(f)
6647            .ok()
6648            .and_then(|f| this.heap.parambase_storage.get(f))
6649            .and_then(|&base| usize::try_from(base.checked_add(n)?).ok())
6650            .and_then(|index| this.heap.fontinfo_storage.get(index))
6651            .map_or(0, |word| unsafe { word.u.CINT })
6652    }
6653
6654    /// `\XeTeXletterspace` of font `f`, which XeTeX adds to every native italic correction.
6655    fn font_letter_space(this: &Self, f: integer) -> scaled {
6656        usize::try_from(f)
6657            .ok()
6658            .and_then(|f| this.heap.fontletterspace_storage.get(f))
6659            .copied()
6660            .unwrap_or(0)
6661    }
6662
6663    /// XeTeX `D2Fix`: points to scaled points, truncating after adding half a unit.
6664    fn d2fix(points: f32) -> scaled {
6665        (f64::from(points) * 65536.0 + 0.5) as scaled
6666    }
6667
6668    /// XeTeX `getGlyphItalCorr`: how far the control box reaches right of the advance.
6669    fn glyph_italic_correction(bounds: PortableGlyphBounds) -> f32 {
6670        if bounds.x_max > bounds.advance {
6671            bounds.x_max - bounds.advance
6672        } else {
6673            0.0
6674        }
6675    }
6676
6677    /// Left and right side bearings, the right one measured inward from the advance.
6678    fn side_bearings(bounds: PortableGlyphBounds) -> (scaled, scaled) {
6679        (Self::d2fix(bounds.x_min), Self::d2fix(bounds.advance - bounds.x_max))
6680    }
6681
6682    /// XeTeX `getnativecharheightdepth`, snapping to the baseline, x height and cap height within a 25th quad.
6683    fn snapped_char_height_depth(this: &mut Self, f: integer, c: integer) -> (scaled, scaled) {
6684        let bounds = Self::native_char_bounds(this, f, c);
6685        let mut height = Self::d2fix(bounds.y_max);
6686        let mut depth = Self::d2fix(-bounds.y_min);
6687        let fuzz = Self::font_dimen(this, f, QUAD_CODE) / 25;
6688        let snap = |value: &mut scaled, target: scaled| {
6689            if (*value - target).abs() <= fuzz {
6690                *value = target;
6691            }
6692        };
6693        snap(&mut depth, 0);
6694        snap(&mut height, 0);
6695        snap(&mut height, Self::font_dimen(this, f, X_HEIGHT_CODE));
6696        snap(&mut height, Self::font_dimen(this, f, CAP_HEIGHT_CODE));
6697        (height, depth)
6698    }
6699
6700    /// XeTeX `getnativecharwd`.
6701    pub(crate) unsafe fn native_char_width(engine: *mut Self, f: integer, c: integer) -> scaled {
6702        engine.as_mut().map_or(0, |this| Self::d2fix(Self::native_char_bounds(this, f, c).advance))
6703    }
6704
6705    /// XeTeX `getnativecharht`.
6706    pub(crate) unsafe fn native_char_height(engine: *mut Self, f: integer, c: integer) -> scaled {
6707        engine.as_mut().map_or(0, |this| Self::snapped_char_height_depth(this, f, c).0)
6708    }
6709
6710    /// XeTeX `getnativechardp`.
6711    pub(crate) unsafe fn native_char_depth(engine: *mut Self, f: integer, c: integer) -> scaled {
6712        engine.as_mut().map_or(0, |this| Self::snapped_char_height_depth(this, f, c).1)
6713    }
6714
6715    /// XeTeX `getnativecharheightdepth`.
6716    pub(crate) unsafe fn native_char_height_depth(
6717        engine: *mut Self,
6718        f: integer,
6719        c: integer,
6720        height: *mut scaled,
6721        depth: *mut scaled,
6722    ) {
6723        let (h, d) = engine
6724            .as_mut()
6725            .map_or((0, 0), |this| Self::snapped_char_height_depth(this, f, c));
6726        if !height.is_null() {
6727            *height = h;
6728        }
6729        if !depth.is_null() {
6730            *depth = d;
6731        }
6732    }
6733
6734    /// XeTeX `getnativecharsidebearings`.
6735    pub(crate) unsafe fn native_char_side_bearings(
6736        engine: *mut Self,
6737        f: integer,
6738        c: integer,
6739        lsb: *mut scaled,
6740        rsb: *mut scaled,
6741    ) {
6742        let (left, right) = engine
6743            .as_mut()
6744            .map_or((0, 0), |this| Self::side_bearings(Self::native_char_bounds(this, f, c)));
6745        if !lsb.is_null() {
6746            *lsb = left;
6747        }
6748        if !rsb.is_null() {
6749            *rsb = right;
6750        }
6751    }
6752
6753    /// XeTeX `getnativecharic`: the overhang past the advance plus the letter spacing.
6754    pub(crate) unsafe fn native_char_italic_correction(engine: *mut Self, f: integer, c: integer) -> scaled {
6755        let Some(this) = engine.as_mut() else {
6756            return 0;
6757        };
6758        let (_, rsb) = Self::side_bearings(Self::native_char_bounds(this, f, c));
6759        Self::font_letter_space(this, f) - rsb.min(0)
6760    }
6761
6762    /// XeTeX `getglyphbounds`: edges 1 to 4 are the left bearing, height, right bearing and depth of glyph `gid`.
6763    pub(crate) unsafe fn glyph_bound(engine: *mut Self, f: integer, edge: integer, gid: integer) -> scaled {
6764        let Some(this) = engine.as_mut() else {
6765            return 0;
6766        };
6767        let bounds = Self::native_glyph_bounds(this, f, gid);
6768        let (a, b) = if edge & 1 != 0 {
6769            (bounds.x_min, bounds.advance - bounds.x_max)
6770        } else {
6771            (bounds.y_max, -bounds.y_min)
6772        };
6773        Self::d2fix(if edge <= 2 { a } else { b })
6774    }
6775
6776    /// XeTeX `getfontcharrange`: the first or last character code native font `f` maps.
6777    pub(crate) unsafe fn font_char_range(engine: *mut Self, f: integer, first: integer) -> integer {
6778        let Some(this) = engine.as_mut() else {
6779            return 0;
6780        };
6781        let Some(handle) = Self::font_handle_for_number(this, f) else {
6782            return 0;
6783        };
6784        this.fonts
6785            .char_code_range(handle)
6786            .map_or(0, |(low, high)| if first != 0 { low } else { high })
6787    }
6788
6789    /// XeTeX `get_native_italic_correction`: italic correction of a native word's last glyph plus letter spacing.
6790    pub(crate) unsafe fn native_word_italic_correction(engine: *mut Self, node: voidpointer) -> scaled {
6791        let Some(this) = engine.as_mut() else {
6792            return 0;
6793        };
6794        let Some(node) = Self::node_index_for_pointer(this, node) else {
6795            return 0;
6796        };
6797        let Some(last) = this.native_glyph_infos.get(&node).and_then(|info| info.glyphs.last()) else {
6798            return 0;
6799        };
6800        let glyph = integer::from(last.glyph_id);
6801        let font = Self::native_node_font(this.state.zmem, node);
6802        let bounds = Self::native_glyph_bounds(this, font, glyph);
6803        Self::d2fix(Self::glyph_italic_correction(bounds)) + Self::font_letter_space(this, font)
6804    }
6805
6806    /// XeTeX `get_native_glyph_italic_correction`.
6807    pub(crate) unsafe fn native_glyph_italic_correction(engine: *mut Self, node: voidpointer) -> scaled {
6808        let Some(this) = engine.as_mut() else {
6809            return 0;
6810        };
6811        let Some(node) = Self::node_index_for_pointer(this, node) else {
6812            return 0;
6813        };
6814        let mem = this.state.zmem;
6815        let font = Self::native_node_font(mem, node);
6816        let glyph = (*mem.offset((node + 4) as isize)).v.QQQQ.u.B2 as integer;
6817        Self::d2fix(Self::glyph_italic_correction(Self::native_glyph_bounds(this, font, glyph)))
6818    }
6819
6820    /// Height of a math glyph in points, XeTeXOTMath.cpp `glyph_height` takes it unrounded from the control box.
6821    fn math_glyph_height(engine: &mut PortableTexEngine<'resources>, f_0: integer, g_0: integer) -> f32 {
6822        Self::native_glyph_bounds(engine, f_0, g_0).y_max
6823    }
6824
6825    /// Depth of a math glyph in points, XeTeXOTMath.cpp `glyph_depth`.
6826    fn math_glyph_depth(engine: &mut PortableTexEngine<'resources>, f_0: integer, g_0: integer) -> f32 {
6827        -Self::native_glyph_bounds(engine, f_0, g_0).y_min
6828    }
6829
6830    /// One MATH kern corner of glyph `g` at a height in design units, in design units.
6831    unsafe fn math_kern_at(
6832        engine: &mut PortableTexEngine<'resources>,
6833        f_0: integer,
6834        g_0: integer,
6835        height: integer,
6836        corner: PortableMathKernCorner,
6837    ) -> integer {
6838        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6839            return 0;
6840        };
6841        engine.fonts.math_kern_at(font_handle, g_0, corner, height)
6842    }
6843
6844    /// XeTeX `get_ot_math_kern`: the cut in kern between base glyph `g` and script glyph `sg`, in scaled points.
6845    pub(crate) unsafe fn get_ot_math_kern(
6846        engine: *mut PortableTexEngine<'resources>,
6847        f_0: integer,
6848        g_0: integer,
6849        sf: integer,
6850        sg: integer,
6851        cmd: integer,
6852        shift_scaled: integer,
6853    ) -> integer {
6854        const SUP_CMD: integer = 0;
6855        const SUB_CMD: integer = 1;
6856        let Some(engine) = engine.as_mut() else {
6857            return 0;
6858        };
6859        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6860            return 0;
6861        };
6862        let Some(sfont_handle) = Self::font_handle_for_number(engine, sf) else {
6863            return 0;
6864        };
6865
6866        let g_height_pt = Self::math_glyph_height(engine, f_0, g_0);
6867        let g_depth_pt = Self::math_glyph_depth(engine, f_0, g_0);
6868        let sg_height_pt = Self::math_glyph_height(engine, sf, sg);
6869        let sg_depth_pt = Self::math_glyph_depth(engine, sf, sg);
6870
6871        // XeTeXOTMath.cpp works in design units of the base glyph's font.
6872        let g_height = engine.fonts.math_points_to_units(font_handle, g_height_pt) as integer;
6873        let g_depth = engine.fonts.math_points_to_units(font_handle, g_depth_pt) as integer;
6874        let sg_height = engine
6875            .fonts
6876            .math_points_to_units(sfont_handle, sg_height_pt) as integer;
6877        let sg_depth = engine
6878            .fonts
6879            .math_points_to_units(sfont_handle, sg_depth_pt) as integer;
6880        // `Fix2D` divides in double before `pointsToUnits` narrows to float.
6881        let shift_pt = (f64::from(shift_scaled) / 65536.0) as f32;
6882        let shift = engine.fonts.math_points_to_units(font_handle, shift_pt) as integer;
6883
6884        let f_size = engine.fonts.math_point_size(font_handle);
6885        let sf_size = engine.fonts.math_point_size(sfont_handle);
6886        if f_size == 0.0 {
6887            return 0;
6888        }
6889        let scale_factor = sf_size / f_size;
6890
6891        // Mixed int and float expressions evaluate in `f32` and truncate once, as C does in XeTeXOTMath.cpp.
6892        let truncate = |value: f32| value as integer;
6893        let mut rval: integer;
6894        if cmd == SUP_CMD {
6895            let kern = Self::math_kern_at(
6896                engine,
6897                f_0,
6898                g_0,
6899                truncate(shift as f32 - scale_factor * sg_depth as f32),
6900                PortableMathKernCorner::TopRight,
6901            );
6902            let skern =
6903                Self::math_kern_at(engine, sf, sg, -sg_depth, PortableMathKernCorner::BottomLeft);
6904            let top_kern = truncate(kern as f32 + scale_factor * skern as f32);
6905
6906            let kern =
6907                Self::math_kern_at(engine, f_0, g_0, g_height, PortableMathKernCorner::TopRight);
6908            let skern = Self::math_kern_at(
6909                engine,
6910                sf,
6911                sg,
6912                truncate((g_height - shift) as f32 / scale_factor),
6913                PortableMathKernCorner::BottomLeft,
6914            );
6915            let bot_kern = truncate(kern as f32 + scale_factor * skern as f32);
6916
6917            rval = if top_kern > bot_kern { top_kern } else { bot_kern };
6918        } else if cmd == SUB_CMD {
6919            let kern = Self::math_kern_at(
6920                engine,
6921                f_0,
6922                g_0,
6923                truncate(scale_factor * sg_height as f32 - shift as f32),
6924                PortableMathKernCorner::BottomRight,
6925            );
6926            let skern =
6927                Self::math_kern_at(engine, sf, sg, sg_height, PortableMathKernCorner::TopLeft);
6928            let top_kern = truncate(kern as f32 + scale_factor * skern as f32);
6929
6930            let kern =
6931                Self::math_kern_at(engine, f_0, g_0, -g_depth, PortableMathKernCorner::BottomRight);
6932            let skern = Self::math_kern_at(
6933                engine,
6934                sf,
6935                sg,
6936                truncate((shift - g_depth) as f32 / scale_factor),
6937                PortableMathKernCorner::TopLeft,
6938            );
6939            let bot_kern = truncate(kern as f32 + scale_factor * skern as f32);
6940
6941            rval = if top_kern > bot_kern { top_kern } else { bot_kern };
6942        } else {
6943            return 0;
6944        }
6945
6946        rval = engine.fonts.math_units_to_scaled(font_handle, rval);
6947        rval
6948    }
6949
6950    pub(crate) unsafe fn get_native_word_cp(
6951        engine: *mut PortableTexEngine<'resources>,
6952        node: voidpointer,
6953        side: integer,
6954    ) -> integer {
6955        let Some(engine) = engine.as_mut() else {
6956            return 0;
6957        };
6958        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
6959            return 0;
6960        };
6961        let Some(info) = engine.native_glyph_infos.get(&node_index) else {
6962            return 0;
6963        };
6964        let glyph = if side == 0 {
6965            info.glyphs.first()
6966        } else {
6967            info.glyphs.last()
6968        };
6969        let Some(glyph) = glyph else {
6970            return 0;
6971        };
6972        let font = Self::native_node_font(engine.state.zmem, node_index);
6973        Self::character_protrusion(engine, font, u32::from(glyph.glyph_id), side)
6974    }
6975
6976    pub(crate) unsafe fn get_native_glyph(
6977        engine: *mut PortableTexEngine<'resources>,
6978        node: voidpointer,
6979        index: u32,
6980    ) -> uint16_t {
6981        let Some(engine) = engine.as_mut() else {
6982            return 0;
6983        };
6984        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
6985            return 0;
6986        };
6987        engine
6988            .native_glyph_infos
6989            .get(&node_index)
6990            .and_then(|info| info.glyphs.get(index as usize))
6991            .map_or(0, |glyph| glyph.glyph_id)
6992    }
6993
6994    pub(crate) unsafe fn get_character_protrusion(
6995        engine: *mut PortableTexEngine<'_>,
6996        font: integer,
6997        code: u32,
6998        side: integer,
6999    ) -> integer {
7000        engine
7001            .as_mut()
7002            .map_or(0, |engine| Self::character_protrusion(engine, font, code, side))
7003    }
7004
7005    pub(crate) fn character_protrusion(
7006        engine: &PortableTexEngine<'_>,
7007        font: integer,
7008        code: u32,
7009        side: integer,
7010    ) -> integer {
7011        engine
7012            .character_protrusions
7013            .get(&(font, code, side))
7014            .copied()
7015            .unwrap_or(0)
7016    }
7017
7018    pub(crate) unsafe fn set_character_protrusion(
7019        engine: *mut PortableTexEngine<'_>,
7020        font: integer,
7021        code: u32,
7022        side: integer,
7023        value: integer,
7024    ) {
7025        let Some(engine) = engine.as_mut() else {
7026            return;
7027        };
7028        let key = (font, code, side);
7029        if value == 0 {
7030            engine.character_protrusions.remove(&key);
7031        } else {
7032            engine.character_protrusions.insert(key, value);
7033        }
7034    }
7035
7036    pub(crate) unsafe fn get_opentype_math_constant(
7037        engine: *mut PortableTexEngine<'resources>,
7038        font: integer,
7039        constant: integer,
7040    ) -> integer {
7041        let Some(engine) = engine.as_mut() else {
7042            return 0;
7043        };
7044        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7045            return 0;
7046        };
7047        engine.fonts.opentype_math_constant(font_handle, constant)
7048    }
7049
7050    pub(crate) unsafe fn get_opentype_math_accent_position(
7051        engine: *mut PortableTexEngine<'resources>,
7052        font: integer,
7053        glyph: integer,
7054    ) -> integer {
7055        let Some(engine) = engine.as_mut() else {
7056            return 0;
7057        };
7058        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7059            return 0;
7060        };
7061        engine.fonts.opentype_math_accent_position(font_handle, glyph)
7062    }
7063
7064    pub(crate) unsafe fn map_char_to_glyph(
7065        engine: *mut PortableTexEngine<'resources>,
7066        font: integer,
7067        ch: integer,
7068    ) -> integer {
7069        let Some(engine) = engine.as_mut() else {
7070            return 0;
7071        };
7072        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7073            return 0;
7074        };
7075        engine.fonts.map_char_to_glyph(font_handle, ch)
7076    }
7077
7078    pub(crate) unsafe fn map_glyph_to_index(
7079        engine: *mut PortableTexEngine<'resources>,
7080        font: integer,
7081    ) -> integer {
7082        let Some(engine) = engine.as_mut() else {
7083            return 0;
7084        };
7085        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7086            return 0;
7087        };
7088        let Some(name) = engine.pool_string(engine.state.curname) else {
7089            return 0;
7090        };
7091        engine.fonts.map_glyph_to_index(font_handle, name.as_str())
7092    }
7093
7094    /// XeTeX `ot_font_get` for the `\XeTeXOT` and `\XeTeXcountglyphs` primitives.
7095    pub(crate) unsafe fn ot_font_get(
7096        engine: *mut PortableTexEngine<'resources>,
7097        what: integer,
7098        font: integer,
7099        param1: integer,
7100        param2: integer,
7101        param3: integer,
7102    ) -> integer {
7103        let Some(engine) = engine.as_mut() else {
7104            return 0;
7105        };
7106        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7107            return 0;
7108        };
7109        engine
7110            .fonts
7111            .ot_font_get(font_handle, what, param1, param2, param3)
7112    }
7113
7114    pub(crate) unsafe fn is_opentype_math_font(
7115        engine: *mut PortableTexEngine<'_>,
7116        font: FontHandle,
7117    ) -> boolean {
7118        engine
7119            .as_mut()
7120            .map(|engine| engine.fonts.is_opentype_math_font(font) as boolean)
7121            .unwrap_or(false_0)
7122    }
7123
7124    pub(crate) unsafe fn using_opentype(
7125        engine: *mut PortableTexEngine<'_>,
7126        font: FontHandle,
7127    ) -> boolean {
7128        engine
7129            .as_mut()
7130            .map(|engine| engine.fonts.using_opentype(font) as boolean)
7131            .unwrap_or(false_0)
7132    }
7133
7134    pub(crate) unsafe fn release_font_engine(
7135        engine: *mut PortableTexEngine<'_>,
7136        font: FontHandle,
7137        type_flag: integer,
7138    ) {
7139        if let Some(engine) = engine.as_mut() {
7140            engine.fonts.release_font_handle(font, type_flag);
7141        }
7142    }
7143
7144    pub(crate) unsafe fn measure_opentype_font_metrics(
7145        engine: *mut PortableTexEngine<'_>,
7146        font: FontHandle,
7147        ascent: *mut integer,
7148        descent: *mut integer,
7149        xheight: *mut integer,
7150        capheight: *mut integer,
7151        slant: *mut integer,
7152    ) {
7153        let metrics = engine
7154            .as_mut()
7155            .map(|engine| engine.fonts.opentype_font_metrics(font))
7156            .unwrap_or_default();
7157        if !ascent.is_null() {
7158            *ascent = metrics.ascent;
7159        }
7160        if !descent.is_null() {
7161            *descent = metrics.descent;
7162        }
7163        if !xheight.is_null() {
7164            *xheight = metrics.xheight;
7165        }
7166        if !capheight.is_null() {
7167            *capheight = metrics.capheight;
7168        }
7169        if !slant.is_null() {
7170            *slant = metrics.slant;
7171        }
7172    }
7173
7174    pub(crate) unsafe fn measure_native_node(
7175        engine: *mut PortableTexEngine<'resources>,
7176        node: voidpointer,
7177        use_glyph_metrics: integer,
7178    ) {
7179        let Some(engine) = engine.as_mut() else {
7180            return;
7181        };
7182        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7183            return;
7184        };
7185        let mem = engine.state.zmem;
7186        let font_number = Self::native_node_font(mem, node_index);
7187        let Some(font_handle) = Self::font_handle_for_number(engine, font_number) else {
7188            return;
7189        };
7190        let text = Self::native_node_text(mem, node_index);
7191        let mut metrics =
7192            engine
7193                .fonts
7194                .shape_native_text(font_handle, text, use_glyph_metrics != 0);
7195        Self::src_resolve_native_glyphs(
7196            engine as *mut PortableTexEngine<'resources>,
7197            node_index,
7198            text,
7199            metrics.glyphs.as_mut_slice(),
7200        );
7201        Self::write_native_node_metrics(
7202            mem,
7203            node_index,
7204            metrics.width,
7205            metrics.height,
7206            metrics.depth,
7207        );
7208        (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B3 =
7209            (metrics.glyphs.len().min(i32::MAX as usize) as quarterword) as u16;
7210        (*mem.offset((node_index + 5) as isize)).raw = 0;
7211        engine.native_glyph_infos.insert(
7212            node_index,
7213            PortableNativeGlyphInfo {
7214                glyphs: metrics.glyphs,
7215            },
7216        );
7217    }
7218
7219    pub(crate) unsafe fn measure_native_glyph(
7220        engine: *mut PortableTexEngine<'resources>,
7221        node: voidpointer,
7222        use_glyph_metrics: integer,
7223    ) {
7224        let Some(engine) = engine.as_mut() else {
7225            return;
7226        };
7227        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7228            return;
7229        };
7230        let mem = engine.state.zmem;
7231        let font_number = Self::native_node_font(mem, node_index);
7232        let Some(font_handle) = Self::font_handle_for_number(engine, font_number) else {
7233            return;
7234        };
7235        let glyph = (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B2 as u16;
7236        let mut metrics = engine
7237            .fonts
7238            .measure_native_glyph(font_handle, glyph, use_glyph_metrics != 0);
7239        // Without `\XeTeXuseglyphmetrics` XeTeX gives every glyph the font's ascent and descent.
7240        if use_glyph_metrics == 0 {
7241            let font = usize::try_from(font_number).unwrap_or(usize::MAX);
7242            metrics.height = engine.heap.heightbase_storage.get(font).copied().unwrap_or(0);
7243            metrics.depth = engine.heap.depthbase_storage.get(font).copied().unwrap_or(0);
7244        }
7245        Self::write_native_node_metrics(
7246            mem,
7247            node_index,
7248            metrics.width,
7249            metrics.height,
7250            metrics.depth,
7251        );
7252        (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B3 = (1 as quarterword) as u16;
7253        // XeTeX's glyph info pointer would sit at word 5, one past this five word node, the side table holds it.
7254        engine.native_glyph_infos.insert(
7255            node_index,
7256            PortableNativeGlyphInfo {
7257                glyphs: Vec::from([PortableNativeGlyph {
7258                    glyph_id: glyph,
7259                    x: 0,
7260                    y: 0,
7261                    advance: metrics.width,
7262                    cluster_start: 0,
7263                    cluster_end: 0,
7264                    src_start: 0,
7265                    src_end: 0,
7266                }]),
7267            },
7268        );
7269    }
7270
7271    pub(crate) unsafe fn znotaatfonterror(
7272        self: &mut Self,
7273        cmd: integer,
7274        c_0: integer,
7275        f_0: integer,
7276    ) -> EngineFlow<()> {
7277        self.znototfonterror(cmd, c_0, f_0)?;
7278        Ok(())
7279    }
7280
7281    pub(crate) unsafe fn znotaatgrfonterror(
7282        self: &mut Self,
7283        cmd: integer,
7284        c_0: integer,
7285        f_0: integer,
7286    ) -> EngineFlow<()> {
7287        self.znototfonterror(cmd, c_0, f_0)?;
7288        Ok(())
7289    }
7290
7291    /// XeTeX `stack_glyph_into_box`: appends a measured glyph node to box `b` and grows the box.
7292    unsafe fn stack_glyph_into_box(
7293        self: &mut Self,
7294        b: halfword,
7295        f_0: internalfontnumber,
7296        g_0: integer,
7297    ) -> EngineFlow<()> {
7298        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7299        let p = (&mut *(self as *mut PortableTexEngine<'_>)).zgetnode(5)?;
7300        (*mem.offset(p as isize)).hh.u.B0 = WHATSIT_NODE as i16;
7301        (*mem.offset(p as isize)).hh.u.B1 = GLYPH_NODE as i16;
7302        (*mem.offset((p + 4) as isize)).v.QQQQ.u.B1 = (f_0 as quarterword) as u16;
7303        (*mem.offset((p + 4) as isize)).v.QQQQ.u.B2 = (g_0 as quarterword) as u16;
7304        Self::measure_native_glyph(
7305            self as *mut PortableTexEngine<'resources>,
7306            mem.offset(p as isize) as *mut memoryword as *mut (),
7307            1,
7308        );
7309        Ok(
7310            if (*mem.offset(b as isize)).hh.u.B0 as i32 == HLIST_NODE {
7311                let mut q = (*mem.offset((b + 5) as isize)).hh.v.RH;
7312                if q == TEX_NULL {
7313                    (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7314                } else {
7315                    while (*mem.offset(q as isize)).hh.v.RH != TEX_NULL {
7316                        q = (*mem.offset(q as isize)).hh.v.RH;
7317                    }
7318                    (*mem.offset(q as isize)).hh.v.RH = p;
7319                    if (*mem.offset((b + 3) as isize)).u.CINT
7320                        < (*mem.offset((p + 3) as isize)).u.CINT
7321                    {
7322                        (*mem.offset((b + 3) as isize)).u.CINT = (*mem
7323                            .offset((p + 3) as isize))
7324                            .u
7325                            .CINT;
7326                    }
7327                    if (*mem.offset((b + 2) as isize)).u.CINT
7328                        < (*mem.offset((p + 2) as isize)).u.CINT
7329                    {
7330                        (*mem.offset((b + 2) as isize)).u.CINT = (*mem
7331                            .offset((p + 2) as isize))
7332                            .u
7333                            .CINT;
7334                    }
7335                }
7336            } else {
7337                (*mem.offset(p as isize)).hh.v.RH = (*mem.offset((b + 5) as isize))
7338                    .hh
7339                    .v
7340                    .RH;
7341                (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7342                (*mem.offset((b + 3) as isize)).u.CINT = (*mem.offset((p + 3) as isize))
7343                    .u
7344                    .CINT;
7345                if (*mem.offset((b + 1) as isize)).u.CINT
7346                    < (*mem.offset((p + 1) as isize)).u.CINT
7347                {
7348                    (*mem.offset((b + 1) as isize)).u.CINT = (*mem
7349                        .offset((p + 1) as isize))
7350                        .u
7351                        .CINT;
7352                }
7353            },
7354        )
7355    }
7356
7357    /// XeTeX `stack_glue_into_box`: appends glue of width `min` stretching to `max` to box `b`.
7358    unsafe fn stack_glue_into_box(
7359        self: &mut Self,
7360        b: halfword,
7361        min: scaled,
7362        max: scaled,
7363    ) -> EngineFlow<()> {
7364        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7365        const ZERO_GLUE: halfword = 0;
7366        let q = (&mut *(self as *mut PortableTexEngine<'_>)).znewspec(ZERO_GLUE)?;
7367        (*mem.offset((q + 1) as isize)).u.CINT = min;
7368        (*mem.offset((q + 2) as isize)).u.CINT = max - min;
7369        let p = (&mut *(self as *mut PortableTexEngine<'_>)).znewglue(q)?;
7370        Ok(
7371            if (*mem.offset(b as isize)).hh.u.B0 as i32 == HLIST_NODE {
7372                let mut r = (*mem.offset((b + 5) as isize)).hh.v.RH;
7373                if r == TEX_NULL {
7374                    (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7375                } else {
7376                    while (*mem.offset(r as isize)).hh.v.RH != TEX_NULL {
7377                        r = (*mem.offset(r as isize)).hh.v.RH;
7378                    }
7379                    (*mem.offset(r as isize)).hh.v.RH = p;
7380                }
7381            } else {
7382                (*mem.offset(p as isize)).hh.v.RH = (*mem.offset((b + 5) as isize))
7383                    .hh
7384                    .v
7385                    .RH;
7386                (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7387                (*mem.offset((b + 3) as isize)).u.CINT = (*mem.offset((p + 3) as isize))
7388                    .u
7389                    .CINT;
7390                (*mem.offset((b + 1) as isize)).u.CINT = (*mem.offset((p + 1) as isize))
7391                    .u
7392                    .CINT;
7393            },
7394        )
7395    }
7396
7397    /// XeTeX `build_opentype_assembly`: a box at least `s` long stacking `assembly` parts with overlap glue.
7398    pub(crate) unsafe fn zbuildopentypeassembly(
7399        self: &mut Self,
7400        f_0: internalfontnumber,
7401        assembly: voidpointer,
7402        s: scaled,
7403        horiz_flag: integer,
7404    ) -> EngineFlow<halfword> {
7405        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7406        let horiz = horiz_flag != 0;
7407        let b = (&mut *(self as *mut PortableTexEngine<'_>)).newnullbox()?;
7408        (*mem.offset(b as isize)).hh.u.B0 = if horiz { HLIST_NODE } else { VLIST_NODE }
7409            as i16;
7410        let parts: &[PortableMathAssemblyPart] = if assembly.is_null() {
7411            &[]
7412        } else {
7413            &(*(assembly as *const GlyphAssembly)).parts
7414        };
7415        let min_o = Self::ot_min_connector_overlap(
7416            self as *mut PortableTexEngine<'resources>,
7417            f_0 as i32,
7418        );
7419        let mut n: integer = -1;
7420        let mut no_extenders = true;
7421        loop {
7422            n += 1;
7423            let mut s_max: scaled = 0;
7424            let mut prev_o: scaled = 0;
7425            for part in parts.iter() {
7426                if part.extender {
7427                    no_extenders = false;
7428                    for _ in 0..n {
7429                        let mut o = part.start_connector;
7430                        if min_o < o {
7431                            o = min_o;
7432                        }
7433                        if prev_o < o {
7434                            o = prev_o;
7435                        }
7436                        s_max = s_max - o + part.full_advance;
7437                        prev_o = part.end_connector;
7438                    }
7439                } else {
7440                    let mut o = part.start_connector;
7441                    if min_o < o {
7442                        o = min_o;
7443                    }
7444                    if prev_o < o {
7445                        o = prev_o;
7446                    }
7447                    s_max = s_max - o + part.full_advance;
7448                    prev_o = part.end_connector;
7449                }
7450            }
7451            if s_max >= s || no_extenders {
7452                break;
7453            }
7454        }
7455        let mut prev_o: scaled = 0;
7456        for &part in parts {
7457            let reps = if part.extender { n } else { 1 };
7458            for _ in 0..reps {
7459                let mut o = part.start_connector;
7460                if prev_o < o {
7461                    o = prev_o;
7462                }
7463                let oo = o;
7464                if min_o < o {
7465                    o = min_o;
7466                }
7467                if oo > 0 {
7468                    (&mut *(self as *mut PortableTexEngine<'_>))
7469                        .stack_glue_into_box(b, -oo, -o)?;
7470                }
7471                let g = part.glyph;
7472                (&mut *(self as *mut PortableTexEngine<'_>))
7473                    .stack_glyph_into_box(b, f_0, g)?;
7474                prev_o = part.end_connector;
7475            }
7476        }
7477        let mut p = (*mem.offset((b + 5) as isize)).hh.v.RH;
7478        let mut nat: scaled = 0;
7479        let mut str_: scaled = 0;
7480        while p != TEX_NULL {
7481            let ty = (*mem.offset(p as isize)).hh.u.B0 as i32;
7482            if ty == WHATSIT_NODE {
7483                if horiz {
7484                    nat += (*mem.offset((p + 1) as isize)).u.CINT;
7485                } else {
7486                    nat
7487                        += (*mem.offset((p + 3) as isize)).u.CINT
7488                            + (*mem.offset((p + 2) as isize)).u.CINT;
7489                }
7490            } else if ty == GLUE_NODE {
7491                let spec = (*mem.offset((p + 1) as isize)).hh.v.LH;
7492                nat += (*mem.offset((spec + 1) as isize)).u.CINT;
7493                str_ += (*mem.offset((spec + 2) as isize)).u.CINT;
7494            }
7495            p = (*mem.offset(p as isize)).hh.v.RH;
7496        }
7497        if s > nat && str_ > 0 {
7498            let mut o = s - nat;
7499            if o > str_ {
7500                o = str_;
7501            }
7502            (*mem.offset((b + 5) as isize)).hh.u.B1 = GLUE_ORDER_NORMAL;
7503            (*mem.offset((b + 5) as isize)).hh.u.B0 = GLUE_SIGN_STRETCHING;
7504            (*mem.offset((b + 6) as isize)).gr = o as f64 / str_ as f64;
7505            let stretched = nat
7506                + (str_ as f64 * (*mem.offset((b + 6) as isize)).gr).round() as scaled;
7507            if horiz {
7508                (*mem.offset((b + 1) as isize)).u.CINT = stretched;
7509            } else {
7510                (*mem.offset((b + 3) as isize)).u.CINT = stretched;
7511            }
7512        } else if horiz {
7513            (*mem.offset((b + 1) as isize)).u.CINT = nat;
7514        } else {
7515            (*mem.offset((b + 3) as isize)).u.CINT = nat;
7516        }
7517        Ok(b)
7518    }
7519
7520}
7521
7522pub(crate) unsafe fn fputs(_text: const_string, _file: NativeFileHandle) -> i32 {
7523    0
7524}
7525
7526pub(crate) unsafe fn getcreationdate() {}
7527
7528pub(crate) unsafe fn getfilemoddate(_s: integer) {}
7529
7530pub(crate) unsafe fn getfiledump(_s: integer, _offset: i32, _length: i32) {}
7531
7532pub(crate) unsafe fn getmd5sum(_s: integer, _file: i32) {}
7533
7534pub(crate) unsafe fn u_close_file_or_pipe(file: *mut unicodefile) {
7535    if !file.is_null() {
7536        PortableTexEngine::boundary_close_file(*file);
7537        *file = core::ptr::null_mut();
7538    }
7539}
7540
7541pub(crate) unsafe fn setinputfileencoding(
7542    file: unicodefile,
7543    mode: integer,
7544    _encoding_data: integer,
7545) {
7546    // XeTeX modes: auto 0, UTF-8 1, UTF-16 big endian 2, little endian 3, raw 4, ICU mapping 5 read as raw bytes.
7547    if file.is_null() {
7548        return;
7549    }
7550    let handle = &mut *file;
7551    handle.encoding = match mode {
7552        1 => InputEncoding::Utf8,
7553        2 => InputEncoding::Utf16Be,
7554        3 => InputEncoding::Utf16Le,
7555        4 => InputEncoding::Bytes,
7556        0 => InputEncoding::Utf8,
7557        _ => InputEncoding::Bytes,
7558    };
7559}
7560
7561pub(crate) unsafe fn usingGraphite(_engine: FontHandle) -> boolean {
7562    false_0
7563}
7564
7565pub(crate) unsafe fn aatprintfontname(
7566    _what: i32,
7567    _attrs: CFDictionaryRef,
7568    _param1: i32,
7569    _param2: i32,
7570) {
7571}
7572
7573pub(crate) unsafe fn grprintfontname(
7574    _what: integer,
7575    _engine: voidpointer,
7576    _param1: integer,
7577    _param2: integer,
7578) {
7579}
7580
7581pub(crate) unsafe fn printglyphname(_font: integer, _gid: integer) {}
7582
7583pub(crate) unsafe fn applymapping(
7584    _mapping: voidpointer,
7585    _text: *mut uint16_t,
7586    text_len: i32,
7587) -> i32 {
7588    text_len
7589}
7590
7591pub(crate) unsafe fn checkfortfmfontmapping() {}
7592
7593pub(crate) unsafe fn loadtfmfontmapping() -> voidpointer {
7594    nullptr
7595}
7596
7597pub(crate) unsafe fn applytfmfontmapping(_mapping: voidpointer, c_0: i32) -> i32 {
7598    c_0
7599}
7600
7601pub(crate) unsafe fn countpdffilepages() -> i32 {
7602    0
7603}
7604
7605pub(crate) unsafe fn aatfontget(_what: i32, _attrs: CFDictionaryRef) -> i32 {
7606    0
7607}
7608
7609pub(crate) unsafe fn aatfontget1(_what: i32, _attrs: CFDictionaryRef, _param: i32) -> i32 {
7610    0
7611}
7612
7613pub(crate) unsafe fn aatfontget2(
7614    _what: i32,
7615    _attrs: CFDictionaryRef,
7616    _param1: i32,
7617    _param2: i32,
7618) -> i32 {
7619    0
7620}
7621
7622pub(crate) unsafe fn aatfontgetnamed(_what: i32, _attrs: CFDictionaryRef) -> i32 {
7623    0
7624}
7625
7626pub(crate) unsafe fn aatfontgetnamed1(
7627    _what: i32,
7628    _attrs: CFDictionaryRef,
7629    _param: i32,
7630) -> i32 {
7631    0
7632}
7633
7634pub(crate) unsafe fn grfontgetnamed(_what: integer, _engine: voidpointer) -> integer {
7635    0
7636}
7637
7638pub(crate) unsafe fn grfontgetnamed1(
7639    _what: integer,
7640    _engine: voidpointer,
7641    _param: integer,
7642) -> integer {
7643    0
7644}
7645
7646/// XeTeX `free_ot_assembly`, `assembly` must come from `get_ot_assembly_ptr` and not be freed yet.
7647pub(crate) unsafe fn free_ot_assembly(assembly: *mut GlyphAssembly) {
7648    if !assembly.is_null() {
7649        drop(Box::from_raw(assembly));
7650    }
7651}
7652
7653#[cfg(test)]
7654mod tests {
7655    use super::*;
7656
7657    /// Build a text [`PortableFileHandle`] over `bytes` with the given encoding.
7658    fn text_handle(bytes: Vec<u8>, encoding: InputEncoding) -> PortableFileHandle {
7659        let mut handle = PortableFileHandle::new(
7660            "test.tex".to_string(),
7661            None,
7662            resource_format_tex_input,
7663            bytes,
7664        );
7665        handle.encoding = encoding;
7666        handle
7667    }
7668
7669    /// Drain every Unicode scalar the decoder produces until EOF.
7670    fn decode_all(handle: &mut PortableFileHandle) -> Vec<u32> {
7671        let mut out = Vec::new();
7672        while let Some(scalar) = handle.next_input_scalar() {
7673            out.push(scalar);
7674        }
7675        out
7676    }
7677
7678    #[test]
7679    fn utf8_decoder_reads_multibyte_scalars() {
7680        // "αβγ" = CE B1 CE B2 CE B3 -> U+03B1, U+03B2, U+03B3.
7681        let mut h = text_handle(vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3], InputEncoding::Utf8);
7682        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
7683        // ASCII stays one scalar per byte, three and four byte sequences round trip.
7684        let mut h = text_handle(
7685            vec![b'A', 0xE2, 0x82, 0xAC, 0xF0, 0x9F, 0x98, 0x80],
7686            InputEncoding::Utf8,
7687        );
7688        assert_eq!(decode_all(&mut h), vec![0x41, 0x20AC, 0x1F600]);
7689    }
7690
7691    #[test]
7692    fn utf8_decoder_replaces_bad_sequences() {
7693        // A lead byte then a non continuation byte gives U+FFFD and leaves the byte to decode next.
7694        let mut h = text_handle(vec![0xCE, b'A'], InputEncoding::Utf8);
7695        assert_eq!(decode_all(&mut h), vec![0xFFFD, 0x41]);
7696        // A lone continuation byte is a lead byte with no continuation bytes and decodes to itself.
7697        let mut h = text_handle(vec![0x80], InputEncoding::Utf8);
7698        assert_eq!(decode_all(&mut h), vec![0x80]);
7699    }
7700
7701    #[test]
7702    fn utf16_decoders_read_units_and_surrogates() {
7703        // "αβγ" UTF-16LE: B1 03 B2 03 B3 03.
7704        let mut h = text_handle(
7705            vec![0xB1, 0x03, 0xB2, 0x03, 0xB3, 0x03],
7706            InputEncoding::Utf16Le,
7707        );
7708        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
7709        // Same in UTF-16BE: 03 B1 03 B2 03 B3.
7710        let mut h = text_handle(
7711            vec![0x03, 0xB1, 0x03, 0xB2, 0x03, 0xB3],
7712            InputEncoding::Utf16Be,
7713        );
7714        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
7715        // Surrogate pair U+1F600 in UTF-16 little endian, units `D83D DE00`.
7716        let mut h = text_handle(vec![0x3D, 0xD8, 0x00, 0xDE], InputEncoding::Utf16Le);
7717        assert_eq!(decode_all(&mut h), vec![0x1F600]);
7718        // A high surrogate then a non low unit gives U+FFFD and leaves the unit to decode next.
7719        let mut h = text_handle(vec![0x3D, 0xD8, 0x41, 0x00], InputEncoding::Utf16Le);
7720        assert_eq!(decode_all(&mut h), vec![0xFFFD, 0x41]);
7721        // Lone low surrogate -> U+FFFD.
7722        let mut h = text_handle(vec![0x00, 0xDC], InputEncoding::Utf16Le);
7723        assert_eq!(decode_all(&mut h), vec![0xFFFD]);
7724    }
7725
7726    #[test]
7727    fn bytes_mode_reads_each_byte_raw() {
7728        // Raw bytes: every byte is its own scalar.
7729        let mut h = text_handle(vec![0xCE, 0xB1, 0x41], InputEncoding::Bytes);
7730        assert_eq!(decode_all(&mut h), vec![0xCE, 0xB1, 0x41]);
7731    }
7732
7733    #[test]
7734    fn bom_sniff_selects_encoding_and_consumes_bom() {
7735        // A UTF-8 BOM selects UTF-8 and is skipped.
7736        let mut h = text_handle(vec![0xEF, 0xBB, 0xBF, b'A'], InputEncoding::Bytes);
7737        h.resolve_text_encoding_auto();
7738        assert_eq!(h.encoding, InputEncoding::Utf8);
7739        assert_eq!(h.cursor, 3);
7740        assert_eq!(decode_all(&mut h), vec![0x41]);
7741        // A UTF-16 big endian BOM selects that encoding and is skipped.
7742        let mut h = text_handle(vec![0xFE, 0xFF, 0x03, 0xB1], InputEncoding::Bytes);
7743        h.resolve_text_encoding_auto();
7744        assert_eq!(h.encoding, InputEncoding::Utf16Be);
7745        assert_eq!(h.cursor, 2);
7746        assert_eq!(decode_all(&mut h), vec![0x3B1]);
7747        // A UTF-16 little endian BOM selects that encoding and is skipped.
7748        let mut h = text_handle(vec![0xFF, 0xFE, 0xB1, 0x03], InputEncoding::Bytes);
7749        h.resolve_text_encoding_auto();
7750        assert_eq!(h.encoding, InputEncoding::Utf16Le);
7751        assert_eq!(h.cursor, 2);
7752        assert_eq!(decode_all(&mut h), vec![0x3B1]);
7753        // ASCII text without a BOM reads as UTF-8 from its first byte.
7754        let mut h = text_handle(vec![b'h', b'i'], InputEncoding::Bytes);
7755        h.resolve_text_encoding_auto();
7756        assert_eq!(h.encoding, InputEncoding::Utf8);
7757        assert_eq!(h.cursor, 0);
7758        // A zero first byte without a BOM guesses UTF-16 big endian and skips nothing.
7759        let mut h = text_handle(vec![0x00, 0x41], InputEncoding::Bytes);
7760        h.resolve_text_encoding_auto();
7761        assert_eq!(h.encoding, InputEncoding::Utf16Be);
7762        assert_eq!(h.cursor, 0);
7763        assert_eq!(decode_all(&mut h), vec![0x41]);
7764    }
7765
7766    #[test]
7767    fn input_line_decodes_text_into_buffer() {
7768        // Reads one line through `boundary_input_line` after the encoding sniff files get when opened.
7769        fn buffer_after_input_line(bytes: Vec<u8>) -> Vec<u32> {
7770            let image = PortableFormatImage::empty();
7771            let mut engine = PortableTexEngine::from_format(&image, EmptyResourceProvider);
7772            assert!(engine.initialize_format_state());
7773            let mut handle = PortableFileHandle::new(
7774                "input.tex".to_string(),
7775                None,
7776                resource_format_tex_input,
7777                bytes,
7778            );
7779            PortableTexEngine::resolve_input_encoding(&mut handle);
7780            let raw = Box::into_raw(Box::new(handle));
7781            engine.state.first = 0;
7782            let read = unsafe {
7783                PortableTexEngine::boundary_input_line(
7784                    &mut engine as *mut PortableTexEngine<'_>,
7785                    raw as NativeFileHandle,
7786                )
7787            };
7788            assert!(matches!(read, Ok(line) if line != 0), "boundary_input_line should succeed");
7789            let first = engine.state.first.max(0) as usize;
7790            let last = engine.state.last.max(0) as usize;
7791            let out = engine.heap.buffer_storage[first..last]
7792                .iter()
7793                .map(|&scalar| scalar as u32)
7794                .collect();
7795            unsafe { drop(Box::from_raw(raw)) };
7796            out
7797        }
7798        // "αβγ" in UTF-8, then UTF-16LE and UTF-16BE with byte order marks.
7799        let utf8 = vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3];
7800        assert_eq!(buffer_after_input_line(utf8), vec![0x3B1, 0x3B2, 0x3B3]);
7801        let utf16le_bom = vec![0xFF, 0xFE, 0xB1, 0x03, 0xB2, 0x03, 0xB3, 0x03];
7802        assert_eq!(buffer_after_input_line(utf16le_bom), vec![0x3B1, 0x3B2, 0x3B3]);
7803        let utf16be_bom = vec![0xFE, 0xFF, 0x03, 0xB1, 0x03, 0xB2, 0x03, 0xB3];
7804        assert_eq!(buffer_after_input_line(utf16be_bom), vec![0x3B1, 0x3B2, 0x3B3]);
7805    }
7806
7807    #[test]
7808    fn engine_abort_is_captured_at_runtime_boundary() {
7809        let image = PortableFormatImage::empty();
7810        let mut engine = PortableTexEngine::from_format(&image, EmptyResourceProvider);
7811
7812        let completed = engine.catch_engine_abort(|engine| unsafe {
7813            PortableTexEngine::abort_engine(engine as *mut PortableTexEngine<'_>, 7)?;
7814            Ok(())
7815        });
7816
7817        assert!(!completed);
7818        assert_eq!(engine.last_abort_status(), Some(7));
7819    }
7820
7821    #[test]
7822    fn successful_engine_abort_completes_runtime_boundary() {
7823        let image = PortableFormatImage::empty();
7824        let mut engine = PortableTexEngine::from_format(&image, EmptyResourceProvider);
7825
7826        let completed = engine.catch_engine_abort(|engine| unsafe {
7827            PortableTexEngine::abort_engine(engine as *mut PortableTexEngine<'_>, 0)?;
7828            Ok(())
7829        });
7830
7831        assert!(completed);
7832        assert_eq!(engine.last_abort_status(), None);
7833    }
7834
7835    fn initialized_engine() -> PortableTexEngine<'static> {
7836        let mut engine =
7837            PortableTexEngine::from_format(&PortableFormatImage::empty(), EmptyResourceProvider);
7838        assert!(engine.initialize_format_state());
7839        engine
7840    }
7841
7842    /// Runs INITEX `input` with brace catcodes set, as no format assigns them yet.
7843    fn run_initex(engine: &mut PortableTexEngine<'_>, input: &str) -> bool {
7844        let input = format!("\\catcode`\\{{=1 \\catcode`\\}}=2 {input}");
7845        assert!(engine.begin_primary_input("input.tex", input.into_bytes()));
7846        engine.run_main_control()
7847    }
7848
7849    /// Replaces the body checksum after a test edits an encoded image.
7850    fn reseal(bytes: &mut [u8]) {
7851        let checksum = image_checksum(&bytes[HEADER_LEN..]);
7852        bytes[VALIDATION_TAG_LEN + 8..HEADER_LEN].copy_from_slice(&checksum.to_le_bytes());
7853    }
7854
7855    #[test]
7856    fn state_holds_no_heap_owning_field() {
7857        // `PortableTexState` is copied bitwise, so a `Vec` there would be freed once per copy.
7858        assert!(!core::mem::needs_drop::<PortableTexState>());
7859    }
7860
7861    #[test]
7862    fn heap_fields_survive_empty_clone_and_image_round_trip() {
7863        let image = PortableFormatImage::empty();
7864        let mut first = PortableTexEngine::from_format(&image, EmptyResourceProvider);
7865        first.heap.src_grp_stack.push(7);
7866        first.heap.src_spans.push(RawSpan::default());
7867        let copy = first.snapshot_format();
7868        let mut second = PortableTexEngine::from_format(&copy, EmptyResourceProvider);
7869        second.heap.src_grp_stack.push(8);
7870        assert_eq!(first.heap.src_grp_stack, [7]);
7871        assert_eq!(second.heap.src_grp_stack, [7, 8]);
7872        let reloaded = PortableFormatImage::from_bytes(&copy.to_bytes()).expect("empty image reloads");
7873        let third = PortableTexEngine::from_format(&reloaded, EmptyResourceProvider);
7874        assert!(third.heap.src_grp_stack.is_empty() && third.heap.src_spans.is_empty());
7875    }
7876
7877    #[test]
7878    fn snapshot_after_source_tracking_render_does_not_share_buffers() {
7879        let mut engine = initialized_engine();
7880        engine.set_source_tracking(true);
7881        assert!(run_initex(&mut engine, "\\setbox0\\hbox{\\kern1pt}\\end"));
7882        engine.heap.src_grp_stack.push(3);
7883        let image = engine.snapshot_format();
7884        engine.heap.src_grp_stack.clear();
7885        engine.heap.src_grp_stack.shrink_to_fit();
7886        let instance = PortableTexEngine::from_format(&image, EmptyResourceProvider);
7887        assert_eq!(instance.heap.src_grp_stack, [3]);
7888        drop(engine);
7889        drop(image);
7890        assert_eq!(instance.heap.src_grp_stack, [3]);
7891    }
7892
7893    #[test]
7894    fn format_dumps_are_deterministic_and_round_trip() {
7895        let first = initialized_engine().into_format().to_bytes();
7896        let second = initialized_engine().into_format().to_bytes();
7897        assert!(first == second, "two dumps of the same format differ");
7898        let reloaded = PortableFormatImage::from_bytes(&first).expect("dump reloads");
7899        assert!(reloaded.to_bytes() == first, "a reloaded image dumps differently");
7900        let mut engine = PortableTexEngine::from_format(&reloaded, EmptyResourceProvider);
7901        assert!(run_initex(&mut engine, "\\setbox0\\hbox{\\kern1pt}\\end"));
7902    }
7903
7904    #[test]
7905    fn hostile_images_return_errors() {
7906        let bytes = initialized_engine().into_format().to_bytes();
7907        let decode = |bytes: &[u8]| PortableFormatImage::from_bytes(bytes).err();
7908        assert_eq!(decode(&bytes[..10]), Some(FormatImageError::Truncated));
7909        assert_eq!(decode(&bytes[..bytes.len() - 1]), Some(FormatImageError::Truncated));
7910        let mut other_build = bytes.clone();
7911        other_build[9] ^= 1;
7912        assert_eq!(decode(&other_build), Some(FormatImageError::OtherBuild));
7913        let mut corrupt = bytes.clone();
7914        let last = corrupt.len() - 1;
7915        corrupt[last] ^= 1;
7916        assert_eq!(decode(&corrupt), Some(FormatImageError::ChecksumMismatch));
7917        let mut trailing = bytes.clone();
7918        trailing.push(0);
7919        assert_eq!(decode(&trailing), Some(FormatImageError::TrailingBytes));
7920        // `initialized` is the first field, so the first body byte is a bool.
7921        let mut bad_bool = bytes.clone();
7922        bad_bool[HEADER_LEN] = 2;
7923        reseal(&mut bad_bool);
7924        assert_eq!(decode(&bad_bool), Some(FormatImageError::InvalidField("initialized")));
7925        assert_eq!(
7926            PortableFormatImage::from_static_bytes(Box::leak(corrupt.into_boxed_slice())).err(),
7927            Some(FormatImageError::ChecksumMismatch)
7928        );
7929    }
7930
7931    #[test]
7932    fn image_tags_carry_byte_order_and_no_pointer_width() {
7933        let bytes = initialized_engine().into_format().to_bytes();
7934        let mut expected = *b"MTXfmt\x07\x00\x00________";
7935        expected[9..].copy_from_slice(&BUILD_FINGERPRINT.to_le_bytes());
7936        assert_eq!(bytes[..VALIDATION_TAG_LEN], expected, "the tag must read the same on every little endian target");
7937        let decode = |edit: fn(&mut [u8])| {
7938            let mut bytes = bytes.clone();
7939            edit(&mut bytes);
7940            PortableFormatImage::from_bytes(&bytes).err()
7941        };
7942        assert_eq!(decode(|bytes| bytes[0] = b'x'), Some(FormatImageError::NotAnImage));
7943        // Layout 6 stored the pointer width here and let a memory word hold a pointer.
7944        assert_eq!(decode(|bytes| bytes[6] = 6), Some(FormatImageError::Version(6)));
7945        assert_eq!(decode(|bytes| bytes[8] = 1), Some(FormatImageError::ByteOrder));
7946    }
7947
7948    #[test]
7949    fn native_glyph_words_never_keep_stale_bytes() {
7950        // A 32 bit pointer store zeroed only the low half, so a reused word kept a stale high half.
7951        let stale = memoryword { raw: 0xDEAD_BEEF_0000_0000 };
7952        let mut engine = initialized_engine();
7953        unsafe {
7954            let mem = engine.state.zmem;
7955            // Six words of node plus one word holding up to four UTF-16 units.
7956            let size = 7;
7957            let block = engine.zgetnode(size).expect("allocates");
7958            for word in block..block + size {
7959                *mem.offset(word as isize) = stale;
7960            }
7961            engine.zfreenode(block, size);
7962            engine.state.rover = block;
7963            let node = engine.znewnativewordnode(0, 3).expect("allocates");
7964            assert_eq!(node, block, "the node reuses the stale block");
7965            assert_eq!((*mem.offset(node as isize + 5)).raw, 0);
7966            *mem.offset(node as isize + 5) = stale;
7967            let copy = engine.zcopynodelist(node).expect("copies");
7968            *mem.offset(node as isize + 5) = memoryword::default();
7969            assert_eq!((*mem.offset(copy as isize + 5)).raw, 0);
7970            assert_eq!(engine.zcharpw(copy, 0), 0);
7971            engine.zflushnodelist(copy).expect("frees");
7972            engine.zflushnodelist(node).expect("frees");
7973        }
7974    }
7975
7976    #[cfg(debug_assertions)]
7977    #[test]
7978    #[should_panic(expected = "glyph info word is nonzero")]
7979    fn a_nonzero_glyph_info_word_is_never_read_as_a_pointer() {
7980        let mut engine = initialized_engine();
7981        unsafe {
7982            let node = engine.znewnativewordnode(0, 3).expect("allocates");
7983            (*engine.state.zmem.offset(node as isize + 5)).raw = 0xDEAD_BEEF_0000_0000;
7984            engine.zcharpw(node, 0);
7985        }
7986    }
7987
7988    #[test]
7989    fn images_with_inconsistent_sizes_are_refused() {
7990        let image = initialized_engine().into_format();
7991        let encode_with = |edit: &dyn Fn(&mut PortableTexState)| {
7992            let mut state = boxed_state_copy(&image.state);
7993            edit(&mut state);
7994            encode_image(&state, &image.heap)
7995        };
7996        let decode = |bytes: Vec<u8>| PortableFormatImage::from_bytes(&bytes).err();
7997        assert_eq!(
7998            decode(encode_with(&|state| state.memtop = i32::MAX)),
7999            Some(FormatImageError::Geometry("memtop"))
8000        );
8001        assert_eq!(
8002            decode(encode_with(&|state| state.memend = state.memmax + 1)),
8003            Some(FormatImageError::Geometry("lomemmax, himemmin and memend"))
8004        );
8005        assert_eq!(
8006            decode(encode_with(&|state| state.poolptr = state.poolsize + 1)),
8007            Some(FormatImageError::Geometry("poolptr"))
8008        );
8009        assert_eq!(
8010            decode(encode_with(&|state| state.fmemptr = -1)),
8011            Some(FormatImageError::Geometry("fmemptr"))
8012        );
8013        // A huge length must be refused before anything is allocated for it.
8014        assert_eq!(
8015            decode(encode_with(&|state| state.nativetextsize = i32::MAX)),
8016            Some(FormatImageError::Geometry("nativetextsize"))
8017        );
8018        let empty = PortableFormatImage::empty();
8019        let mut state = boxed_state_copy(&empty.state);
8020        state.nativetextsize = i32::MAX;
8021        assert_eq!(
8022            decode(encode_image(&state, &empty.heap)),
8023            Some(FormatImageError::Geometry("nativetextsize"))
8024        );
8025    }
8026
8027    #[test]
8028    fn uninitialized_engine_refuses_every_run_entry_point() {
8029        let mut engine =
8030            PortableTexEngine::from_format(&PortableFormatImage::empty(), EmptyResourceProvider);
8031        assert!(!engine.begin_primary_input("input.tex", b"\\end".to_vec()));
8032        assert_eq!(engine.last_error_message(), Some(UNINITIALIZED_STATE_MESSAGE));
8033        assert!(!engine.run_main_control());
8034        assert!(!engine.run_format_initialization());
8035        assert!(!engine.finalize_trie());
8036        assert!(engine.snapshot_node(PortableNodeHandle(0)).is_none());
8037        assert!(engine.native_font_spec(0).is_none());
8038    }
8039
8040    #[test]
8041    fn seal_and_load_close_file_slots() {
8042        let mut engine = initialized_engine();
8043        let handle = Box::into_raw(Box::new(PortableFileHandle::new(
8044            "open.tex".to_string(),
8045            None,
8046            resource_format_tex_input,
8047            Vec::new(),
8048        )));
8049        engine.state.readfile[3] = handle;
8050        engine.state.readopen[3] = 1;
8051        engine.state.writefile[4] = handle;
8052        engine.state.writeopen[4] = true_0;
8053        let image = engine.snapshot_format();
8054        assert!(image.state.readfile.iter().all(|file| file.is_null()));
8055        assert!(image.state.writefile.iter().all(|file| file.is_null()));
8056        assert!(image.state.readopen.iter().all(|&state| state == 2));
8057        assert!(image.state.writeopen.iter().all(|&open| open == false_0));
8058        let mut state = boxed_state_copy(&image.state);
8059        state.readopen[5] = 0;
8060        let reloaded = PortableFormatImage::from_bytes(&encode_image(&state, &image.heap))
8061            .expect("image reloads");
8062        assert_eq!(reloaded.state.readopen[5], 2);
8063        engine.state.readfile[3] = core::ptr::null_mut();
8064        engine.state.writefile[4] = core::ptr::null_mut();
8065        unsafe { drop(Box::from_raw(handle)) };
8066    }
8067
8068    #[test]
8069    fn public_accessors_bound_their_indices() {
8070        let mut engine = initialized_engine();
8071        assert!(engine.native_font_spec(10_000).is_none());
8072        assert!(engine.native_font_spec(-1).is_none());
8073        assert!(engine.pool_string(i32::MAX).is_none());
8074        assert!(engine.pool_string(65536 + MAX_STRINGS).is_none());
8075        assert!(engine.font_name(10_000).is_none());
8076        // A high memory word that is not a char node, with a font byte past every font array.
8077        let word = engine.state.himemmin;
8078        let slot = (word - engine.state.memmin) as usize;
8079        engine.heap.mem[slot].hh.u.B0 = i16::MAX;
8080        engine.heap.mem[slot].hh.u.B1 = i16::MAX;
8081        let snapshot = engine.snapshot_node(PortableNodeHandle(word)).expect("in memory");
8082        assert_eq!(snapshot.kind, PortableNodeKind::Character);
8083        assert_eq!(snapshot.width, 0);
8084        assert!(engine.snapshot_node(PortableNodeHandle(i32::MAX)).is_none());
8085        assert!(engine.snapshot_node(PortableNodeHandle(-5)).is_none());
8086    }
8087
8088    #[test]
8089    fn pdf_error_stops_the_run() {
8090        let mut engine = initialized_engine();
8091        assert!(!run_initex(&mut engine, "\\message{\\leftmarginkern0}\\end"));
8092        let message = engine.last_error_message().expect("pdf_error surfaces a message");
8093        assert!(message.starts_with("Error"), "{message}");
8094        engine.state.hliststacklevel = 0;
8095        assert!(matches!(unsafe { engine.popnode() }, Err(EngineBreak::Error(_))));
8096        engine.state.hliststacklevel = 513;
8097        assert!(matches!(unsafe { engine.zpushnode(0) }, Err(EngineBreak::Error(_))));
8098    }
8099
8100    #[test]
8101    fn finalize_trie_reports_pattern_memory_overflow() {
8102        let mut engine = initialized_engine();
8103        let input = "\\catcode`\\{=1 \\catcode`\\}=2 \\patterns{a1b c1d e1f}\\end";
8104        assert!(engine.begin_primary_input("input.tex", input.as_bytes().to_vec()));
8105        assert!(engine.run_format_initialization());
8106        assert_ne!(engine.state.trienotready, 0);
8107        // Room for the few trie nodes in the hash, too little for `first_fit` to pack them.
8108        engine.state.triesize = 64;
8109        assert!(!engine.finalize_trie());
8110        let message = engine.last_error_message().expect("the overflow surfaces its message");
8111        assert!(message.contains("pattern memory"), "{message}");
8112    }
8113
8114    #[test]
8115    fn long_native_run_grows_the_native_text_buffer() {
8116        let mut engine = initialized_engine().with_font_platform(MockNativeFonts);
8117        let run = "a".repeat(300);
8118        let input = format!("\\font\\x=\"mock\" \\x \\setbox0\\hbox{{{run}}}\\end");
8119        assert!(run_initex(&mut engine, &input), "{:?}", engine.last_error_message());
8120        assert!(engine.state.nativetextsize > 300);
8121        assert_eq!(
8122            engine.heap.nativetext_storage.len(),
8123            engine.state.nativetextsize as usize
8124        );
8125        let widest = engine
8126            .native_glyph_infos
8127            .values()
8128            .map(|info| info.glyphs.len())
8129            .max();
8130        assert_eq!(widest, Some(300));
8131    }
8132
8133    /// Spans of the primary input after a tracked INITEX run, tracking switched on before or after input starts.
8134    fn tracked_spans(bytes: &[u8], track_before_input: bool) -> Vec<(u32, u32)> {
8135        let mut engine = initialized_engine();
8136        if track_before_input {
8137            engine.set_source_tracking(true);
8138        }
8139        assert!(engine.begin_primary_input("input.tex", bytes.to_vec()));
8140        if !track_before_input {
8141            engine.set_source_tracking(true);
8142        }
8143        assert!(engine.run_main_control(), "{:?}", engine.last_error_message());
8144        engine
8145            .input_source_spans()
8146            .into_iter()
8147            .filter(|span| span.name == "input.tex")
8148            .map(|span| (span.start, span.end))
8149            .collect()
8150    }
8151
8152    /// Byte range of the `nth` occurrence of `needle` in `text`, shifted by `offset` bytes.
8153    fn byte_range(text: &str, needle: &str, nth: usize, offset: usize) -> (u32, u32) {
8154        let start = text.match_indices(needle).nth(nth).expect("needle occurs").0 + offset;
8155        (start as u32, (start + needle.len()) as u32)
8156    }
8157
8158    #[test]
8159    fn source_spans_are_byte_offsets_into_the_input() {
8160        let text = "\\catcode`\\{=1 \\catcode`\\}=2 \\setbox0\\hbox{\u{3b1}\\relax \u{e9}\u{2308}   \r\n\\relax \u{2308}q\r\n}\\end\r\n";
8161        let mut bytes = vec![0xEF, 0xBB, 0xBF];
8162        bytes.extend_from_slice(text.as_bytes());
8163        let spans = tracked_spans(&bytes, true);
8164        let bom = 3;
8165        for expected in [
8166            byte_range(text, "\\setbox", 0, bom),
8167            byte_range(text, "\u{3b1}", 0, bom),
8168            byte_range(text, "\u{e9}", 0, bom),
8169            byte_range(text, "\u{2308}", 0, bom),
8170            byte_range(text, "\\relax", 1, bom),
8171            byte_range(text, "\u{2308}", 1, bom),
8172            byte_range(text, "q", 0, bom),
8173            byte_range(text, "\\end", 0, bom),
8174        ] {
8175            assert!(spans.contains(&expected), "{expected:?} missing from {spans:?}");
8176        }
8177    }
8178
8179    #[test]
8180    fn source_spans_do_not_depend_on_when_tracking_is_enabled() {
8181        let text = "\\catcode`\\{=1 \\catcode`\\}=2\n\\setbox0\\hbox{a\\relax\nb\\relax}\n\\end\n";
8182        let before = tracked_spans(text.as_bytes(), true);
8183        let after = tracked_spans(text.as_bytes(), false);
8184        assert_eq!(before, after);
8185        assert!(before.contains(&byte_range(text, "\\relax", 1, 0)), "{before:?}");
8186        assert!(before.contains(&byte_range(text, "\\end", 0, 0)), "{before:?}");
8187    }
8188
8189    #[test]
8190    fn node_allocation_clears_a_stale_span() {
8191        let mut engine = initialized_engine();
8192        engine.set_source_tracking(true);
8193        engine.state.cmd_span = engine.intern_span_raw(65536, 1, 2, 0);
8194        engine.heap.src_stack_cells.push(SrcStackCell::default());
8195        engine.state.cur_stack_head = 1;
8196        let node = unsafe { engine.zgetnode(4) }.expect("allocates");
8197        assert_ne!(engine.heap.node_src.get_copy(node as usize), 0);
8198        assert_eq!(engine.heap.node_stack.get_copy(node as usize), 1);
8199        unsafe { engine.zfreenode(node, 4) };
8200        // The allocation hook stamps a reused block, which must not keep its previous occupant's span.
8201        engine.state.cmd_span = 0;
8202        engine.state.cur_stack_head = 0;
8203        unsafe { PortableTexEngine::src_stamp_node_range(&mut engine, node, 4) };
8204        for word in node..node + 4 {
8205            assert_eq!(engine.heap.node_src.get_copy(word as usize), 0);
8206            assert_eq!(engine.heap.node_stack.get_copy(word as usize), 0);
8207        }
8208    }
8209
8210    /// Nodes of the list starting at `first`, in order.
8211    fn list_snapshots(
8212        engine: &PortableTexEngine<'_>,
8213        first: Option<PortableNodeHandle>,
8214    ) -> Vec<PortableNodeSnapshot> {
8215        let mut nodes = Vec::new();
8216        let mut next = first;
8217        while let Some(handle) = next {
8218            let node = engine.snapshot_node(handle).expect("list node");
8219            next = node.link;
8220            nodes.push(node);
8221        }
8222        nodes
8223    }
8224
8225    /// Runs INITEX `input` with fragment capture and returns the captured root.
8226    fn captured_root(engine: &mut PortableTexEngine<'_>, input: &str) -> PortableNodeSnapshot {
8227        engine.begin_fragment_capture();
8228        assert!(run_initex(engine, input), "{:?}", engine.last_error_message());
8229        engine.end_fragment_capture();
8230        let root = engine.captured_fragment_root().expect("a captured root");
8231        engine.snapshot_node(root).expect("root snapshot")
8232    }
8233
8234    #[test]
8235    fn a_backed_up_macro_body_token_maps_to_the_invocation() {
8236        let mut engine = initialized_engine();
8237        engine.set_source_tracking(true);
8238        let body = "\\catcode`\\#=6 \\def\\m#1{#1\\kern1pt}\\hbox{\\count1=\\m{7}}\\end";
8239        let root = captured_root(&mut engine, body);
8240        let kern = list_snapshots(&engine, root.list)
8241            .into_iter()
8242            .find(|node| node.kind == PortableNodeKind::Kern)
8243            .expect("the macro's kern");
8244        let source = kern.source.expect("the kern is stamped");
8245        // `run_initex` prepends the brace catcodes, which shift every offset.
8246        let prefix = "\\catcode`\\{=1 \\catcode`\\}=2 ".len();
8247        assert_eq!((source.start, source.end), byte_range(body, "\\m{7}", 0, prefix));
8248    }
8249
8250    /// Resolves every font to handle 1, maps each character to itself and shapes one glyph per UTF-16 unit.
8251    struct MockNativeFonts;
8252
8253    impl FontPlatform for MockNativeFonts {
8254        fn resolve_font_handle(self: &mut Self, _name: &[i32], _size: i32) -> Option<PortableFontHandle> {
8255            Some(1)
8256        }
8257
8258        fn map_char_to_glyph(self: &mut Self, _font: PortableFontHandle, codepoint: i32) -> i32 {
8259            codepoint
8260        }
8261
8262        fn shape_native_text(
8263            self: &mut Self,
8264            _font: PortableFontHandle,
8265            text: &[u16],
8266            _use_glyph_metrics: bool,
8267        ) -> PortableNativeTextMetrics {
8268            PortableNativeTextMetrics {
8269                width: text.len() as i32 * 65536,
8270                height: 65536,
8271                depth: 0,
8272                glyphs: text
8273                    .iter()
8274                    .map(|&unit| PortableNativeGlyph {
8275                        glyph_id: unit,
8276                        ..PortableNativeGlyph::default()
8277                    })
8278                    .collect(),
8279            }
8280        }
8281
8282        // Glyph `g` is `g` points wide and its control box reaches one point further, a 1pt italic correction.
8283        fn glyph_bounds(self: &mut Self, _font: PortableFontHandle, glyph: u16) -> PortableGlyphBounds {
8284            let advance = f32::from(glyph);
8285            PortableGlyphBounds {
8286                advance,
8287                x_min: -0.5,
8288                y_min: -2.0,
8289                x_max: advance + 1.0,
8290                y_max: 3.0,
8291            }
8292        }
8293
8294        fn char_code_range(self: &mut Self, _font: PortableFontHandle) -> Option<(i32, i32)> {
8295            Some((32, 126))
8296        }
8297    }
8298
8299    const MOCK_FONT: &str = "\\font\\x=\"mock\" \\x ";
8300
8301    #[test]
8302    fn native_glyph_side_table_follows_copies_and_frees() {
8303        let run = |input: &str| {
8304            let mut engine = initialized_engine().with_font_platform(MockNativeFonts);
8305            assert!(run_initex(&mut engine, &format!("{MOCK_FONT}{input}\\end")), "{:?}", engine.last_error_message());
8306            engine.native_glyph_infos.len()
8307        };
8308        let boxes = "\\setbox1\\hbox{\\XeTeXglyph5 ab}\\setbox2\\copy1 ";
8309        // A glyph node and a native word, each in box 1 and in its copy.
8310        assert_eq!(run(boxes), 4);
8311        assert_eq!(run(&format!("{boxes}\\setbox1\\box3 \\setbox2\\box3 ")), 0);
8312    }
8313
8314    /// Hands every host box token `t` back as a box `t` points wide.
8315    struct MockHostBoxes;
8316
8317    impl PortablePlatform for MockHostBoxes {
8318        fn host_box(self: &mut Self, request: PortableHostBoxRequest) -> Option<PortableHostBox> {
8319            Some(PortableHostBox {
8320                width: request.token * 65536,
8321                height: 65536,
8322                depth: 0,
8323                runs: Vec::new(),
8324                rules: Vec::new(),
8325            })
8326        }
8327    }
8328
8329    /// Record ids of every host box marker under `node`.
8330    fn host_box_ids(engine: &PortableTexEngine<'_>, node: &PortableNodeSnapshot, ids: &mut Vec<i32>) {
8331        if node.kind == PortableNodeKind::HostBoxRef {
8332            ids.push(node.character);
8333        }
8334        for child in list_snapshots(engine, node.list) {
8335            host_box_ids(engine, &child, ids);
8336        }
8337    }
8338
8339    #[test]
8340    fn a_host_box_kept_from_an_earlier_format_finds_no_record() {
8341        let mut first = initialized_engine().with_platform(MockHostBoxes);
8342        assert!(run_initex(&mut first, "\\global\\setbox1\\hbox{\\Uhostbox 5\\relax}\\end"));
8343        let image = first.snapshot_format();
8344        let mut second = PortableTexEngine::from_format(&image, EmptyResourceProvider).with_platform(MockHostBoxes);
8345        let root = captured_root(&mut second, "\\hbox{\\copy1 \\Uhostbox 6\\relax}\\end");
8346        let mut ids = Vec::new();
8347        host_box_ids(&second, &root, &mut ids);
8348        assert_eq!(ids.len(), 2, "{ids:?}");
8349        assert_ne!(ids[0], ids[1]);
8350        let records = ids
8351            .iter()
8352            .map(|&id| second.host_box_record(id as usize).map(|record| record.width))
8353            .collect::<Vec<_>>();
8354        assert_eq!(records, [None, Some(6 * 65536)]);
8355    }
8356
8357    #[test]
8358    fn a_text_host_box_is_appended_like_a_box() {
8359        let mut engine = initialized_engine().with_platform(MockHostBoxes);
8360        let root = captured_root(
8361            &mut engine,
8362            "\\baselineskip=20pt \\vbox{\\hbox{}\\Uhostbox 5\\relax}\\end",
8363        );
8364        let kinds = list_snapshots(&engine, root.list)
8365            .iter()
8366            .map(|node| node.kind)
8367            .collect::<Vec<_>>();
8368        assert_eq!(
8369            kinds,
8370            [PortableNodeKind::HorizontalBox, PortableNodeKind::Glue, PortableNodeKind::HorizontalBox]
8371        );
8372        let mut engine = initialized_engine().with_platform(MockHostBoxes);
8373        let resets_space_factor =
8374            "\\setbox0\\hbox{\\spacefactor=3000 \\Uhostbox 5\\relax\\ifnum\\spacefactor=1000 \\else\\errmessage{space factor}\\fi}\\end";
8375        assert!(run_initex(&mut engine, resets_space_factor), "{:?}", engine.last_error_message());
8376    }
8377
8378    #[test]
8379    fn the_sandbox_budget_ends_an_expansion_loop() {
8380        let mut engine = initialized_engine();
8381        engine.set_source_tracking(true);
8382        engine.set_sandbox(true);
8383        engine.set_sandbox_op_budget(10_000);
8384        assert!(!run_initex(&mut engine, "\\def\\x{\\x}\\x\\end"));
8385        let message = engine.last_error_message().expect("the budget surfaces an error");
8386        assert!(message.contains("did not terminate"), "{message}");
8387        assert_eq!(engine.last_error().map(|error| error.kind), Some(PortableErrorKind::Budget));
8388        assert!(engine.heap.src_stack_cells.len() <= 10_000);
8389    }
8390
8391    #[test]
8392    fn a_surfaced_error_reports_its_line_and_token_span() {
8393        let mut engine = initialized_engine();
8394        engine.set_source_tracking(true);
8395        let input = "\\relax\n\\relax \\undefined \\end";
8396        assert!(!run_initex(&mut engine, input));
8397        let error = engine.last_error().expect("an undefined control sequence surfaces");
8398        assert_eq!(error.kind, PortableErrorKind::Tex);
8399        assert_eq!(error.line, 2);
8400        let span = error.span.as_ref().expect("the offending token's span");
8401        // run_initex prepends the brace catcodes, so offsets count from the start of the full input.
8402        let full = format!("\\catcode`\\{{=1 \\catcode`\\}}=2 {input}");
8403        assert_eq!(&full[span.start as usize..span.end as usize], "\\undefined");
8404
8405        engine.set_sandbox(true);
8406        assert!(!run_initex(&mut engine, "\\openin1=x \\end"));
8407        assert_eq!(engine.last_error().map(|error| error.kind), Some(PortableErrorKind::Sandbox));
8408    }
8409
8410    #[test]
8411    fn the_construct_stack_freezes_at_its_cap() {
8412        let mut engine = initialized_engine();
8413        engine.set_source_tracking(true);
8414        engine.heap.src_stack_cells = vec![SrcStackCell::default(); SRC_STACK_CAP];
8415        engine.state.cur_stack_head = 7;
8416        engine.src_stack_push(1, 0, 0, false);
8417        assert!(engine.state.src_stack_frozen);
8418        assert_eq!(engine.state.cur_stack_head, 0);
8419        assert_eq!(engine.heap.src_stack_cells.len(), SRC_STACK_CAP);
8420        engine.src_stack_pop();
8421        assert_eq!(engine.state.cur_stack_head, 0);
8422    }
8423
8424    /// A sandboxed engine whose write stream 1 was opened before the fragment started.
8425    fn sandboxed_with_open_stream() -> PortableTexEngine<'static> {
8426        let mut engine = initialized_engine();
8427        let file = PortableFileHandle::new("open.tex".to_string(), None, resource_format_tex_input, Vec::new());
8428        engine.state.writefile[1] = Box::into_raw(Box::new(file));
8429        engine.state.writeopen[1] = true_0;
8430        engine.set_sandbox(true);
8431        engine
8432    }
8433
8434    #[test]
8435    fn the_sandbox_refuses_file_access() {
8436        for (input, command) in [
8437            ("\\input other.tex ", "\\input"),
8438            ("\\openin1=other ", "\\openin"),
8439            ("\\openout1=other ", "\\openout"),
8440            ("\\immediate\\openout1=other ", "\\openout"),
8441            ("\\setbox0\\hbox{\\write1{x}}", "\\write"),
8442            ("\\immediate\\write1{x}", "\\write"),
8443            ("\\read16 to\\x ", "\\read"),
8444        ] {
8445            let mut engine = sandboxed_with_open_stream();
8446            assert!(!run_initex(&mut engine, &format!("{input}\\end")), "{input}");
8447            let message = engine.last_error_message().unwrap_or_default();
8448            assert!(message.starts_with(command), "{input}: {message}");
8449        }
8450        // Streams not open to a file write to the terminal, as LaTeX's `\typeout` does.
8451        let mut engine = sandboxed_with_open_stream();
8452        let terminal = "\\immediate\\write16{x}\\immediate\\write5{y}\\setbox0\\hbox{\\write16{x}\\write-1{y}}\\end";
8453        assert!(run_initex(&mut engine, terminal), "{:?}", engine.last_error_message());
8454    }
8455
8456    #[test]
8457    fn a_written_file_reads_back_through_input() {
8458        let mut engine = initialized_engine();
8459        let input = concat!(
8460            "\\immediate\\openout3=written ",
8461            "\\immediate\\write3{\\noexpand\\gdef\\noexpand\\readback{ok}}",
8462            "\\immediate\\closeout3 ",
8463            "\\input written.tex ",
8464            "\\ifx\\readback\\undefined \\errmessage{nothing read back}\\fi\\end",
8465        );
8466        assert!(run_initex(&mut engine, input), "{:?}", engine.last_error_message());
8467        assert_eq!(
8468            engine.virtual_files.get("written.tex").map(Vec::as_slice),
8469            Some(&b"\\gdef \\readback {ok}\n"[..])
8470        );
8471    }
8472
8473    #[test]
8474    fn the_transcript_records_the_log_once_and_errors_keep_their_whole_message() {
8475        let mut engine = initialized_engine();
8476        assert!(run_initex(&mut engine, "\\message{\\jobname}\\message{hello}\\end"));
8477        let transcript = String::from_utf8_lossy(engine.transcript_bytes()).into_owned();
8478        assert!(transcript.contains("hello") && !transcript.contains("hheelllloo"), "{transcript}");
8479        let long = "a long message that wraps past the seventy nine column limit of the transcript lines, twice over";
8480        let mut engine = initialized_engine();
8481        assert!(!run_initex(&mut engine, &format!("\\message{{\\jobname}}\\errmessage{{{long}}}\\end")));
8482        assert_eq!(engine.last_error_message(), Some(long));
8483        let mut engine = initialized_engine();
8484        let split = "\\catcode`\\^=7 \\newlinechar=10 \\errmessage{first^^Jsecond}\\end";
8485        assert!(!run_initex(&mut engine, split));
8486        assert_eq!(engine.last_error_message(), Some("first\nsecond"));
8487    }
8488
8489    /// Serves one file.
8490    struct OneFile;
8491
8492    impl ResourceProvider for OneFile {
8493        fn read(self: &mut Self, request: ResourceRequest<'_>) -> Option<Vec<u8>> {
8494            (request.name == "present.tex").then(|| b"12345".to_vec())
8495        }
8496    }
8497
8498    #[test]
8499    fn file_size_is_empty_for_a_missing_file_and_recorded() {
8500        let mut engine = PortableTexEngine::from_format(&PortableFormatImage::empty(), OneFile);
8501        assert!(engine.initialize_format_state());
8502        let input = concat!(
8503            "\\def\\empty{}\\def\\five{5}",
8504            "\\edef\\a{\\filesize{missing.tex}}\\ifx\\a\\empty\\else\\errmessage{missing has a size}\\fi",
8505            "\\edef\\b{\\filesize{present.tex}}\\ifx\\b\\five\\else\\errmessage{wrong size}\\fi\\end",
8506        );
8507        assert!(run_initex(&mut engine, input), "{:?}", engine.last_error_message());
8508        let probes = engine
8509            .resource_request_records()
8510            .iter()
8511            .filter(|record| record.mode == FILE_SIZE_PROBE_MODE)
8512            .map(|record| (record.name.as_str(), record.byte_len))
8513            .collect::<Vec<_>>();
8514        assert_eq!(probes, [("missing.tex", None), ("present.tex", Some(5))]);
8515    }
8516
8517    #[test]
8518    fn a_line_longer_than_the_buffer_overflows() {
8519        let mut engine = initialized_engine();
8520        let input = format!("\\relax\n\\setbox0\\hbox{{{}}}\\end\n", "a".repeat(BUF_SIZE as usize + 100));
8521        assert!(engine.begin_primary_input("input.tex", input.into_bytes()));
8522        assert!(!engine.run_main_control());
8523        // `overflow` opens the log, so `succumb` reports through `error` before stopping.
8524        let message = engine.last_error_message().unwrap_or_default();
8525        assert!(message.contains("buffer size=200000"), "{message}");
8526    }
8527
8528    #[test]
8529    fn vsplit_prunes_the_top_of_the_remainder() {
8530        let mut engine = initialized_engine();
8531        let input = concat!(
8532            "\\splittopskip=10pt \\savingvdiscards=1 ",
8533            "\\setbox1\\vbox{\\hbox{}\\penalty-10000 \\kern5pt\\hbox{}}",
8534            "\\setbox2\\vsplit1 to 0pt \\setbox3\\vbox{\\splitdiscards}",
8535            "\\hbox{\\box1\\box3}\\end",
8536        );
8537        let root = captured_root(&mut engine, input);
8538        let boxes = list_snapshots(&engine, root.list);
8539        let kinds = |node: &PortableNodeSnapshot| {
8540            list_snapshots(&engine, node.list)
8541                .iter()
8542                .map(|node| (node.kind, node.width))
8543                .collect::<Vec<_>>()
8544        };
8545        assert_eq!(
8546            kinds(&boxes[0]),
8547            [(PortableNodeKind::Glue, 10 * 65536), (PortableNodeKind::HorizontalBox, 0)]
8548        );
8549        // The zero lineskip glue above the second box is discarded with the penalty and kern.
8550        assert_eq!(
8551            kinds(&boxes[1]),
8552            [
8553                (PortableNodeKind::Penalty, 0),
8554                (PortableNodeKind::Kern, 5 * 65536),
8555                (PortableNodeKind::Glue, 0),
8556            ]
8557        );
8558    }
8559
8560    #[test]
8561    fn native_char_metrics_come_from_the_font_platform() {
8562        let mut engine = initialized_engine().with_font_platform(MockNativeFonts);
8563        // The mock gives `d`, 100, a 100pt advance, 3pt height, 2pt depth and 1pt overhang, `D2Fix` truncates.
8564        let input = concat!(
8565            "\\font\\x=\"mock\" \\x ",
8566            "\\ifdim\\fontcharwd\\x`d=100pt \\else\\errmessage{width}\\fi",
8567            "\\ifdim\\fontcharht\\x`d=3pt \\else\\errmessage{height}\\fi",
8568            "\\ifdim\\fontchardp\\x`d=2pt \\else\\errmessage{depth}\\fi",
8569            "\\ifdim\\fontcharic\\x`d=65535sp \\else\\errmessage{italic correction}\\fi",
8570            "\\ifnum\\XeTeXfirstfontchar\\x=32 \\else\\errmessage{first char}\\fi",
8571            "\\ifnum\\XeTeXlastfontchar\\x=126 \\else\\errmessage{last char}\\fi",
8572            "\\ifdim\\XeTeXglyphbounds1 100=-32767sp \\else\\errmessage{left bearing}\\fi",
8573            "\\ifdim\\XeTeXglyphbounds3 100=-65535sp \\else\\errmessage{right bearing}\\fi",
8574            // The mock shapes a word one point per character and measures a lone glyph as empty.
8575            "\\setbox0\\hbox{d\\/}\\ifdim\\wd0=2pt \\else\\errmessage{word correction}\\fi",
8576            "\\setbox0\\hbox{\\XeTeXglyph100\\/}\\ifdim\\wd0=1pt \\else\\errmessage{glyph correction}\\fi\\end",
8577        );
8578        assert!(run_initex(&mut engine, input), "{:?}", engine.last_error_message());
8579    }
8580
8581    #[test]
8582    fn snapshots_expose_leaders_unset_boxes_and_noad_kinds() {
8583        let mut engine = initialized_engine();
8584        let root = captured_root(&mut engine, "\\hbox{\\leaders\\hrule\\hskip 10pt}\\end");
8585        let glue = list_snapshots(&engine, root.list).remove(0);
8586        assert_eq!((glue.kind, glue.width), (PortableNodeKind::Glue, 10 * 65536));
8587        let leader = engine.snapshot_node(glue.leader.expect("a leader")).expect("leader node");
8588        assert_eq!(leader.kind, PortableNodeKind::Rule);
8589        let node = unsafe { engine.zgetnode(7) }.expect("allocates");
8590        let slot = (node - engine.state.memmin) as usize;
8591        let words = &mut engine.heap.mem[slot..slot + 7];
8592        words[0].hh.u.B0 = UNSET_NODE as i16;
8593        words[1].u.CINT = 11;
8594        words[4].u.CINT = 22;
8595        words[5].hh.u.B0 = 1;
8596        words[5].hh.u.B1 = 2;
8597        words[6].u.CINT = 33;
8598        let unset = engine.snapshot_node(PortableNodeHandle(node)).expect("unset node");
8599        assert_eq!(
8600            (unset.width, unset.shift, unset.glue_set, unset.glue_stretch, unset.glue_shrink),
8601            (11, 0, 0.0, 33, 22)
8602        );
8603        assert_eq!((unset.glue_stretch_order, unset.glue_shrink_order), (2, 1));
8604        for (noad_type, kind) in [(ORD_NOAD, PortableNoadKind::Ord), (FRACTION_NOAD, PortableNoadKind::Fraction), (RIGHT_NOAD, PortableNoadKind::Right)] {
8605            engine.heap.mem[slot].hh.u.B0 = noad_type as i16;
8606            let noad = engine.snapshot_node(PortableNodeHandle(node)).expect("noad");
8607            assert_eq!(noad.kind, PortableNodeKind::Noad(kind));
8608        }
8609    }
8610}