Skip to main content

mathtex_portable_engine_generated/
runtime.rs

1//! Runtime prelude for auto-patched Web2C/C2Rust engine source.
2//! Generated code must be lowered from raw globals into `PortableTexState`
3//! before it is linked into `mathtex-engine`.
4
5#![allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
6
7pub(crate) type integer = i32;
8pub(crate) type size_t = usize;
9pub(crate) type int32_t = i32;
10pub(crate) type uint32_t = u32;
11pub(crate) type real = f64;
12pub(crate) type glueratio = f64;
13pub(crate) type boolean = i32;
14pub(crate) type schar = i8;
15pub(crate) type ASCIIcode = integer;
16pub(crate) type eightbits = integer;
17pub(crate) type poolpointer = integer;
18pub(crate) type strnumber = integer;
19pub(crate) type savepointer = integer;
20pub(crate) type packedASCIIcode = u16;
21pub(crate) type packedUTF16code = u16;
22pub(crate) type scaled = integer;
23pub(crate) type nonnegativeinteger = integer;
24pub(crate) type smallnumber = integer;
25pub(crate) type quarterword = integer;
26pub(crate) type halfword = integer;
27pub(crate) type glueord = integer;
28pub(crate) type groupcode = integer;
29pub(crate) type internalfontnumber = integer;
30pub(crate) type fontindex = integer;
31pub(crate) type ninebits = integer;
32pub(crate) type triepointer = integer;
33pub(crate) type trieopcode = integer;
34pub(crate) type hyphpointer = integer;
35pub(crate) type UTF16code = u16;
36pub(crate) type UnicodeScalar = integer;
37pub(crate) type uint16_t = u16;
38pub(crate) type UTF8code = integer;
39pub(crate) type voidpointer = *mut ();
40pub(crate) type address = voidpointer;
41pub(crate) type string = *mut i8;
42pub(crate) type const_string = *const i8;
43pub(crate) type Fixed = int32_t;
44pub(crate) struct PortableFileHandle {
45    name: String,
46    kind: ResourceKind,
47    package: Option<String>,
48    format: integer,
49    bytes: Vec<u8>,
50    cursor: usize,
51    eof_after_failed_read: bool,
52    /// Input encoding used to decode this file's bytes into Unicode scalars.
53    /// `Bytes` (XeTeX `RAW`) reads each byte verbatim; `Utf8`/`Utf16Be`/`Utf16Le`
54    /// reproduce XeTeX's `get_uni_c` decoders. Binary inputs (tfm/font/fmt) are
55    /// always `Bytes`.
56    encoding: InputEncoding,
57    /// One-unit lookahead for the UTF-16 surrogate decoder (XeTeX `savedChar`).
58    saved_char: Option<u32>,
59}
60
61/// Input decoding mode for a [`PortableFileHandle`], mirroring XeTeX's encoding
62/// modes (`xetex.h`): `Bytes` corresponds to `RAW`. `AUTO`/`ICUMAPPING` are not
63/// stored here — `AUTO` is resolved to a concrete mode at open time (BOM sniff),
64/// and ICU mappings degrade to `Bytes`.
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
66pub enum InputEncoding {
67    /// Raw bytes, one Unicode scalar per byte (XeTeX `RAW`).
68    #[default]
69    Bytes,
70    /// UTF-8 (XeTeX `UTF8`).
71    Utf8,
72    /// UTF-16 big-endian (XeTeX `UTF16BE`).
73    Utf16Be,
74    /// UTF-16 little-endian (XeTeX `UTF16LE`).
75    Utf16Le,
76}
77pub(crate) type bytefile = NativeFileHandle;
78pub(crate) type unicodefile = NativeFileHandle;
79pub(crate) type ResourceSearchHandle = *mut ResourceSearchState;
80pub type PortableFontHandle = usize;
81pub(crate) type FontHandle = PortableFontHandle;
82pub(crate) type CFDictionaryRef = voidpointer;
83pub(crate) type NativeFileHandle = *mut PortableFileHandle;
84pub(crate) type alphafile = NativeFileHandle;
85pub(crate) type UFILE = PortableFileHandle;
86
87pub(crate) const true_0: boolean = 1;
88pub(crate) const false_0: boolean = 0;
89pub(crate) const firstmathfontdimen: integer = 10;
90pub(crate) const native_node_size: integer = 6;
91
92#[derive(Debug)]
93pub(crate) struct EngineAbort {
94    status: integer,
95}
96
97/// A surfaced, recoverable engine error: a TeX `! ...` diagnostic (undefined
98/// control sequence, bad argument, runaway, or a sandbox-rule violation). Unlike
99/// [`EngineAbort`] -- the fatal `jump_out`/`fatal_error`/`overflow` channel --
100/// this carries the captured message so the host can report *what* went wrong
101/// instead of only that the run aborted.
102#[derive(Debug)]
103pub(crate) struct EngineError {
104    pub(crate) message: String,
105}
106
107/// The engine's non-`Ok` outcomes. `Abort` is the fatal jump_out/overflow
108/// channel; `Error` is a surfaced TeX error carrying its message. This is the
109/// `Err` payload of a `Result` rather than a bespoke enum replacing `Result`,
110/// so the `?` operator keeps working across the ~120 generated bodies -- stable
111/// Rust `?` is `Result`/`Option`-only.
112#[derive(Debug)]
113pub(crate) enum EngineBreak {
114    Abort(EngineAbort),
115    Error(EngineError),
116}
117
118impl From<EngineAbort> for EngineBreak {
119    fn from(abort: EngineAbort) -> Self {
120        EngineBreak::Abort(abort)
121    }
122}
123
124/// Collapse a uniformly character-doubled line back to its original. TeX's
125/// `term_and_log` selector writes each byte to both the terminal and the log,
126/// and in this headless build both feed the single transcript buffer, so a
127/// diagnostic such as `! Undefined control sequence.` arrives with every
128/// character repeated (`!! Undefined ...`). Returns `Some` only when the line is
129/// exactly 2x doubled (even length, every adjacent pair equal); otherwise `None`
130/// so genuinely non-doubled lines pass through untouched.
131fn collapse_doubled_line(line: &str) -> Option<String> {
132    let chars: Vec<char> = line.chars().collect();
133    if chars.len() < 2 || chars.len() % 2 != 0 {
134        return None;
135    }
136    if chars.chunks_exact(2).all(|pair| pair[0] == pair[1]) {
137        Some(chars.iter().step_by(2).collect())
138    } else {
139        None
140    }
141}
142
143/// Main-control iteration budget for a sandboxed fragment render. Far more than
144/// any real expression needs, but bounds infinite loops so a malicious or
145/// mistaken input (`\def\x{\x}\x`) cannot hang the host.
146pub(crate) const SANDBOX_OP_BUDGET: u64 = 2_000_000;
147
148/// Non-unwinding abort/error channel. Functions that can reach the engine's
149/// fatal `jump_out`/`fatal_error`/`overflow` paths -- or that surface a TeX
150/// error -- return this instead of diverging via `panic_any`, so the engine
151/// runs under `panic=abort` (wasm). The `?` operator threads the `Err(EngineBreak)`
152/// straight back to the driver boundary in [`PortableTexEngine::catch_engine_abort`].
153/// The alias keeps signatures off the bare `Result` identifier, which is shadowed
154/// by ~120 local `Result` bindings in the generated bodies; `core::result::Result`
155/// is `no_std`-safe.
156pub(crate) type EngineFlow<T> = core::result::Result<T, EngineBreak>;
157pub(crate) const nullptr: voidpointer = core::ptr::null_mut::<()>();
158pub(crate) const nil: voidpointer = core::ptr::null_mut::<()>();
159pub(crate) const maxint: integer = i32::MAX;
160pub(crate) const mintrieop: integer = 0;
161pub(crate) const trieopsize: i64 = 35111;
162pub(crate) const negtrieopsize: i64 = -35111;
163pub(crate) const maxtrieop: i64 = 65535;
164pub(crate) const hashoffset: integer = 514;
165pub(crate) const xetex_hash_top: halfword = 1_205_763;
166pub(crate) const xetex_eqtb_top: halfword = 9_006_997;
167pub(crate) const DIR_SEP: integer = b'/' as integer;
168pub(crate) const resource_format_tex_input: integer = 26;
169pub(crate) const resource_format_tfm: integer = 3;
170pub(crate) const resource_format_format_image: integer = 10;
171pub(crate) const resource_format_config: integer = 8;
172pub(crate) const resource_format_font_map: integer = 11;
173pub(crate) const resource_format_encoding: integer = 44;
174pub(crate) const resource_format_font: integer = 47;
175pub(crate) const FOPEN_RBIN_MODE: [i8; 3] = [b'r' as i8, b'b' as i8, 0];
176
177#[derive(Clone, Debug, PartialEq, Eq)]
178pub struct PortableSourceSpan {
179    pub name: String,
180    pub start: u32,
181    pub end: u32,
182    /// Provenance role, mirroring the IR `SourceRole` discriminant: 0=Primary,
183    /// 1=MacroExpansion. Set deterministically by the stamping site, never
184    /// guessed.
185    pub role: u8,
186}
187
188/// Interned source-span id used by the (feature-gated) source-tracking subsystem.
189/// `0` is the canonical NONE; real spans start at `1`. The id indexes
190/// `PortableTexState::src_spans`.
191pub(crate) type SrcId = u32;
192
193/// One recorded source span in a *source file's own* character coordinates
194/// (NOT wrapper-relative). `name` is `curinput.namefield` (the source-file
195/// string number); `start`/`end` are character offsets in that file's input.
196/// `role` matches [`PortableSourceSpan::role`]. Hashed/equated by all fields so
197/// the intern table assigns stable first-touch ids.
198#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
199pub(crate) struct RawSpan {
200    pub name: strnumber,
201    pub start: u32,
202    pub end: u32,
203    pub role: u8,
204}
205
206/// `default_fn` for the `node_src` paged shadow: an unstamped node is NONE.
207fn node_src_default(_i: usize) -> u32 {
208    0
209}
210
211/// Significant-byte range for `node_src` words (the whole `u32` is live).
212fn node_src_sig(_i: usize) -> (usize, usize) {
213    (0, 4)
214}
215
216/// One frame on the enclosing-construct stack (source-tracking inc2). A frame is
217/// an interned construct extent (a macro invocation or a delimited primitive
218/// argument group) plus a link to its parent frame, forming a per-node snapshot
219/// of the construct nesting that was live when the node was allocated. Group
220/// frames are PENDING between their `{` open and `}` close (the closing-delimiter
221/// offset is unknown until the close), finalized in place at the close. Cells are
222/// addressed 1-based via [`PortableTexState::cur_stack_head`] / `node_stack`
223/// (`0` = no enclosure). Transient per-render parse state, cleared with the rest
224/// of the source-tracking tables.
225#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
226pub(crate) struct SrcStackCell {
227    /// Finalized interned construct-extent span id (`0` while pending / none).
228    pub span: SrcId,
229    /// Parent frame index (1-based; `0` = root / no parent).
230    pub parent: u32,
231    /// Pending group: char offset of the enclosing command's start.
232    pub start: u32,
233    /// Pending group: source-file string number the offsets live in.
234    pub name: strnumber,
235    /// True while a group frame is open (its end offset is not yet known).
236    pub pending: bool,
237}
238
239/// `default_fn` for the `node_stack` paged shadow: an unstamped node has no
240/// enclosing frame (`0`).
241fn node_stack_default(_i: usize) -> u32 {
242    0
243}
244
245/// Significant-byte range for `node_stack` words (the whole `u32` is live).
246fn node_stack_sig(_i: usize) -> (usize, usize) {
247    (0, 4)
248}
249
250#[derive(Clone, Debug, PartialEq, Eq)]
251pub struct PortableNodeSourceSpan {
252    pub node: i32,
253    pub size: i32,
254    pub source: PortableSourceSpan,
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258pub enum ResourceKind {
259    TexInput,
260    Package,
261    Class,
262    FontDefinition,
263    PackageSupport,
264    Font,
265    Encoding,
266    Map,
267    Config,
268    FormatImage,
269    Asset,
270    Other(integer),
271}
272
273#[derive(Clone, Debug, PartialEq, Eq)]
274pub struct ResourceRequest<'a> {
275    pub name: &'a str,
276    pub kind: ResourceKind,
277    pub package: Option<&'a str>,
278    pub format: integer,
279    pub mode: &'a str,
280    pub source: Option<PortableSourceSpan>,
281}
282
283#[derive(Clone, Debug, PartialEq, Eq)]
284pub struct PortableResourceRequestRecord {
285    pub name: String,
286    pub kind: ResourceKind,
287    pub package: Option<String>,
288    pub format: integer,
289    pub mode: String,
290    pub source: Option<PortableSourceSpan>,
291    pub byte_len: Option<u32>,
292}
293
294#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
295pub struct PortableFontMetrics {
296    pub ascent: i32,
297    pub descent: i32,
298    pub xheight: i32,
299    pub capheight: i32,
300    pub slant: i32,
301}
302
303#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
304pub struct PortableNativeGlyph {
305    pub glyph_id: u16,
306    pub x: i32,
307    pub y: i32,
308    pub advance: i32,
309    pub cluster_start: u32,
310    pub cluster_end: u32,
311    /// SOURCE byte span (in the producing node's `primary_source.source`
312    /// coordinates) of the input char(s) that shaped this glyph, resolved from
313    /// the per-code-unit tracked spans. `0/0` means unmapped (tracking off, no
314    /// source, cross-source, or stale). Set by `src_resolve_native_glyphs`; the
315    /// rendering fields (`glyph_id`/`x`/`y`/`advance`) are never touched.
316    pub src_start: u32,
317    pub src_end: u32,
318}
319
320#[derive(Clone, Debug, Default, PartialEq, Eq)]
321pub struct PortableNativeTextMetrics {
322    pub width: i32,
323    pub height: i32,
324    pub depth: i32,
325    pub glyphs: Vec<PortableNativeGlyph>,
326}
327
328#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
329pub struct PortableNativeGlyphMetrics {
330    pub width: i32,
331    pub height: i32,
332    pub depth: i32,
333}
334
335#[derive(Clone, Debug, Default, PartialEq, Eq)]
336struct PortableNativeGlyphInfo {
337    glyphs: Vec<PortableNativeGlyph>,
338}
339
340/// A larger OpenType MATH glyph variant: the variant glyph id and its advance in
341/// scaled points (`-1` advance means "no such variant").
342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343pub struct PortableMathVariant {
344    pub glyph: i32,
345    pub advance: i32,
346}
347
348/// One part of an OpenType MATH glyph assembly, all measurements in scaled
349/// points (mirrors `hb_ot_math_glyph_part_t` / ttf-parser `GlyphPart`).
350#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
351pub struct PortableMathAssemblyPart {
352    pub glyph: i32,
353    pub start_connector: i32,
354    pub end_connector: i32,
355    pub full_advance: i32,
356    pub extender: bool,
357}
358
359/// A corner of an OpenType `MathKernInfo` record (mirrors `hb_ot_math_kern_t`).
360#[derive(Clone, Copy, Debug, PartialEq, Eq)]
361pub enum PortableMathKernCorner {
362    TopRight,
363    TopLeft,
364    BottomRight,
365    BottomLeft,
366}
367
368/// Heap-owned OpenType MATH glyph assembly handed to the engine as an opaque
369/// pointer by `get_ot_assembly_ptr` and reclaimed by `free_ot_assembly`.
370///
371/// This replaces XeTeX's C `GlyphAssembly` (`{count; hb_ot_math_glyph_part_t*}`)
372/// with a safe Rust struct: it is allocated with `Box::into_raw` and freed with
373/// `Box::from_raw`, never libc. `build_opentype_assembly` (the sole consumer in
374/// this engine) reads it directly in Rust.
375#[derive(Clone, Debug, Default, PartialEq, Eq)]
376pub struct GlyphAssembly {
377    pub parts: Vec<PortableMathAssemblyPart>,
378}
379
380pub trait FontPlatform {
381    fn resolve_font_handle(&mut self, _name: &[i32], _size: i32) -> Option<PortableFontHandle> {
382        None
383    }
384
385    fn release_font_handle(&mut self, _font: PortableFontHandle, _type_flag: i32) {}
386
387    /// Snapshot the platform's native-font table as `(handle, spec, size)`
388    /// tuples. A serialized format image keeps integer font handles in
389    /// `fontlayoutengine`, but those handles only mean something to the platform
390    /// that minted them; capturing the table lets a cold load rebind them.
391    fn font_table(&self) -> Vec<(PortableFontHandle, String, i32)> {
392        Vec::new()
393    }
394
395    /// Rebuild the native-font table from a [`Self::font_table`] snapshot,
396    /// (re)loading each font *at its original handle* so the `fontlayoutengine`
397    /// handles preserved in a reloaded format image resolve again. Returns
398    /// `false` if any font failed to load.
399    fn restore_font_table(&mut self, _table: &[(PortableFontHandle, String, i32)]) -> bool {
400        true
401    }
402
403    fn font_metrics(&mut self, _font: PortableFontHandle) -> PortableFontMetrics {
404        PortableFontMetrics::default()
405    }
406
407    fn opentype_font_metrics(&mut self, font: PortableFontHandle) -> PortableFontMetrics {
408        self.font_metrics(font)
409    }
410
411    fn is_opentype_math_font(&mut self, _font: PortableFontHandle) -> bool {
412        false
413    }
414
415    /// Whether `font` is shaped through the OpenType (HarfBuzz/rustybuzz) shaper.
416    /// Mirrors XeTeX's `usingOpenType`, which is true for the `"ot"` shaper (the
417    /// default). This engine always shapes native fonts with rustybuzz, so any
418    /// loaded native font handle returns `true`.
419    fn using_opentype(&mut self, _font: PortableFontHandle) -> bool {
420        false
421    }
422
423    fn math_symbol_parameter(&mut self, _font: PortableFontHandle, _parameter: i32) -> i32 {
424        0
425    }
426
427    fn math_extension_parameter(&mut self, _font: PortableFontHandle, _parameter: i32) -> i32 {
428        0
429    }
430
431    fn opentype_math_constant(&mut self, _font: PortableFontHandle, _constant: i32) -> i32 {
432        0
433    }
434
435    fn opentype_math_accent_position(&mut self, _font: PortableFontHandle, _glyph: i32) -> i32 {
436        0
437    }
438
439    /// OpenType MATH italic correction for a glyph, in scaled points.
440    fn math_glyph_italic_correction(&mut self, _font: PortableFontHandle, _glyph: i32) -> i32 {
441        0
442    }
443
444    /// The `index`-th larger MATH glyph variant of `glyph` (horizontal or
445    /// vertical), or `None` when there is no such variant. The advance is in
446    /// scaled points.
447    fn math_glyph_variant(
448        &mut self,
449        _font: PortableFontHandle,
450        _glyph: i32,
451        _index: u16,
452        _horizontal: bool,
453    ) -> Option<PortableMathVariant> {
454        None
455    }
456
457    /// The MATH glyph-assembly parts for `glyph` (horizontal or vertical), each
458    /// metric in scaled points. Empty when the glyph has no assembly.
459    fn math_glyph_assembly(
460        &mut self,
461        _font: PortableFontHandle,
462        _glyph: i32,
463        _horizontal: bool,
464    ) -> Vec<PortableMathAssemblyPart> {
465        Vec::new()
466    }
467
468    /// The MATH minimum connector overlap for assembly parts, in scaled points.
469    fn math_min_connector_overlap(&mut self, _font: PortableFontHandle) -> i32 {
470        0
471    }
472
473    /// One MATH `MathKernInfo` corner evaluated at `correction_height` (font
474    /// design units), returned in raw font design units (NOT scaled).
475    fn math_kern_at(
476        &mut self,
477        _font: PortableFontHandle,
478        _glyph: i32,
479        _corner: PortableMathKernCorner,
480        _correction_height: i32,
481    ) -> i32 {
482        0
483    }
484
485    /// Convert a measurement in points to font design units for `font`
486    /// (`pointsToUnits`).
487    fn math_points_to_units(&mut self, _font: PortableFontHandle, _points: f32) -> f32 {
488        0.0
489    }
490
491    /// Convert a measurement in font design units to scaled points for `font`
492    /// (`D2Fix(unitsToPoints(...))`).
493    fn math_units_to_scaled(&mut self, _font: PortableFontHandle, _units: i32) -> i32 {
494        0
495    }
496
497    /// The point size at which `font` was loaded (`getPointSize`).
498    fn math_point_size(&mut self, _font: PortableFontHandle) -> f32 {
499        0.0
500    }
501
502    fn map_char_to_glyph(&mut self, _font: PortableFontHandle, _codepoint: i32) -> i32 {
503        0
504    }
505
506    fn map_glyph_to_index(&mut self, _font: PortableFontHandle, _name: &str) -> i32 {
507        0
508    }
509
510    /// OpenType layout enumeration backing the `\XeTeXOT*` / `\XeTeXcountglyphs`
511    /// `last_item` primitives (XeTeX `ot_font_get`/`_1`/`_2`/`_3`). `what` is the
512    /// XeTeX_ext code (1 = count glyphs, 16 = count scripts, 17 = count
513    /// languages, 18 = count features, 19 = script tag, 20 = language tag,
514    /// 21 = feature tag); unused params are 0.
515    fn ot_font_get(
516        &mut self,
517        _font: PortableFontHandle,
518        _what: i32,
519        _param1: i32,
520        _param2: i32,
521        _param3: i32,
522    ) -> i32 {
523        0
524    }
525
526    /// The `\font` spec string a loaded handle was created from (e.g.
527    /// `[latinmodern-math.otf]:script=math;ssty=1`). Immutable so the read-only
528    /// IR-building path can recover the originating font file for a glyph run.
529    fn font_spec(&self, _font: PortableFontHandle) -> Option<String> {
530        None
531    }
532
533    fn shape_native_text(
534        &mut self,
535        _font: PortableFontHandle,
536        _text: &[u16],
537        _use_glyph_metrics: bool,
538    ) -> PortableNativeTextMetrics {
539        PortableNativeTextMetrics::default()
540    }
541
542    fn measure_native_glyph(
543        &mut self,
544        _font: PortableFontHandle,
545        _glyph: u16,
546        _use_glyph_metrics: bool,
547    ) -> PortableNativeGlyphMetrics {
548        PortableNativeGlyphMetrics::default()
549    }
550}
551
552#[derive(Default)]
553pub struct EmptyFontPlatform;
554
555impl FontPlatform for EmptyFontPlatform {}
556
557pub trait ResourceProvider {
558    fn read(&mut self, request: ResourceRequest<'_>) -> Option<Vec<u8>>;
559}
560
561#[derive(Default)]
562pub struct EmptyResourceProvider;
563
564impl ResourceProvider for EmptyResourceProvider {
565    fn read(&mut self, _request: ResourceRequest<'_>) -> Option<Vec<u8>> {
566        None
567    }
568}
569
570#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
571pub struct PortableClock {
572    pub seconds: integer,
573    pub micros: integer,
574}
575
576#[derive(Clone, Copy, Debug, PartialEq, Eq)]
577pub struct PortableLinebreakRequest<'a> {
578    pub font: integer,
579    pub locale: integer,
580    pub text: &'a [uint16_t],
581}
582
583pub trait PortablePlatform {
584    fn clock(&mut self) -> PortableClock {
585        PortableClock::default()
586    }
587
588    fn linebreak_start(&mut self, _request: PortableLinebreakRequest<'_>) {}
589
590    fn linebreak_next(&mut self) -> Option<integer> {
591        None
592    }
593}
594
595#[derive(Default)]
596pub struct EmptyPlatform;
597
598impl PortablePlatform for EmptyPlatform {}
599
600impl PortableFileHandle {
601    fn new(
602        name: String,
603        kind: ResourceKind,
604        package: Option<String>,
605        format: integer,
606        bytes: Vec<u8>,
607    ) -> Self {
608        Self {
609            name,
610            kind,
611            package,
612            format,
613            bytes,
614            cursor: 0,
615            eof_after_failed_read: false,
616            encoding: InputEncoding::Bytes,
617            saved_char: None,
618        }
619    }
620
621    fn read_byte(&mut self) -> Option<u8> {
622        let Some(byte) = self.bytes.get(self.cursor).copied() else {
623            self.eof_after_failed_read = true;
624            return None;
625        };
626        self.cursor += 1;
627        Some(byte)
628    }
629
630    fn has_remaining(&self) -> bool {
631        self.cursor < self.bytes.len()
632    }
633
634    fn is_eof(&self) -> bool {
635        self.eof_after_failed_read
636    }
637
638    /// Resolve the input encoding for a freshly opened TEXT file, reproducing
639    /// XeTeX's `u_open_in` AUTO byte-order-mark sniff and consuming the BOM by
640    /// advancing the initial cursor. Only meaningful for text inputs under the
641    /// XeTeX profile; binary inputs and non-XeTeX profiles stay `Bytes`.
642    ///
643    /// XeTeX (`u_open_in`, AUTO mode):
644    ///   * `FE FF`         -> UTF16BE, consume 2 (BOM)
645    ///   * `FF FE`         -> UTF16LE, consume 2 (BOM)
646    ///   * `00 xx (xx!=0)` -> UTF16BE, rewind (no consume)
647    ///   * `xx 00 (xx!=0)` -> UTF16LE, rewind (no consume)
648    ///   * `EF BB BF`      -> UTF8,    consume 3 (BOM)
649    ///   * otherwise       -> UTF8,    rewind (no consume)
650    fn resolve_text_encoding_auto(&mut self) {
651        let b1 = self.bytes.first().copied();
652        let b2 = self.bytes.get(1).copied();
653        match (b1, b2) {
654            (Some(0xFE), Some(0xFF)) => {
655                self.encoding = InputEncoding::Utf16Be;
656                self.cursor = 2;
657            }
658            (Some(0xFF), Some(0xFE)) => {
659                self.encoding = InputEncoding::Utf16Le;
660                self.cursor = 2;
661            }
662            (Some(0x00), Some(b2)) if b2 != 0 => {
663                self.encoding = InputEncoding::Utf16Be;
664                // rewind: no BOM consumed.
665            }
666            (Some(b1), Some(0x00)) if b1 != 0 => {
667                self.encoding = InputEncoding::Utf16Le;
668            }
669            (Some(0xEF), Some(0xBB)) if self.bytes.get(2).copied() == Some(0xBF) => {
670                self.encoding = InputEncoding::Utf8;
671                self.cursor = 3;
672            }
673            _ => {
674                self.encoding = InputEncoding::Utf8;
675            }
676        }
677    }
678
679    /// Decode the next Unicode scalar from the input, dispatching on the file's
680    /// encoding. Returns `None` at end of input (XeTeX `EOF`). Faithful port of
681    /// XeTeX's `get_uni_c` (`XeTeX_ext.c`): `GETC` == [`read_byte`], `UNGETC` ==
682    /// `cursor -= 1`, and `savedChar` == [`saved_char`].
683    fn next_input_scalar(&mut self) -> Option<u32> {
684        // savedChar lookahead (only set by the UTF-16 decoders).
685        if let Some(saved) = self.saved_char.take() {
686            return Some(saved);
687        }
688        match self.encoding {
689            InputEncoding::Bytes => self.read_byte().map(u32::from),
690            InputEncoding::Utf8 => self.next_utf8_scalar(),
691            InputEncoding::Utf16Be => self.next_utf16_scalar(true),
692            InputEncoding::Utf16Le => self.next_utf16_scalar(false),
693        }
694    }
695
696    /// UTF-8 branch of `get_uni_c`. Uses the `bytesFromUTF8` extra-byte count with
697    /// fall-through continuation reads; a continuation outside `0x80..=0xBF` is
698    /// bad UTF-8 -> UNGETC the offending byte and return U+FFFD; the assembled
699    /// value is corrected by `offsetsFromUTF8` and range-checked.
700    fn next_utf8_scalar(&mut self) -> Option<u32> {
701        // offsetsFromUTF8[extraBytes].
702        const OFFSETS: [u32; 6] = [
703            0x0000_0000,
704            0x0000_3080,
705            0x000E_2080,
706            0x03C8_2080,
707            0xFA08_2080,
708            0x8208_2080,
709        ];
710        let lead = self.read_byte()?;
711        // bytesFromUTF8[lead]: extra continuation bytes (0..=5).
712        let extra: usize = match lead {
713            0x00..=0xBF => 0,
714            0xC0..=0xDF => 1,
715            0xE0..=0xEF => 2,
716            0xF0..=0xF7 => 3,
717            0xF8..=0xFB => 4,
718            0xFC..=0xFF => 5,
719        };
720        // Assemble in a wider type to mirror C's `int rval` (continuations <<6).
721        let mut rval: i64 = i64::from(lead);
722        // C `switch(extraBytes)` falls through cases 3,2,1; cases 4,5 jump
723        // straight to the bad-utf8 return without reading continuations.
724        if extra >= 4 {
725            // Lead bytes 0xF8..0xFF: no valid 4/5-byte sequence; return U+FFFD
726            // without consuming further (mirrors the `case 5/4:` bad path).
727            return Some(0xFFFD);
728        }
729        for _ in 0..extra {
730            match self.read_byte() {
731                Some(c) if (0x80..0xC0).contains(&c) => {
732                    rval = (rval << 6) + i64::from(c);
733                }
734                Some(c) => {
735                    // bad_utf8: UNGETC the offending byte, return U+FFFD.
736                    self.cursor -= 1;
737                    let _ = c;
738                    return Some(0xFFFD);
739                }
740                None => {
741                    // EOF mid-sequence: bad_utf8 without UNGETC.
742                    return Some(0xFFFD);
743                }
744            }
745        }
746        rval -= i64::from(OFFSETS[extra]);
747        if rval < 0 || rval > 0x10_FFFF {
748            return Some(0xFFFD);
749        }
750        Some(rval as u32)
751    }
752
753    /// UTF-16 branch of `get_uni_c` for big- or little-endian. A high surrogate
754    /// (`D800..=DBFF`) pulls a second unit; a matching low surrogate
755    /// (`DC00..=DFFF`) combines to a supplementary scalar, otherwise U+FFFD and
756    /// the stray unit is stashed in `saved_char`. A lone low surrogate -> U+FFFD.
757    fn next_utf16_scalar(&mut self, big_endian: bool) -> Option<u32> {
758        let unit = self.read_utf16_unit(big_endian)?;
759        if (0xD800..=0xDBFF).contains(&unit) {
760            // High surrogate: read the low surrogate (may hit EOF).
761            match self.read_utf16_unit(big_endian) {
762                Some(lo) if (0xDC00..=0xDFFF).contains(&lo) => {
763                    Some(0x10000 + (unit - 0xD800) * 0x400 + (lo - 0xDC00))
764                }
765                Some(lo) => {
766                    self.saved_char = Some(lo);
767                    Some(0xFFFD)
768                }
769                None => Some(0xFFFD),
770            }
771        } else if (0xDC00..=0xDFFF).contains(&unit) {
772            // Lone low surrogate.
773            Some(0xFFFD)
774        } else {
775            Some(unit)
776        }
777    }
778
779    /// Read one 16-bit UTF-16 code unit (two bytes) in the given endianness.
780    /// Returns `None` only when the first byte is at EOF (mirrors `GETC` ==
781    /// `EOF`); a missing trailing byte is treated as `0` like C's `GETC`/`<<8`.
782    fn read_utf16_unit(&mut self, big_endian: bool) -> Option<u32> {
783        let first = self.read_byte()?;
784        let second = self.read_byte().unwrap_or(0);
785        let unit = if big_endian {
786            (u32::from(first) << 8) + u32::from(second)
787        } else {
788            u32::from(first) + (u32::from(second) << 8)
789        };
790        Some(unit)
791    }
792}
793
794pub(crate) trait StatePtrCompat<T> {
795    fn as_mut_ptr(self) -> *mut T;
796    fn is_empty(self) -> bool;
797}
798
799impl<T> StatePtrCompat<T> for *mut T {
800    fn as_mut_ptr(self) -> *mut T {
801        self
802    }
803
804    fn is_empty(self) -> bool {
805        self.is_null()
806    }
807}
808
809#[derive(Copy, Clone)]
810#[repr(C)]
811pub(crate) union twohalves {
812    pub v: TwoHalvesPair,
813    pub u: TwoHalvesBytes,
814}
815
816impl Default for twohalves {
817    fn default() -> Self {
818        Self {
819            v: TwoHalvesPair::default(),
820        }
821    }
822}
823
824#[derive(Copy, Clone, Default)]
825#[repr(C)]
826pub(crate) struct TwoHalvesBytes {
827    pub B1: i16,
828    pub B0: i16,
829}
830
831#[derive(Copy, Clone, Default)]
832#[repr(C)]
833pub(crate) struct TwoHalvesPair {
834    pub LH: halfword,
835    pub RH: halfword,
836}
837
838#[derive(Copy, Clone)]
839#[repr(C)]
840pub(crate) struct fourquarters {
841    pub u: FourQuarterBytes,
842}
843
844impl Default for fourquarters {
845    fn default() -> Self {
846        Self {
847            u: FourQuarterBytes::default(),
848        }
849    }
850}
851
852// Real XeTeX packs a `memory_word` into 8 bytes: its `four_quarters` view is
853// four *16-bit* quarterwords. The c2rust translation widened these to 32-bit
854// (`quarterword = i32`), tripling every `memory_word`/`font_memory_word` to 24
855// bytes (and with it `mem`, `eqtb`, `fontinfo`). Storing them as `u16` — XeTeX's
856// real unsigned quarterword width (0..=65535, enough for native glyph ids) —
857// restores the packed layout (memory_word 24 -> 16 bytes). The `quarterword`
858// alias stays `i32` for general arithmetic; only this storage view is narrowed.
859// NOTE: the c2rust functions/*.rs write/read these fields with `as quarterword`
860// (i32); the `FourQuarterCastPass` in tools/web2c-import wraps those sites with
861// `as u16`/`as i32` so they stay type-correct against this narrowed view.
862#[derive(Copy, Clone, Default)]
863#[repr(C)]
864pub(crate) struct FourQuarterBytes {
865    pub B3: u16,
866    pub B2: u16,
867    pub B1: u16,
868    pub B0: u16,
869}
870
871pub(crate) type C2RustUnnamed_2 = FourQuarterBytes;
872
873#[derive(Copy, Clone)]
874#[repr(C)]
875pub(crate) union memoryword {
876    pub gr: glueratio,
877    pub hh: twohalves,
878    pub u: MemoryInt,
879    pub v: MemoryQuarters,
880    pub ptr: voidpointer,
881}
882
883impl Default for memoryword {
884    fn default() -> Self {
885        Self {
886            u: MemoryInt::default(),
887        }
888    }
889}
890
891// Packed to match real XeTeX (and the already-packed `fmemoryword`): the
892// `four_quarters`/`cint` views sit at offset 0, not behind a `junk` halfword.
893// Dropping `junk` takes `memory_word` from 16 -> 8 bytes (`MemoryQuarters` was
894// the union's largest variant at junk(4)+qqqq(8)=12, rounded to 16). TeX accesses
895// each live word through exactly one view, so the `cint`/`qqqq` <-> `hh.rh`
896// offset-4 aliasing the c2rust output happened to expose is never relied upon.
897#[derive(Copy, Clone, Default)]
898#[repr(C)]
899pub(crate) struct MemoryQuarters {
900    pub QQQQ: fourquarters,
901}
902
903#[derive(Copy, Clone, Default)]
904#[repr(C)]
905pub(crate) struct MemoryInt {
906    pub CINT: integer,
907}
908
909#[derive(Copy, Clone)]
910#[repr(C)]
911pub(crate) union fmemoryword {
912    pub u: FontMemoryInt,
913    pub v: FontMemoryQuarters,
914}
915
916impl Default for fmemoryword {
917    fn default() -> Self {
918        Self {
919            u: FontMemoryInt::default(),
920        }
921    }
922}
923
924#[derive(Copy, Clone, Default)]
925#[repr(C)]
926pub(crate) struct FontMemoryQuarters {
927    pub QQQQ: fourquarters,
928}
929
930#[derive(Copy, Clone, Default)]
931#[repr(C)]
932pub(crate) struct FontMemoryInt {
933    pub CINT: integer,
934}
935
936#[derive(Copy, Clone, Default)]
937#[repr(C)]
938pub(crate) struct liststaterecord {
939    pub modefield: i16,
940    pub headfield: halfword,
941    pub tailfield: halfword,
942    pub eTeXauxfield: halfword,
943    pub pgfield: integer,
944    pub mlfield: integer,
945    pub auxfield: memoryword,
946}
947
948#[derive(Copy, Clone, Default)]
949#[repr(C)]
950pub(crate) struct instaterecord {
951    pub statefield: quarterword,
952    pub indexfield: quarterword,
953    pub startfield: halfword,
954    pub locfield: halfword,
955    pub limitfield: halfword,
956    pub namefield: halfword,
957    /// Source-tracking AMBIENT span for this input level (feature-gated). Rides
958    /// the existing whole-record input-stack push (`zbegintokenlist`) / pop
959    /// (`endtokenlist`/`endfilereading`) save/restore with zero extra code, so a
960    /// macro/file level's inherited span is torn down automatically on pop. `0`
961    /// (NONE) on the default render path.
962    pub spanfield: SrcId,
963}
964
965#[derive(Copy, Clone, Default)]
966#[repr(C)]
967pub(crate) struct transform {
968    pub a: f64,
969    pub b: f64,
970    pub c: f64,
971    pub d: f64,
972    pub x: f64,
973    pub y: f64,
974}
975
976#[derive(Copy, Clone, Default)]
977#[repr(C)]
978pub(crate) struct realpoint {
979    pub x: f32,
980    pub y: f32,
981}
982
983#[derive(Copy, Clone, Default)]
984#[repr(C)]
985pub(crate) struct realrect {
986    pub x: f32,
987    pub y: f32,
988    pub wd: f32,
989    pub ht: f32,
990}
991
992#[derive(Copy, Clone, Default)]
993#[repr(C)]
994pub(crate) struct ResourceSearchState {
995    pub make_tex_discard_errors: boolean,
996}
997
998// ---------------------------------------------------------------------------
999// Paged sparse backing for the per-codepoint `eqtb` / `hash` regions.
1000//
1001// XeTeX's `eqtb` reserves one slot per Unicode codepoint for each of the
1002// cat/lc/uc/sf/math/del code tables — ~7.8M slots spanning absolute indices
1003// [`CODEPOINT_LO`..`eqtb_top`]. `initialize` fills them with a per-band default
1004// (cat=12, sf=1000, math=identity, lc/uc/band6=0, del=-1); a real
1005// LaTeX+unicode-math format overrides only a few thousand. The parallel `hash`
1006// array never touches that middle region at all. Stored densely this is ~125 MB
1007// (eqtb) + ~62 MB (hash) of mostly-identical defaults.
1008//
1009// `PagedArray` keeps the frequently-touched low region [base..CODEPOINT_LO)
1010// dense and pages the high region lazily: a page faults in (filled from
1011// `default_fn`) only on first access, and `compact()` — run when a format image
1012// is sealed — frees any page still byte-equal to its defaults. Correctness never
1013// depends on `default_fn` matching TeX's real defaults: a freed page is
1014// regenerated identically on next access; only how much memory is reclaimed does.
1015const CODEPOINT_LO: usize = 1_207_592; // cat_code_base: first per-codepoint band
1016const PAGE_BITS: usize = 12;
1017const PAGE_LEN: usize = 1 << PAGE_BITS;
1018/// Live byte range of an `eqtb` word at absolute index `i`. The cat/lc/uc/sf/math
1019/// code bands read the whole `two_halves` (bytes 0..8: value `RH` + eq_type/level
1020/// `B0`/`B1`). The del/int region [7892264..=9006997] reads only `.u.CINT`
1021/// (bytes 4..8) — its `junk` half is dead. (The hashextra region above that holds
1022/// csname meanings read via `.hh`, so it keeps the full range.)
1023fn eqtb_sig_range(i: usize) -> (usize, usize) {
1024    if (7_892_264..=9_006_997).contains(&i) {
1025        (0, 4) // del/int region: only `.u.CINT` (now at offset 0) is live
1026    } else {
1027        (0, 8)
1028    }
1029}
1030
1031/// `hash` words are `two_halves` — the full 8 bytes are live.
1032fn hash_sig_range(_i: usize) -> (usize, usize) {
1033    (0, 8)
1034}
1035
1036/// Compare two `T` values over a byte sub-range `[off, off+len)`. Only the bytes
1037/// a TeX word's *live* fields occupy are significant: a `memoryword` is 16 bytes
1038/// but the eqtb code bands read only `.hh` (bytes 0..8) and the del/int region
1039/// reads only `.u.CINT` (bytes 4..8). The dead bytes carry copy-loop / allocator
1040/// residue, so comparing only the live range lets compaction recognise pages that
1041/// hold nothing but band defaults. Freeing such a page is safe because a re-fault
1042/// regenerates the same live bytes (and dead bytes are never read).
1043fn paged_bytes_eq<T>(a: &T, b: &T, off: usize, len: usize) -> bool {
1044    // SAFETY: `off+len <= size_of::<T>()`; read-only byte view of two POD values.
1045    unsafe {
1046        let pa = (a as *const T as *const u8).add(off);
1047        let pb = (b as *const T as *const u8).add(off);
1048        core::slice::from_raw_parts(pa, len) == core::slice::from_raw_parts(pb, len)
1049    }
1050}
1051
1052pub(crate) struct PagedArray<T: Copy + Default> {
1053    base: usize,                  // absolute index of `low[0]`
1054    lo: usize,                    // absolute index where paging begins
1055    end: usize,                   // absolute index one past the last element
1056    low: Vec<T>,                  // dense, covers [base..lo)
1057    pages: Vec<Option<Box<[T]>>>, // lazy, covers [lo..end) rounded up to PAGE_LEN
1058    default_fn: fn(usize) -> T,   // value at absolute index `i` when its page is absent
1059    sig_range: fn(usize) -> (usize, usize), // live byte range (off,len) at index `i`
1060}
1061
1062impl<T: Copy + Default> PagedArray<T> {
1063    fn new(
1064        base: usize,
1065        end: usize,
1066        default_fn: fn(usize) -> T,
1067        sig_range: fn(usize) -> (usize, usize),
1068    ) -> Self {
1069        // Page the whole array (no dense prefix): the low region below
1070        // CODEPOINT_LO is also ~97% uniform default (eqtb: undefined-cs slots;
1071        // hash: zero), so paging it too lets compaction reclaim ~18 MB. `lo ==
1072        // base` makes `low` empty and routes every access through the page table.
1073        let lo = base;
1074        let npages = (end - lo).div_ceil(PAGE_LEN);
1075        PagedArray {
1076            base,
1077            lo,
1078            end,
1079            low: vec![T::default(); lo - base],
1080            pages: (0..npages).map(|_| None).collect(),
1081            default_fn,
1082            sig_range,
1083        }
1084    }
1085
1086    #[inline]
1087    fn ptr(&mut self, abs: usize) -> *mut T {
1088        if abs < self.lo {
1089            // SAFETY: callers only index valid absolute slots [base..end).
1090            return unsafe { self.low.as_mut_ptr().add(abs - self.base) };
1091        }
1092        let rel = abs - self.lo;
1093        let pg = rel >> PAGE_BITS;
1094        let off = rel & (PAGE_LEN - 1);
1095        if self.pages[pg].is_none() {
1096            let page_base = self.lo + pg * PAGE_LEN;
1097            let f = self.default_fn;
1098            let mut page: Vec<T> = Vec::with_capacity(PAGE_LEN);
1099            for j in 0..PAGE_LEN {
1100                page.push(f(page_base + j));
1101            }
1102            self.pages[pg] = Some(page.into_boxed_slice());
1103        }
1104        // SAFETY: page just ensured present; `off < PAGE_LEN`.
1105        unsafe {
1106            self.pages[pg]
1107                .as_mut()
1108                .unwrap_unchecked()
1109                .as_mut_ptr()
1110                .add(off)
1111        }
1112    }
1113
1114    /// Free any faulted page whose contents still equal `default_fn` — called
1115    /// when sealing a format snapshot so the persisted/resident image keeps only
1116    /// pages carrying real overrides.
1117    fn compact(&mut self) {
1118        let f = self.default_fn;
1119        for pg in 0..self.pages.len() {
1120            let page_base = self.lo + pg * PAGE_LEN;
1121            let is_default = match &self.pages[pg] {
1122                None => continue,
1123                Some(page) => (0..PAGE_LEN).all(|j| {
1124                    let (off, len) = (self.sig_range)(page_base + j);
1125                    paged_bytes_eq(&page[j], &f(page_base + j), off, len)
1126                }),
1127            };
1128            if is_default {
1129                self.pages[pg] = None;
1130            }
1131        }
1132    }
1133
1134    fn resident_bytes(&self) -> usize {
1135        let elt = core::mem::size_of::<T>();
1136        self.low.capacity() * elt
1137            + self.pages.iter().filter(|p| p.is_some()).count() * PAGE_LEN * elt
1138            + self.pages.capacity() * core::mem::size_of::<Option<Box<[T]>>>()
1139    }
1140
1141    /// Write `v` at absolute index `abs`, faulting its page in. Out-of-range
1142    /// indices are ignored (the source-tracking shadow may be sized smaller than
1143    /// a node address it is asked to stamp on a degenerate path).
1144    #[inline]
1145    fn set(&mut self, abs: usize, v: T) {
1146        if abs < self.base || abs >= self.end {
1147            return;
1148        }
1149        // SAFETY: `abs` is in `[base, end)`; `ptr` faults the page if absent.
1150        unsafe {
1151            *self.ptr(abs) = v;
1152        }
1153    }
1154
1155    /// Read the value at absolute index `abs` WITHOUT faulting a page: an absent
1156    /// page is reported as its `default_fn` value. `&self`, so it is safe to call
1157    /// from the read-only IR snapshot path.
1158    #[inline]
1159    fn get_copy(&self, abs: usize) -> T {
1160        if abs < self.base || abs >= self.end {
1161            return (self.default_fn)(abs);
1162        }
1163        if abs < self.lo {
1164            return self.low[abs - self.base];
1165        }
1166        let rel = abs - self.lo;
1167        let pg = rel >> PAGE_BITS;
1168        let off = rel & (PAGE_LEN - 1);
1169        match self.pages.get(pg).and_then(|p| p.as_ref()) {
1170            Some(page) => page[off],
1171            None => (self.default_fn)(abs),
1172        }
1173    }
1174}
1175
1176impl<T: Copy + Default> Clone for PagedArray<T> {
1177    fn clone(&self) -> Self {
1178        PagedArray {
1179            base: self.base,
1180            lo: self.lo,
1181            end: self.end,
1182            low: self.low.clone(),
1183            pages: self.pages.clone(),
1184            default_fn: self.default_fn,
1185            sig_range: self.sig_range,
1186        }
1187    }
1188}
1189
1190/// A `Copy`, raw-pointer-style handle to a [`PagedArray`], standing in for the
1191/// `*mut memoryword` / `*mut twohalves` that the translated engine binds as
1192/// `let mut eqtb = self.state.zeqtb.as_mut_ptr();` and indexes with
1193/// `eqtb.offset(i)`. `offset` mimics `<*mut T>::offset` but routes through the
1194/// paged backing (faulting the page if needed). Mutating the backing through a
1195/// shared `Copy` handle mirrors the raw-pointer aliasing model the c2rust
1196/// translation already relies on for `zmem`/`zeqtb`/`hash`.
1197pub(crate) struct PagedView<T: Copy + Default>(*mut PagedArray<T>);
1198
1199impl<T: Copy + Default> Clone for PagedView<T> {
1200    fn clone(&self) -> Self {
1201        *self
1202    }
1203}
1204impl<T: Copy + Default> Copy for PagedView<T> {}
1205
1206impl<T: Copy + Default> PagedView<T> {
1207    pub(crate) fn new(arr: &mut PagedArray<T>) -> Self {
1208        PagedView(arr as *mut PagedArray<T>)
1209    }
1210    #[allow(dead_code)]
1211    pub(crate) fn null() -> Self {
1212        PagedView(core::ptr::null_mut())
1213    }
1214    #[inline]
1215    pub(crate) fn as_mut_ptr(self) -> Self {
1216        self
1217    }
1218    #[inline]
1219    pub(crate) fn is_null(self) -> bool {
1220        self.0.is_null()
1221    }
1222    #[inline]
1223    pub(crate) fn offset(self, count: isize) -> *mut T {
1224        // `count` is the absolute eqtb/hash index (the translation computes the
1225        // full index, then offsets once). SAFETY: matches the established
1226        // raw-pointer aliasing model; `self.0` is non-null after rebind.
1227        unsafe { (*self.0).ptr(count as usize) }
1228    }
1229}
1230
1231/// Default `eqtb` word for an absolute codepoint-region index, reproducing what
1232/// XeTeX's `initialize` writes across the cat/lc/uc/sf/math/del bands.
1233fn eqtb_codepoint_default(i: usize) -> memoryword {
1234    // `memoryword` is 16 bytes (the `four_quarters` view needs 12 + padding) but
1235    // only its low 8 bytes are ever used by the code bands. `Default` leaves the
1236    // upper 8 bytes indeterminate, which would make byte-equality (compaction)
1237    // unreliable, so start from a fully-zeroed word.
1238    let mut w: memoryword = unsafe { core::mem::zeroed() };
1239    if (1_207_592..=7_892_263).contains(&i) {
1240        // two_halves code bands: eq_type=undefined_cs (123), eq_level=level_one (1).
1241        let rh: i32 = if (1_207_592..=2_321_703).contains(&i) {
1242            12 // cat_code -> "other"
1243        } else if (4_549_928..=5_664_039).contains(&i) {
1244            1000 // sf_code
1245        } else if (5_664_040..=6_778_151).contains(&i) {
1246            (i - 5_664_040) as i32 // math_code = identity
1247        } else {
1248            0 // lc_code / uc_code / trailing band
1249        };
1250        // Writing union fields is safe (no drop glue); the two halves are disjoint.
1251        w.hh.u.B0 = 123;
1252        w.hh.u.B1 = 1;
1253        w.hh.v.RH = rh;
1254    } else if (7_892_607..=9_006_718).contains(&i) {
1255        // del_code lives in the int view; default is -1.
1256        w.u.CINT = -1;
1257    } else if i < CODEPOINT_LO || i > xetex_eqtb_top as usize {
1258        // The control-sequence-meaning region below the code bands AND the eTeX
1259        // hash_high region above eqtb_top are both pre-filled with the
1260        // undefined-control-sequence template (eq_type=undefined_cs=104,
1261        // eq_level=level_zero=0, equiv=null=-0x0FFFFFFF). Defined csnames and the
1262        // few non-default low slots (glue/int params) overwrite it on demand; the
1263        // vast majority stay undefined and compact away.
1264        w.hh.u.B0 = 104;
1265        w.hh.v.RH = -(268435455 as i32);
1266    }
1267    // else: the small int/dimen gap regions ([7892264..7892606],
1268    // [9006719..9006997]) default to zero.
1269    w
1270}
1271
1272/// Default `hash` word for the paged region: the codepoint middle is never
1273/// populated, so every absent slot is zero.
1274fn hash_codepoint_default(_i: usize) -> twohalves {
1275    twohalves::default()
1276}
1277
1278pub(crate) struct PortableTexState {
1279    pub mem: Vec<memoryword>,
1280    buffer_storage: Vec<UnicodeScalar>,
1281    nest_storage: Vec<liststaterecord>,
1282    savestack_storage: Vec<memoryword>,
1283    inputstack_storage: Vec<instaterecord>,
1284    inputfile_storage: Vec<unicodefile>,
1285    eofseen_storage: Vec<boolean>,
1286    linestack_storage: Vec<integer>,
1287    grpstack_storage: Vec<savepointer>,
1288    ifstack_storage: Vec<halfword>,
1289    sourcefilenamestack_storage: Vec<strnumber>,
1290    fullsourcefilenamestack_storage: Vec<strnumber>,
1291    paramstack_storage: Vec<halfword>,
1292    hyphword_storage: Vec<strnumber>,
1293    hyphlist_storage: Vec<halfword>,
1294    hyphlink_storage: Vec<hyphpointer>,
1295    hash_paged: PagedArray<twohalves>,
1296    eqtb_paged: PagedArray<memoryword>,
1297    strstart_storage: Vec<poolpointer>,
1298    strpool_storage: Vec<packedUTF16code>,
1299    fontinfo_storage: Vec<fmemoryword>,
1300    bcharlabel_storage: Vec<fontindex>,
1301    charbase_storage: Vec<integer>,
1302    widthbase_storage: Vec<integer>,
1303    heightbase_storage: Vec<integer>,
1304    depthbase_storage: Vec<integer>,
1305    italicbase_storage: Vec<integer>,
1306    ligkernbase_storage: Vec<integer>,
1307    kernbase_storage: Vec<integer>,
1308    extenbase_storage: Vec<integer>,
1309    parambase_storage: Vec<integer>,
1310    fontarea_storage: Vec<strnumber>,
1311    fontname_storage: Vec<strnumber>,
1312    fontbc_storage: Vec<UTF16code>,
1313    fontec_storage: Vec<UTF16code>,
1314    fontbchar_storage: Vec<ninebits>,
1315    fontfalsebchar_storage: Vec<ninebits>,
1316    fontcheck_storage: Vec<fourquarters>,
1317    fontdsize_storage: Vec<scaled>,
1318    fontsize_storage: Vec<scaled>,
1319    fontflags_storage: Vec<i8>,
1320    fontglue_storage: Vec<halfword>,
1321    fontlayoutengine_storage: Vec<voidpointer>,
1322    fontletterspace_storage: Vec<scaled>,
1323    fontmapping_storage: Vec<voidpointer>,
1324    fontparams_storage: Vec<fontindex>,
1325    fontused_storage: Vec<boolean>,
1326    nativetext_storage: Vec<UTF16code>,
1327    hyphenchar_storage: Vec<integer>,
1328    skewchar_storage: Vec<integer>,
1329    triec_storage: Vec<packedUTF16code>,
1330    triehash_storage: Vec<triepointer>,
1331    triel_storage: Vec<triepointer>,
1332    trieo_storage: Vec<trieopcode>,
1333    trier_storage: Vec<triepointer>,
1334    trietaken_storage: Vec<boolean>,
1335    trietrc_storage: Vec<quarterword>,
1336    trietrl_storage: Vec<triepointer>,
1337    trietro_storage: Vec<triepointer>,
1338    // --- Source-tracking subsystem (feature-gated; all TRANSIENT per-render
1339    // parse state, NOT meaningfully round-tripped through the format image: the
1340    // heap tables below are reset to empty on load and the scalars are reset at
1341    // `begin_primary_input`). The default render path leaves `source_tracking`
1342    // false and allocates nothing here. ---
1343    /// Master gate. When false every hook is a cheap predictable no-op.
1344    source_tracking: bool,
1345    /// The construct latch: the span of the command currently executing, frozen
1346    /// at the `main_control` dispatch boundary before its argument sub-scans run.
1347    cmd_span: SrcId,
1348    /// Carried from a `macro_call` to the next `begin_token_list(.., macro)` to
1349    /// supply the call-site baseline for a macro body's synthesized content.
1350    pending_call_span: SrcId,
1351    /// Buffer offset of the token currently being lexed (captured at the top of
1352    /// `get_next`'s outer loop, so leading skipped material is excluded).
1353    src_token_start: integer,
1354    /// Multi-line base: the absolute character offset, in the primary input's
1355    /// own coordinates, of the first buffer slot of the current line.
1356    src_line_base: u32,
1357    /// Buffer index of the first slot of the current primary-input line. Each
1358    /// line is reloaded at the same `start`, so `loc - this` is a within-line
1359    /// column and `src_line_base + (loc - this)` is the absolute char offset.
1360    src_line_buf_start: integer,
1361    /// Character length of the most-recently-read primary-input line, used to
1362    /// advance `src_line_base` (`+= len + 1` for the line break) at the next refill.
1363    src_prev_line_len: u32,
1364    /// Whether the line accumulator has seen the first primary-input line (so the
1365    /// first line gets base 0 and subsequent lines accumulate).
1366    src_line_initialized: bool,
1367    /// The primary input's namefield (source-file string number); the line
1368    /// accumulator and resolver key on this.
1369    src_primary_name: strnumber,
1370    /// `macro_call` scratch: char offset of the invoking control sequence start.
1371    src_call_start: u32,
1372    /// `macro_call` scratch: namefield of the level the macro was read from.
1373    src_call_name: strnumber,
1374    /// `macro_call` scratch: statefield of that level (0 = token list / nested).
1375    src_call_state: integer,
1376    /// `macro_call` scratch: indexfield (token type) of the level the macro was
1377    /// read from, captured at entry before its arguments / the exhausted-list pop
1378    /// loop change `curinput`. `< 5` = a user-typed argument / backed-up replay
1379    /// (its hull is recovered from the scanned args); `>= 5` = a macro body / every
1380    /// list (inherits the baseline). `-1` when read from the buffer.
1381    src_call_index: integer,
1382    /// `macro_call` scratch: the inherited span fallback for a nested call.
1383    src_call_span: SrcId,
1384    /// `macro_call` scratch: span of the LAST token consumed while scanning this
1385    /// invocation's arguments (the closing `}` of the final brace group), captured
1386    /// before the exhausted-list pop loop. Unions into the argument hull so a
1387    /// token-list-replayed `\frac{q}{2}` recovers its trailing delimiter.
1388    src_call_argspan: SrcId,
1389    /// The span of the most recent control-sequence token lexed from the primary
1390    /// input BUFFER (set in `src_record_buffer_span` when `curcs != 0`). This is the
1391    /// in-fragment USER command currently being expanded: kernel helper macros
1392    /// reached through its expansion (`\@sqrt`, `\root`, `\mathpalette`, ... for
1393    /// `\sqrt`) are read from token lists, so they never overwrite it. It survives
1394    /// the eager pop of token-list input levels, unlike `curinput.spanfield`, so a
1395    /// helper invoked from the buffer after a `\futurelet`/`\@ifnextchar` peek can
1396    /// still recover the user command's start instead of the peeked token's.
1397    src_user_cmd_span: SrcId,
1398    /// Like `src_user_cmd_span` but tracks the innermost in-fragment command being
1399    /// REPLAYED from an argument-level token list (a nested `\sqrt{..}` inside a
1400    /// degree-form radicand, e.g. Cardano's inner radical). `src_user_cmd_span` is set
1401    /// only on BUFFER reads, so it goes stale during a mathchoice/macro replay (a later
1402    /// `\frac` overwrites it during the outer arg pre-scan). This one is set at
1403    /// `src_macro_begin` for argument-level replays and reset on the next buffer read,
1404    /// so it holds exactly the command that built the next noad. Consumed ONLY by
1405    /// `src_construct_anchor` — never by the arg-hull baseline — so it cannot perturb
1406    /// the degree-form extent.
1407    src_anchor_cmd: SrcId,
1408    /// `macro_call` scratch: `src_user_cmd_span` captured at `src_macro_begin`
1409    /// (before the macro reads its arguments, so it is the ENCLOSING user command,
1410    /// not one lexed while scanning the args). The buffer-branch baseline anchors
1411    /// its START here so every helper in a user command's expansion maps back to
1412    /// that command (the transitive macro-call chain).
1413    src_call_user_span: SrcId,
1414    /// The source span (origin) of the token currently held in `cur_tok` -- set at
1415    /// each token read (buffer or token-list) to the read token's own span, and NOT
1416    /// moved by later input-level pushes/pops. Lets `back_input` re-stamp a backed-up
1417    /// token with its true origin instead of the ambient `spanfield`, which the lexer
1418    /// may have advanced past during a `\futurelet`/`\@ifnextchar` look-ahead.
1419    src_tok_span: SrcId,
1420    /// Intern table: `SrcId` (minus 1) indexes this. Stable first-touch order.
1421    src_spans: Vec<RawSpan>,
1422    /// Dedup map so equal spans share one `SrcId`.
1423    src_dedup: std::collections::HashMap<RawSpan, SrcId>,
1424    /// Paged-sparse shadow of `mem`: `node_src[addr]` is the `SrcId` stamped on
1425    /// the node at `addr`. Same substrate as the paged eqtb; faults only touched
1426    /// pages, so cost is proportional to the fragment, not to `mem`.
1427    node_src: PagedArray<u32>,
1428    /// Transient per-code-unit source ids for the native-text run currently being
1429    /// collected in `main_control`'s main loop: `src_native_offsets[i]` is the
1430    /// `SrcId` of the input char whose UTF-16 code unit landed at `nativetext[i]`
1431    /// (a 2-unit surrogate fills both slots with one id). Filled per char at the
1432    /// `ishyph` seam, then CONSUMED (taken) by `src_resolve_native_glyphs` when
1433    /// the run is shaped, so a later re-measure maps to None rather than stale.
1434    src_native_offsets: Vec<SrcId>,
1435    /// Enclosing-construct stack arena (source-tracking inc2): each frame links
1436    /// to its parent, so a single `u32` per node (`node_stack`) snapshots the
1437    /// whole nesting. Grows by one cell per construct entered; transient.
1438    src_stack_cells: Vec<SrcStackCell>,
1439    /// Index (1-based) of the top enclosing-construct frame currently live;
1440    /// `0` = no enclosure. Pushed/popped at macro-body and math-group boundaries.
1441    cur_stack_head: u32,
1442    /// Balanced stack of the OPENING-token span of each live math GROUP (the `{` of a
1443    /// `scan_math` field / bare math group, or the `\left` of a `\left..\right`). Pushed
1444    /// at the group open, popped at the matching close, where the one general
1445    /// `src_construct_extent` rule maps the construct's synthesized marks to
1446    /// `[min(noad's command start, group open), consumed end]` -- the
1447    /// consumed-source-extent of the group. Replaces the per-construct delimiter/accent
1448    /// extenders. Transient (empty between fragments).
1449    src_grp_stack: Vec<SrcId>,
1450    /// The group-open span popped at the most recent group-9 close, handed from
1451    /// `src_scan_math_group_close` (the `9 =>` arm head) to `src_construct_extend_to_loc`
1452    /// (the field-fill point later in the same arm).
1453    src_grp_closing: SrcId,
1454    /// Paged-sparse shadow of `mem` parallel to `node_src`: `node_stack[addr]` is
1455    /// the [`Self::cur_stack_head`] that was live when the node at `addr` was
1456    /// allocated -- the head of its enclosing-construct chain.
1457    node_stack: PagedArray<u32>,
1458    pub LRproblems: integer,
1459    pub LRptr: halfword,
1460    pub OKtointerrupt: boolean,
1461    pub terminal_output: NativeFileHandle,
1462    pub activenodesize: smallnumber,
1463    pub activewidth: [scaled; 7],
1464    pub actuallooseness: integer,
1465    pub adjusttail: halfword,
1466    pub aftertoken: halfword,
1467    pub alignptr: halfword,
1468    pub alignstate: integer,
1469    pub areadelimiter: poolpointer,
1470    pub aritherror: boolean,
1471    pub avail: halfword,
1472    pub background: [scaled; 7],
1473    pub baseptr: integer,
1474    pub bchar: halfword,
1475    pub bcharlabel: *mut fontindex,
1476    pub bestbet: halfword,
1477    pub bestheightplusdepth: scaled,
1478    pub bestline: halfword,
1479    pub bestplace: [halfword; 4],
1480    pub bestplglue: [scaled; 4],
1481    pub bestplline: [halfword; 4],
1482    pub bestplshort: [scaled; 4],
1483    pub breadthmax: integer,
1484    pub breakwidth: [scaled; 7],
1485    pub buffer: *mut UnicodeScalar,
1486    pub bufsize: integer,
1487    pub c: quarterword,
1488    pub cancelboundary: boolean,
1489    pub charbase: *mut integer,
1490    pub condptr: halfword,
1491    pub cscount: integer,
1492    pub curactivewidth: [scaled; 7],
1493    pub curalign: halfword,
1494    pub curarea: strnumber,
1495    pub curboundary: integer,
1496    pub curbox: halfword,
1497    pub curc: integer,
1498    pub curchr: halfword,
1499    pub curcmd: eightbits,
1500    pub curcs: halfword,
1501    pub curdir: smallnumber,
1502    pub curext: strnumber,
1503    pub curf: internalfontnumber,
1504    pub curgroup: groupcode,
1505    pub curhead: halfword,
1506    pub curi: fourquarters,
1507    pub curif: smallnumber,
1508    pub curinput: instaterecord,
1509    pub curl: halfword,
1510    pub curlang: eightbits,
1511    pub curlevel: quarterword,
1512    pub curlist: liststaterecord,
1513    pub curloop: halfword,
1514    pub curmark: [halfword; 5],
1515    pub curmlist: halfword,
1516    pub curmu: scaled,
1517    pub curname: strnumber,
1518    pub curorder: glueord,
1519    pub curp: halfword,
1520    pub curprehead: halfword,
1521    pub curpretail: halfword,
1522    pub curptr: halfword,
1523    pub curq: halfword,
1524    pub curr: halfword,
1525    pub curs: integer,
1526    pub cursize: integer,
1527    pub curspan: halfword,
1528    pub curstyle: smallnumber,
1529    pub curtail: halfword,
1530    pub curtok: halfword,
1531    pub curval: integer,
1532    pub curval1: integer,
1533    pub curvallevel: eightbits,
1534    pub deadcycles: integer,
1535    pub defref: halfword,
1536    pub deletionsallowed: boolean,
1537    pub depthbase: *mut integer,
1538    pub depththreshold: integer,
1539    pub dig: [eightbits; 23],
1540    pub discptr: [halfword; 4],
1541    pub discwidth: scaled,
1542    pub doingleaders: boolean,
1543    pub doingspecial: boolean,
1544    pub dolastlinefit: boolean,
1545    pub downptr: halfword,
1546    pub dvibufsize: integer,
1547    pub dvigone: integer,
1548    pub dvilimit: integer,
1549    pub dvioffset: integer,
1550    pub dviptr: integer,
1551    pub dynused: integer,
1552    pub eTeXmode: eightbits,
1553    pub easyline: halfword,
1554    pub editline: integer,
1555    pub editnamelength: integer,
1556    pub editnamestart: poolpointer,
1557    pub eightbitp: i32,
1558    pub emptyfield: twohalves,
1559    pub eofseen: *mut boolean,
1560    pub epochseconds: integer,
1561    pub eqtbtop: halfword,
1562    pub errorcount: schar,
1563    pub errorline: integer,
1564    pub expanddepth: integer,
1565    pub expanddepthcount: integer,
1566    pub extdelimiter: poolpointer,
1567    pub extenbase: *mut integer,
1568    pub f: internalfontnumber,
1569    pub falsebchar: halfword,
1570    pub fewestdemerits: integer,
1571    pub filelineerrorstylep: i32,
1572    pub filenamequotechar: UTF16code,
1573    pub fileoffset: integer,
1574    pub fillwidth: [scaled; 3],
1575    pub finalpass: boolean,
1576    pub first: integer,
1577    pub firstcount: integer,
1578    pub firstindent: scaled,
1579    pub firstp: halfword,
1580    pub firstwidth: scaled,
1581    pub fmemptr: fontindex,
1582    pub fontarea: *mut strnumber,
1583    pub fontbc: *mut UTF16code,
1584    pub fontbchar: *mut ninebits,
1585    pub fontcheck: *mut fourquarters,
1586    pub fontdsize: *mut scaled,
1587    pub fontec: *mut UTF16code,
1588    pub fontfalsebchar: *mut ninebits,
1589    pub fontflags: *mut i8,
1590    pub fontglue: *mut halfword,
1591    pub fontinfo: *mut fmemoryword,
1592    pub fontinshortdisplay: integer,
1593    pub fontlayoutengine: *mut voidpointer,
1594    pub fontletterspace: *mut scaled,
1595    pub fontmapping: *mut voidpointer,
1596    pub fontmax: integer,
1597    pub fontmemsize: integer,
1598    pub fontname: *mut strnumber,
1599    pub fontparams: *mut fontindex,
1600    pub fontptr: internalfontnumber,
1601    pub fontsize: *mut scaled,
1602    pub fontused: *mut boolean,
1603    pub forceeof: boolean,
1604    pub formatident: strnumber,
1605    pub fullsourcefilenamestack: *mut strnumber,
1606    pub g: halfword,
1607    pub globalprevp: halfword,
1608    pub grpstack: *mut savepointer,
1609    pub ha: halfword,
1610    pub halfbuf: integer,
1611    pub halferrorline: integer,
1612    pub haltingonerrorp: boolean,
1613    pub haltonerrorp: i32,
1614    pub hash: PagedView<twohalves>,
1615    pub hashextra: halfword,
1616    pub hashhigh: halfword,
1617    pub hashused: halfword,
1618    pub hb: halfword,
1619    pub hc: [integer; 4099],
1620    pub heightbase: *mut integer,
1621    pub helpline: [strnumber; 6],
1622    pub helpptr: eightbits,
1623    pub hf: internalfontnumber,
1624    pub himemmin: halfword,
1625    pub history: eightbits,
1626    pub hliststack: [halfword; 513],
1627    pub hliststacklevel: i16,
1628    pub hn: smallnumber,
1629    pub hu: [integer; 4097],
1630    pub hyf: [eightbits; 4097],
1631    pub hyfbchar: halfword,
1632    pub hyfchar: integer,
1633    pub hyfdistance: [smallnumber; 35112],
1634    pub hyfnext: [trieopcode; 35112],
1635    pub hyfnum: [smallnumber; 35112],
1636    pub hyphcount: integer,
1637    pub hyphenchar: *mut integer,
1638    pub hyphenpassed: smallnumber,
1639    pub hyphindex: triepointer,
1640    pub hyphlink: *mut hyphpointer,
1641    pub hyphlist: *mut halfword,
1642    pub hyphnext: integer,
1643    pub hyphsize: integer,
1644    pub hyphstart: triepointer,
1645    pub hyphword: *mut strnumber,
1646    pub iflimit: eightbits,
1647    pub ifline: integer,
1648    pub ifstack: *mut halfword,
1649    pub initcurlang: eightbits,
1650    pub initlft: boolean,
1651    pub initlhyf: integer,
1652    pub initlig: boolean,
1653    pub initlist: halfword,
1654    pub initpoolptr: poolpointer,
1655    pub initrhyf: integer,
1656    pub initstrptr: strnumber,
1657    pub iniversion: boolean,
1658    pub inopen: integer,
1659    pub inputfile: *mut unicodefile,
1660    pub inputptr: integer,
1661    pub inputstack: *mut instaterecord,
1662    pub insdisc: boolean,
1663    pub insertpenalties: integer,
1664    pub insertsrcspecialauto: boolean,
1665    pub insertsrcspecialeverymath: boolean,
1666    pub insertsrcspecialeverypar: boolean,
1667    pub insertsrcspecialeveryvbox: boolean,
1668    pub interaction: eightbits,
1669    pub interactionoption: eightbits,
1670    pub interrupt: integer,
1671    pub ishyph: boolean,
1672    pub isincsname: boolean,
1673    pub italicbase: *mut integer,
1674    pub jobname: strnumber,
1675    pub jrandom: eightbits,
1676    pub justbox: halfword,
1677    pub kernbase: *mut integer,
1678    pub resource_search_state: ResourceSearchState,
1679    pub l: eightbits,
1680    pub last: integer,
1681    pub lastbadness: integer,
1682    pub lastbop: integer,
1683    pub lastglue: halfword,
1684    pub lastkern: scaled,
1685    pub lastleftmostchar: halfword,
1686    pub lastlinefill: halfword,
1687    pub lastnodetype: integer,
1688    pub lastpenalty: integer,
1689    pub lastrightmostchar: halfword,
1690    pub lastspecialline: halfword,
1691    pub lfthit: boolean,
1692    pub lhyf: integer,
1693    pub ligaturepresent: boolean,
1694    pub ligkernbase: *mut integer,
1695    pub ligstack: halfword,
1696    pub line: integer,
1697    pub linediff: integer,
1698    pub linestack: *mut integer,
1699    pub loadedfontdesignsize: scaled,
1700    pub loadedfontflags: i8,
1701    pub loadedfontletterspace: scaled,
1702    pub loadedfontmapping: voidpointer,
1703    pub logfile: alphafile,
1704    pub logopened: boolean,
1705    pub lomemmax: halfword,
1706    pub longhelpseen: boolean,
1707    pub longstate: eightbits,
1708    pub magicoffset: integer,
1709    pub magset: integer,
1710    pub mainf: internalfontnumber,
1711    pub mainh: halfword,
1712    pub maini: fourquarters,
1713    pub mainj: fourquarters,
1714    pub maink: fontindex,
1715    pub mainp: halfword,
1716    pub mainpp: halfword,
1717    pub mainppp: halfword,
1718    pub mains: integer,
1719    pub mappedtext: *mut UTF16code,
1720    pub maxbufstack: integer,
1721    pub maxh: scaled,
1722    pub maxhyphchar: integer,
1723    pub maxinopen: integer,
1724    pub maxinstack: integer,
1725    pub maxneststack: integer,
1726    pub maxopused: trieopcode,
1727    pub maxparamstack: integer,
1728    pub maxprintline: integer,
1729    pub maxpush: integer,
1730    pub maxreghelpline: strnumber,
1731    pub maxregnum: halfword,
1732    pub maxsavestack: integer,
1733    pub maxstrings: integer,
1734    pub maxv: scaled,
1735    pub membot: integer,
1736    pub memend: halfword,
1737    pub memmax: integer,
1738    pub memmin: integer,
1739    pub memtop: integer,
1740    pub microseconds: integer,
1741    pub minimaldemerits: [integer; 4],
1742    pub minimumdemerits: integer,
1743    pub mlistpenalties: boolean,
1744    pub mltexenabledp: boolean,
1745    pub mltexp: boolean,
1746    pub mubytecswrite: [halfword; 128],
1747    pub mubytekeep: integer,
1748    pub mubyteprefix: integer,
1749    pub mubyteread: [halfword; 256],
1750    pub mubyteskip: integer,
1751    pub mubytestart: boolean,
1752    pub mubytestoken: halfword,
1753    pub mubytetoken: halfword,
1754    pub nameinprogress: boolean,
1755    pub namelength: integer,
1756    pub nameoffile: *mut UTF8code,
1757    pub nativefonttypeflag: integer,
1758    pub nativelen: integer,
1759    pub nativetext: *mut UTF16code,
1760    pub nativetextsize: integer,
1761    pub nest: *mut liststaterecord,
1762    pub nestptr: integer,
1763    pub nestsize: integer,
1764    pub nonewcontrolsequence: boolean,
1765    pub noshrinkerroryet: boolean,
1766    pub nullcharacter: fourquarters,
1767    pub nulldelimiter: fourquarters,
1768    pub oldselectorignorederr: eightbits,
1769    pub oldsetting: eightbits,
1770    pub openparens: integer,
1771    pub opstart: [integer; 256],
1772    pub outputactive: boolean,
1773    pub outputcanend: boolean,
1774    pub packbeginline: integer,
1775    pub pagecontents: eightbits,
1776    pub pagemaxdepth: scaled,
1777    pub pagesofar: [scaled; 8],
1778    pub pagetail: halfword,
1779    pub parambase: *mut integer,
1780    pub paramptr: integer,
1781    pub paramsize: integer,
1782    pub paramstack: *mut halfword,
1783    pub parloc: halfword,
1784    pub partoken: halfword,
1785    pub passive: halfword,
1786    pub passnumber: halfword,
1787    pub pdflastxpos: integer,
1788    pub pdflastypos: integer,
1789    pub poolptr: poolpointer,
1790    pub poolsize: integer,
1791    pub preadjusttail: halfword,
1792    pub prevclass: integer,
1793    pub prim: [twohalves; 2101],
1794    pub primused: halfword,
1795    pub printednode: halfword,
1796    pub pseudofiles: halfword,
1797    pub pstack: [halfword; 9],
1798    pub quotedfilename: boolean,
1799    pub radix: smallnumber,
1800    pub randoms: [integer; 55],
1801    pub randomseed: scaled,
1802    pub readfile: [unicodefile; 16],
1803    pub readopen: [eightbits; 17],
1804    pub readyalready: integer,
1805    pub restrictedshell: i32,
1806    pub rhyf: integer,
1807    pub rightptr: halfword,
1808    pub rover: halfword,
1809    pub rthit: boolean,
1810    pub sachain: halfword,
1811    pub salevel: quarterword,
1812    pub sanull: memoryword,
1813    pub saroot: [halfword; 8],
1814    pub savearitherror: boolean,
1815    pub savenativelen: integer,
1816    pub saveptr: integer,
1817    pub savesize: integer,
1818    pub savestack: *mut memoryword,
1819    pub scannerstatus: eightbits,
1820    pub secondindent: scaled,
1821    pub secondpass: boolean,
1822    pub secondwidth: scaled,
1823    pub selector: eightbits,
1824    pub setboxallowed: boolean,
1825    pub shellenabledp: i32,
1826    pub shownmode: i16,
1827    pub skewchar: *mut integer,
1828    pub skipline: integer,
1829    pub sourcefilenamestack: *mut strnumber,
1830    pub spaceclass: integer,
1831    pub speclog: [integer; 29],
1832    pub stacksize: integer,
1833    pub stopatspace: boolean,
1834    pub stringvacancies: integer,
1835    pub strpool: *mut packedUTF16code,
1836    pub strptr: strnumber,
1837    pub strstart: *mut poolpointer,
1838    pub tally: integer,
1839    pub tempptr: halfword,
1840    pub termin: unicodefile,
1841    pub termoffset: integer,
1842    pub texinputtype: i32,
1843    pub texremainder: scaled,
1844    pub tfmfile: bytefile,
1845    pub tfmtemp: i32,
1846    pub threshold: integer,
1847    pub totalpages: integer,
1848    pub totalshrink: [scaled; 4],
1849    pub totalstretch: [scaled; 4],
1850    pub trickbuf: [UnicodeScalar; 256],
1851    pub trickcount: integer,
1852    pub triec: *mut packedUTF16code,
1853    pub triehash: *mut triepointer,
1854    pub triel: *mut triepointer,
1855    pub triemax: triepointer,
1856    pub triemin: [triepointer; 65536],
1857    pub trienotready: boolean,
1858    pub trieo: *mut trieopcode,
1859    pub trieoplang: [eightbits; 35112],
1860    pub trieopptr: integer,
1861    pub trieopval: [trieopcode; 35112],
1862    pub trieptr: triepointer,
1863    pub trier: *mut triepointer,
1864    pub triesize: integer,
1865    pub trietaken: *mut boolean,
1866    pub trietrc: *mut quarterword,
1867    pub trietrl: *mut triepointer,
1868    pub trietro: *mut triepointer,
1869    pub trieused: [trieopcode; 256],
1870    pub twotothe: [integer; 31],
1871    pub useerrhelp: boolean,
1872    pub varused: integer,
1873    pub warningindex: halfword,
1874    pub widthbase: *mut integer,
1875    pub writefile: [alphafile; 16],
1876    pub writeloc: halfword,
1877    pub writeopen: [boolean; 18],
1878    pub xchr: [ASCIIcode; 256],
1879    pub xtxligaturepresent: boolean,
1880    pub zeqtb: PagedView<memoryword>,
1881    pub zmem: *mut memoryword,
1882    pub zzzaa: [quarterword; 1114734],
1883    pub zzzab: [integer; 70223],
1884}
1885
1886/// Magic header for a serialized portable format image (see
1887/// [`PortableTexState::to_portable_bytes`]).
1888const PORTABLE_FORMAT_MAGIC: [u8; 8] = *b"MTXfmt\x04\x00";
1889
1890/// The complete set of heap-owning fields of [`PortableTexState`] — exactly the
1891/// fields that [`PortableTexState::clone_boxed`] deep-copies (everything else is
1892/// POD copied bitwise via the raw struct image). Both the serializer and the
1893/// deserializer walk this single list so they can never drift out of sync.
1894macro_rules! portable_owning_vecs {
1895    ($cb:ident) => {
1896        $cb!(buffer_storage, UnicodeScalar);
1897        $cb!(nest_storage, liststaterecord);
1898        $cb!(savestack_storage, memoryword);
1899        $cb!(inputstack_storage, instaterecord);
1900        $cb!(inputfile_storage, unicodefile);
1901        $cb!(eofseen_storage, boolean);
1902        $cb!(linestack_storage, integer);
1903        $cb!(grpstack_storage, savepointer);
1904        $cb!(ifstack_storage, halfword);
1905        $cb!(sourcefilenamestack_storage, strnumber);
1906        $cb!(fullsourcefilenamestack_storage, strnumber);
1907        $cb!(paramstack_storage, halfword);
1908        $cb!(hyphword_storage, strnumber);
1909        $cb!(hyphlist_storage, halfword);
1910        $cb!(hyphlink_storage, hyphpointer);
1911        $cb!(strstart_storage, poolpointer);
1912        $cb!(strpool_storage, packedUTF16code);
1913        $cb!(fontinfo_storage, fmemoryword);
1914        $cb!(bcharlabel_storage, fontindex);
1915        $cb!(charbase_storage, integer);
1916        $cb!(widthbase_storage, integer);
1917        $cb!(heightbase_storage, integer);
1918        $cb!(depthbase_storage, integer);
1919        $cb!(italicbase_storage, integer);
1920        $cb!(ligkernbase_storage, integer);
1921        $cb!(kernbase_storage, integer);
1922        $cb!(extenbase_storage, integer);
1923        $cb!(parambase_storage, integer);
1924        $cb!(fontarea_storage, strnumber);
1925        $cb!(fontname_storage, strnumber);
1926        $cb!(fontbc_storage, UTF16code);
1927        $cb!(fontec_storage, UTF16code);
1928        $cb!(fontbchar_storage, ninebits);
1929        $cb!(fontfalsebchar_storage, ninebits);
1930        $cb!(fontcheck_storage, fourquarters);
1931        $cb!(fontdsize_storage, scaled);
1932        $cb!(fontsize_storage, scaled);
1933        $cb!(fontflags_storage, i8);
1934        $cb!(fontglue_storage, halfword);
1935        $cb!(fontlayoutengine_storage, voidpointer);
1936        $cb!(fontletterspace_storage, scaled);
1937        $cb!(fontmapping_storage, voidpointer);
1938        $cb!(fontparams_storage, fontindex);
1939        $cb!(fontused_storage, boolean);
1940        $cb!(nativetext_storage, UTF16code);
1941        $cb!(hyphenchar_storage, integer);
1942        $cb!(skewchar_storage, integer);
1943        $cb!(triec_storage, packedUTF16code);
1944        $cb!(triehash_storage, triepointer);
1945        $cb!(triel_storage, triepointer);
1946        $cb!(trieo_storage, trieopcode);
1947        $cb!(trier_storage, triepointer);
1948        $cb!(trietaken_storage, boolean);
1949        $cb!(trietrc_storage, quarterword);
1950        $cb!(trietrl_storage, triepointer);
1951        $cb!(trietro_storage, triepointer);
1952    };
1953}
1954
1955/// Serialize a POD `Vec` as its full length plus only the *used* index ranges
1956/// (the rest is reconstructed as zeros). This is how a `.fmt` dump stays small:
1957/// most engine arrays are allocated to a generous capacity but only sparsely
1958/// populated. `ranges` are `(start, len)` element spans, in order.
1959fn portable_write_vec_ranges<T: Copy>(out: &mut Vec<u8>, v: &[T], ranges: &[(usize, usize)]) {
1960    let elt = core::mem::size_of::<T>();
1961    out.extend_from_slice(&(v.len() as u64).to_le_bytes());
1962    out.extend_from_slice(&(ranges.len() as u32).to_le_bytes());
1963    for &(start, len) in ranges {
1964        out.extend_from_slice(&(start as u64).to_le_bytes());
1965        out.extend_from_slice(&(len as u64).to_le_bytes());
1966        // SAFETY: callers pass ranges within `v`; `T: Copy` POD.
1967        let bytes =
1968            unsafe { core::slice::from_raw_parts(v.as_ptr().add(start) as *const u8, len * elt) };
1969        out.extend_from_slice(bytes);
1970    }
1971}
1972
1973/// Inverse of [`portable_write_vec_ranges`]: allocate a zeroed `Vec<T>` of the
1974/// stored full length and fill in the saved ranges. Returns `None` on
1975/// truncation / overflow / out-of-bounds range.
1976fn portable_read_vec_ranges<T: Copy>(bytes: &[u8], cursor: &mut usize) -> Option<Vec<T>> {
1977    let elt = core::mem::size_of::<T>();
1978    let full = u64::from_le_bytes(bytes.get(*cursor..*cursor + 8)?.try_into().ok()?) as usize;
1979    *cursor += 8;
1980    let nranges = u32::from_le_bytes(bytes.get(*cursor..*cursor + 4)?.try_into().ok()?) as usize;
1981    *cursor += 4;
1982    let total = full.checked_mul(elt)?;
1983    let mut v = Vec::<T>::with_capacity(full);
1984    // SAFETY: zero-initialize `full` elements (zero bytes are a valid value for
1985    // every POD type dumped here), then copy each saved range into place.
1986    unsafe {
1987        core::ptr::write_bytes(v.as_mut_ptr() as *mut u8, 0, total);
1988        for _ in 0..nranges {
1989            let start = u64::from_le_bytes(bytes.get(*cursor..*cursor + 8)?.try_into().ok()?) as usize;
1990            *cursor += 8;
1991            let len = u64::from_le_bytes(bytes.get(*cursor..*cursor + 8)?.try_into().ok()?) as usize;
1992            *cursor += 8;
1993            if start.checked_add(len)? > full {
1994                return None;
1995            }
1996            let nbytes = len.checked_mul(elt)?;
1997            let src = bytes.get(*cursor..*cursor + nbytes)?;
1998            *cursor += nbytes;
1999            core::ptr::copy_nonoverlapping(
2000                src.as_ptr(),
2001                (v.as_mut_ptr() as *mut u8).add(start * elt),
2002                nbytes,
2003            );
2004        }
2005        v.set_len(full);
2006    }
2007    Some(v)
2008}
2009
2010/// Number of leading elements of `v` up to and including the last non-zero
2011/// element (0 if every element is all-zero bytes). Used to drop the zero tail of
2012/// sparsely-populated engine arrays when dumping a format image.
2013fn portable_used_prefix_len<T>(v: &[T]) -> usize {
2014    let bytes =
2015        unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, core::mem::size_of_val(v)) };
2016    match bytes.iter().rposition(|&b| b != 0) {
2017        Some(last) => last / core::mem::size_of::<T>() + 1,
2018        None => 0,
2019    }
2020}
2021
2022/// Serialize a [`PagedArray`]: its geometry, the dense low region (zero tail
2023/// dropped), then only the faulted pages (page index + raw bytes). Absent pages
2024/// are regenerated from `default_fn` on load, so they cost nothing on disk.
2025fn portable_write_paged<T: Copy + Default>(out: &mut Vec<u8>, arr: &PagedArray<T>) {
2026    let elt = core::mem::size_of::<T>();
2027    out.extend_from_slice(&(arr.base as u64).to_le_bytes());
2028    out.extend_from_slice(&(arr.lo as u64).to_le_bytes());
2029    out.extend_from_slice(&(arr.end as u64).to_le_bytes());
2030    let used = portable_used_prefix_len(arr.low.as_slice());
2031    let ranges: &[(usize, usize)] = if used == 0 { &[] } else { &[(0, used)] };
2032    portable_write_vec_ranges::<T>(out, arr.low.as_slice(), ranges);
2033    let present: Vec<usize> = arr
2034        .pages
2035        .iter()
2036        .enumerate()
2037        .filter_map(|(i, p)| p.as_ref().map(|_| i))
2038        .collect();
2039    out.extend_from_slice(&(arr.pages.len() as u64).to_le_bytes());
2040    out.extend_from_slice(&(present.len() as u32).to_le_bytes());
2041    for pg in present {
2042        out.extend_from_slice(&(pg as u64).to_le_bytes());
2043        let page = arr.pages[pg].as_ref().unwrap();
2044        // SAFETY: `page` has exactly PAGE_LEN `T: Copy` POD elements.
2045        let pbytes =
2046            unsafe { core::slice::from_raw_parts(page.as_ptr() as *const u8, PAGE_LEN * elt) };
2047        out.extend_from_slice(pbytes);
2048    }
2049}
2050
2051/// Inverse of [`portable_write_paged`]. `default_fn` must match the array being
2052/// reloaded (eqtb vs hash) so absent pages regenerate identically.
2053fn portable_read_paged<T: Copy + Default>(
2054    bytes: &[u8],
2055    cursor: &mut usize,
2056    default_fn: fn(usize) -> T,
2057    sig_range: fn(usize) -> (usize, usize),
2058) -> Option<PagedArray<T>> {
2059    let elt = core::mem::size_of::<T>();
2060    let read_u64 = |cursor: &mut usize| -> Option<usize> {
2061        let v = u64::from_le_bytes(bytes.get(*cursor..*cursor + 8)?.try_into().ok()?) as usize;
2062        *cursor += 8;
2063        Some(v)
2064    };
2065    let base = read_u64(cursor)?;
2066    let lo = read_u64(cursor)?;
2067    let end = read_u64(cursor)?;
2068    let low: Vec<T> = portable_read_vec_ranges(bytes, cursor)?;
2069    let npages = read_u64(cursor)?;
2070    let npresent = u32::from_le_bytes(bytes.get(*cursor..*cursor + 4)?.try_into().ok()?) as usize;
2071    *cursor += 4;
2072    let mut pages: Vec<Option<Box<[T]>>> = (0..npages).map(|_| None).collect();
2073    for _ in 0..npresent {
2074        let pg = read_u64(cursor)?;
2075        if pg >= npages {
2076            return None;
2077        }
2078        let nbytes = PAGE_LEN.checked_mul(elt)?;
2079        let src = bytes.get(*cursor..*cursor + nbytes)?;
2080        *cursor += nbytes;
2081        let mut page: Vec<T> = Vec::with_capacity(PAGE_LEN);
2082        // SAFETY: copy PAGE_LEN POD elements; zero bytes are valid for `T`.
2083        unsafe {
2084            core::ptr::copy_nonoverlapping(src.as_ptr(), page.as_mut_ptr() as *mut u8, nbytes);
2085            page.set_len(PAGE_LEN);
2086        }
2087        pages[pg] = Some(page.into_boxed_slice());
2088    }
2089    Some(PagedArray {
2090        base,
2091        lo,
2092        end,
2093        low,
2094        pages,
2095        default_fn,
2096        sig_range,
2097    })
2098}
2099
2100impl PortableTexState {
2101    fn new_boxed_default() -> Box<Self> {
2102        let mut state = Box::<Self>::new_uninit();
2103        let state_ptr = state.as_mut_ptr();
2104        unsafe {
2105            core::ptr::write_bytes(state_ptr, 0, 1);
2106            core::ptr::addr_of_mut!((*state_ptr).mem).write(Vec::new());
2107            core::ptr::addr_of_mut!((*state_ptr).buffer_storage).write(Vec::new());
2108            core::ptr::addr_of_mut!((*state_ptr).nest_storage).write(Vec::new());
2109            core::ptr::addr_of_mut!((*state_ptr).savestack_storage).write(Vec::new());
2110            core::ptr::addr_of_mut!((*state_ptr).inputstack_storage).write(Vec::new());
2111            core::ptr::addr_of_mut!((*state_ptr).inputfile_storage).write(Vec::new());
2112            core::ptr::addr_of_mut!((*state_ptr).eofseen_storage).write(Vec::new());
2113            core::ptr::addr_of_mut!((*state_ptr).linestack_storage).write(Vec::new());
2114            core::ptr::addr_of_mut!((*state_ptr).grpstack_storage).write(Vec::new());
2115            core::ptr::addr_of_mut!((*state_ptr).ifstack_storage).write(Vec::new());
2116            core::ptr::addr_of_mut!((*state_ptr).sourcefilenamestack_storage).write(Vec::new());
2117            core::ptr::addr_of_mut!((*state_ptr).fullsourcefilenamestack_storage).write(Vec::new());
2118            core::ptr::addr_of_mut!((*state_ptr).paramstack_storage).write(Vec::new());
2119            core::ptr::addr_of_mut!((*state_ptr).hyphword_storage).write(Vec::new());
2120            core::ptr::addr_of_mut!((*state_ptr).hyphlist_storage).write(Vec::new());
2121            core::ptr::addr_of_mut!((*state_ptr).hyphlink_storage).write(Vec::new());
2122            core::ptr::addr_of_mut!((*state_ptr).hash_paged)
2123                .write(PagedArray::new(0, 0, hash_codepoint_default, hash_sig_range));
2124            core::ptr::addr_of_mut!((*state_ptr).eqtb_paged)
2125                .write(PagedArray::new(0, 0, eqtb_codepoint_default, eqtb_sig_range));
2126            core::ptr::addr_of_mut!((*state_ptr).strstart_storage).write(Vec::new());
2127            core::ptr::addr_of_mut!((*state_ptr).strpool_storage).write(Vec::new());
2128            core::ptr::addr_of_mut!((*state_ptr).fontinfo_storage).write(Vec::new());
2129            core::ptr::addr_of_mut!((*state_ptr).bcharlabel_storage).write(Vec::new());
2130            core::ptr::addr_of_mut!((*state_ptr).charbase_storage).write(Vec::new());
2131            core::ptr::addr_of_mut!((*state_ptr).widthbase_storage).write(Vec::new());
2132            core::ptr::addr_of_mut!((*state_ptr).heightbase_storage).write(Vec::new());
2133            core::ptr::addr_of_mut!((*state_ptr).depthbase_storage).write(Vec::new());
2134            core::ptr::addr_of_mut!((*state_ptr).italicbase_storage).write(Vec::new());
2135            core::ptr::addr_of_mut!((*state_ptr).ligkernbase_storage).write(Vec::new());
2136            core::ptr::addr_of_mut!((*state_ptr).kernbase_storage).write(Vec::new());
2137            core::ptr::addr_of_mut!((*state_ptr).extenbase_storage).write(Vec::new());
2138            core::ptr::addr_of_mut!((*state_ptr).parambase_storage).write(Vec::new());
2139            core::ptr::addr_of_mut!((*state_ptr).fontarea_storage).write(Vec::new());
2140            core::ptr::addr_of_mut!((*state_ptr).fontname_storage).write(Vec::new());
2141            core::ptr::addr_of_mut!((*state_ptr).fontbc_storage).write(Vec::new());
2142            core::ptr::addr_of_mut!((*state_ptr).fontec_storage).write(Vec::new());
2143            core::ptr::addr_of_mut!((*state_ptr).fontbchar_storage).write(Vec::new());
2144            core::ptr::addr_of_mut!((*state_ptr).fontfalsebchar_storage).write(Vec::new());
2145            core::ptr::addr_of_mut!((*state_ptr).fontcheck_storage).write(Vec::new());
2146            core::ptr::addr_of_mut!((*state_ptr).fontdsize_storage).write(Vec::new());
2147            core::ptr::addr_of_mut!((*state_ptr).fontsize_storage).write(Vec::new());
2148            core::ptr::addr_of_mut!((*state_ptr).fontflags_storage).write(Vec::new());
2149            core::ptr::addr_of_mut!((*state_ptr).fontglue_storage).write(Vec::new());
2150            core::ptr::addr_of_mut!((*state_ptr).fontlayoutengine_storage).write(Vec::new());
2151            core::ptr::addr_of_mut!((*state_ptr).fontletterspace_storage).write(Vec::new());
2152            core::ptr::addr_of_mut!((*state_ptr).fontmapping_storage).write(Vec::new());
2153            core::ptr::addr_of_mut!((*state_ptr).fontparams_storage).write(Vec::new());
2154            core::ptr::addr_of_mut!((*state_ptr).fontused_storage).write(Vec::new());
2155            core::ptr::addr_of_mut!((*state_ptr).nativetext_storage).write(Vec::new());
2156            core::ptr::addr_of_mut!((*state_ptr).hyphenchar_storage).write(Vec::new());
2157            core::ptr::addr_of_mut!((*state_ptr).skewchar_storage).write(Vec::new());
2158            core::ptr::addr_of_mut!((*state_ptr).triec_storage).write(Vec::new());
2159            core::ptr::addr_of_mut!((*state_ptr).triehash_storage).write(Vec::new());
2160            core::ptr::addr_of_mut!((*state_ptr).triel_storage).write(Vec::new());
2161            core::ptr::addr_of_mut!((*state_ptr).trieo_storage).write(Vec::new());
2162            core::ptr::addr_of_mut!((*state_ptr).trier_storage).write(Vec::new());
2163            core::ptr::addr_of_mut!((*state_ptr).trietaken_storage).write(Vec::new());
2164            core::ptr::addr_of_mut!((*state_ptr).trietrc_storage).write(Vec::new());
2165            core::ptr::addr_of_mut!((*state_ptr).trietrl_storage).write(Vec::new());
2166            core::ptr::addr_of_mut!((*state_ptr).trietro_storage).write(Vec::new());
2167            core::ptr::addr_of_mut!((*state_ptr).src_spans).write(Vec::new());
2168            core::ptr::addr_of_mut!((*state_ptr).src_dedup)
2169                .write(std::collections::HashMap::new());
2170            core::ptr::addr_of_mut!((*state_ptr).node_src)
2171                .write(PagedArray::new(0, 0, node_src_default, node_src_sig));
2172            core::ptr::addr_of_mut!((*state_ptr).src_native_offsets).write(Vec::new());
2173            core::ptr::addr_of_mut!((*state_ptr).src_stack_cells).write(Vec::new());
2174            core::ptr::addr_of_mut!((*state_ptr).cur_stack_head).write(0);
2175            core::ptr::addr_of_mut!((*state_ptr).node_stack)
2176                .write(PagedArray::new(0, 0, node_stack_default, node_stack_sig));
2177            state.assume_init()
2178        }
2179    }
2180
2181    fn clone_boxed(&self) -> Box<Self> {
2182        let mem = self.mem.clone();
2183        let buffer_storage = self.buffer_storage.clone();
2184        let nest_storage = self.nest_storage.clone();
2185        let savestack_storage = self.savestack_storage.clone();
2186        let inputstack_storage = self.inputstack_storage.clone();
2187        let inputfile_storage = self.inputfile_storage.clone();
2188        let eofseen_storage = self.eofseen_storage.clone();
2189        let linestack_storage = self.linestack_storage.clone();
2190        let grpstack_storage = self.grpstack_storage.clone();
2191        let ifstack_storage = self.ifstack_storage.clone();
2192        let sourcefilenamestack_storage = self.sourcefilenamestack_storage.clone();
2193        let fullsourcefilenamestack_storage = self.fullsourcefilenamestack_storage.clone();
2194        let paramstack_storage = self.paramstack_storage.clone();
2195        let hyphword_storage = self.hyphword_storage.clone();
2196        let hyphlist_storage = self.hyphlist_storage.clone();
2197        let hyphlink_storage = self.hyphlink_storage.clone();
2198        let hash_paged = self.hash_paged.clone();
2199        let eqtb_paged = self.eqtb_paged.clone();
2200        let strstart_storage = self.strstart_storage.clone();
2201        let strpool_storage = self.strpool_storage.clone();
2202        let fontinfo_storage = self.fontinfo_storage.clone();
2203        let bcharlabel_storage = self.bcharlabel_storage.clone();
2204        let charbase_storage = self.charbase_storage.clone();
2205        let widthbase_storage = self.widthbase_storage.clone();
2206        let heightbase_storage = self.heightbase_storage.clone();
2207        let depthbase_storage = self.depthbase_storage.clone();
2208        let italicbase_storage = self.italicbase_storage.clone();
2209        let ligkernbase_storage = self.ligkernbase_storage.clone();
2210        let kernbase_storage = self.kernbase_storage.clone();
2211        let extenbase_storage = self.extenbase_storage.clone();
2212        let parambase_storage = self.parambase_storage.clone();
2213        let fontarea_storage = self.fontarea_storage.clone();
2214        let fontname_storage = self.fontname_storage.clone();
2215        let fontbc_storage = self.fontbc_storage.clone();
2216        let fontec_storage = self.fontec_storage.clone();
2217        let fontbchar_storage = self.fontbchar_storage.clone();
2218        let fontfalsebchar_storage = self.fontfalsebchar_storage.clone();
2219        let fontcheck_storage = self.fontcheck_storage.clone();
2220        let fontdsize_storage = self.fontdsize_storage.clone();
2221        let fontsize_storage = self.fontsize_storage.clone();
2222        let fontflags_storage = self.fontflags_storage.clone();
2223        let fontglue_storage = self.fontglue_storage.clone();
2224        let fontlayoutengine_storage = self.fontlayoutengine_storage.clone();
2225        let fontletterspace_storage = self.fontletterspace_storage.clone();
2226        let fontmapping_storage = self.fontmapping_storage.clone();
2227        let fontparams_storage = self.fontparams_storage.clone();
2228        let fontused_storage = self.fontused_storage.clone();
2229        let nativetext_storage = self.nativetext_storage.clone();
2230        let hyphenchar_storage = self.hyphenchar_storage.clone();
2231        let skewchar_storage = self.skewchar_storage.clone();
2232        let triec_storage = self.triec_storage.clone();
2233        let triehash_storage = self.triehash_storage.clone();
2234        let triel_storage = self.triel_storage.clone();
2235        let trieo_storage = self.trieo_storage.clone();
2236        let trier_storage = self.trier_storage.clone();
2237        let trietaken_storage = self.trietaken_storage.clone();
2238        let trietrc_storage = self.trietrc_storage.clone();
2239        let trietrl_storage = self.trietrl_storage.clone();
2240        let trietro_storage = self.trietro_storage.clone();
2241        let src_spans = self.src_spans.clone();
2242        let src_dedup = self.src_dedup.clone();
2243        let node_src = self.node_src.clone();
2244        let src_native_offsets = self.src_native_offsets.clone();
2245        let src_stack_cells = self.src_stack_cells.clone();
2246        let node_stack = self.node_stack.clone();
2247        let mut state = Box::<Self>::new_uninit();
2248        let state_ptr = state.as_mut_ptr();
2249        unsafe {
2250            core::ptr::copy_nonoverlapping(self as *const Self, state_ptr, 1);
2251            core::ptr::addr_of_mut!((*state_ptr).mem).write(mem);
2252            core::ptr::addr_of_mut!((*state_ptr).buffer_storage).write(buffer_storage);
2253            core::ptr::addr_of_mut!((*state_ptr).nest_storage).write(nest_storage);
2254            core::ptr::addr_of_mut!((*state_ptr).savestack_storage).write(savestack_storage);
2255            core::ptr::addr_of_mut!((*state_ptr).inputstack_storage).write(inputstack_storage);
2256            core::ptr::addr_of_mut!((*state_ptr).inputfile_storage).write(inputfile_storage);
2257            core::ptr::addr_of_mut!((*state_ptr).eofseen_storage).write(eofseen_storage);
2258            core::ptr::addr_of_mut!((*state_ptr).linestack_storage).write(linestack_storage);
2259            core::ptr::addr_of_mut!((*state_ptr).grpstack_storage).write(grpstack_storage);
2260            core::ptr::addr_of_mut!((*state_ptr).ifstack_storage).write(ifstack_storage);
2261            core::ptr::addr_of_mut!((*state_ptr).sourcefilenamestack_storage)
2262                .write(sourcefilenamestack_storage);
2263            core::ptr::addr_of_mut!((*state_ptr).fullsourcefilenamestack_storage)
2264                .write(fullsourcefilenamestack_storage);
2265            core::ptr::addr_of_mut!((*state_ptr).paramstack_storage).write(paramstack_storage);
2266            core::ptr::addr_of_mut!((*state_ptr).hyphword_storage).write(hyphword_storage);
2267            core::ptr::addr_of_mut!((*state_ptr).hyphlist_storage).write(hyphlist_storage);
2268            core::ptr::addr_of_mut!((*state_ptr).hyphlink_storage).write(hyphlink_storage);
2269            core::ptr::addr_of_mut!((*state_ptr).hash_paged).write(hash_paged);
2270            core::ptr::addr_of_mut!((*state_ptr).eqtb_paged).write(eqtb_paged);
2271            core::ptr::addr_of_mut!((*state_ptr).strstart_storage).write(strstart_storage);
2272            core::ptr::addr_of_mut!((*state_ptr).strpool_storage).write(strpool_storage);
2273            core::ptr::addr_of_mut!((*state_ptr).fontinfo_storage).write(fontinfo_storage);
2274            core::ptr::addr_of_mut!((*state_ptr).bcharlabel_storage).write(bcharlabel_storage);
2275            core::ptr::addr_of_mut!((*state_ptr).charbase_storage).write(charbase_storage);
2276            core::ptr::addr_of_mut!((*state_ptr).widthbase_storage).write(widthbase_storage);
2277            core::ptr::addr_of_mut!((*state_ptr).heightbase_storage).write(heightbase_storage);
2278            core::ptr::addr_of_mut!((*state_ptr).depthbase_storage).write(depthbase_storage);
2279            core::ptr::addr_of_mut!((*state_ptr).italicbase_storage).write(italicbase_storage);
2280            core::ptr::addr_of_mut!((*state_ptr).ligkernbase_storage).write(ligkernbase_storage);
2281            core::ptr::addr_of_mut!((*state_ptr).kernbase_storage).write(kernbase_storage);
2282            core::ptr::addr_of_mut!((*state_ptr).extenbase_storage).write(extenbase_storage);
2283            core::ptr::addr_of_mut!((*state_ptr).parambase_storage).write(parambase_storage);
2284            core::ptr::addr_of_mut!((*state_ptr).fontarea_storage).write(fontarea_storage);
2285            core::ptr::addr_of_mut!((*state_ptr).fontname_storage).write(fontname_storage);
2286            core::ptr::addr_of_mut!((*state_ptr).fontbc_storage).write(fontbc_storage);
2287            core::ptr::addr_of_mut!((*state_ptr).fontec_storage).write(fontec_storage);
2288            core::ptr::addr_of_mut!((*state_ptr).fontbchar_storage).write(fontbchar_storage);
2289            core::ptr::addr_of_mut!((*state_ptr).fontfalsebchar_storage).write(fontfalsebchar_storage);
2290            core::ptr::addr_of_mut!((*state_ptr).fontcheck_storage).write(fontcheck_storage);
2291            core::ptr::addr_of_mut!((*state_ptr).fontdsize_storage).write(fontdsize_storage);
2292            core::ptr::addr_of_mut!((*state_ptr).fontsize_storage).write(fontsize_storage);
2293            core::ptr::addr_of_mut!((*state_ptr).fontflags_storage).write(fontflags_storage);
2294            core::ptr::addr_of_mut!((*state_ptr).fontglue_storage).write(fontglue_storage);
2295            core::ptr::addr_of_mut!((*state_ptr).fontlayoutengine_storage).write(fontlayoutengine_storage);
2296            core::ptr::addr_of_mut!((*state_ptr).fontletterspace_storage).write(fontletterspace_storage);
2297            core::ptr::addr_of_mut!((*state_ptr).fontmapping_storage).write(fontmapping_storage);
2298            core::ptr::addr_of_mut!((*state_ptr).fontparams_storage).write(fontparams_storage);
2299            core::ptr::addr_of_mut!((*state_ptr).fontused_storage).write(fontused_storage);
2300            core::ptr::addr_of_mut!((*state_ptr).nativetext_storage).write(nativetext_storage);
2301            core::ptr::addr_of_mut!((*state_ptr).hyphenchar_storage).write(hyphenchar_storage);
2302            core::ptr::addr_of_mut!((*state_ptr).skewchar_storage).write(skewchar_storage);
2303            core::ptr::addr_of_mut!((*state_ptr).triec_storage).write(triec_storage);
2304            core::ptr::addr_of_mut!((*state_ptr).triehash_storage).write(triehash_storage);
2305            core::ptr::addr_of_mut!((*state_ptr).triel_storage).write(triel_storage);
2306            core::ptr::addr_of_mut!((*state_ptr).trieo_storage).write(trieo_storage);
2307            core::ptr::addr_of_mut!((*state_ptr).trier_storage).write(trier_storage);
2308            core::ptr::addr_of_mut!((*state_ptr).trietaken_storage).write(trietaken_storage);
2309            core::ptr::addr_of_mut!((*state_ptr).trietrc_storage).write(trietrc_storage);
2310            core::ptr::addr_of_mut!((*state_ptr).trietrl_storage).write(trietrl_storage);
2311            core::ptr::addr_of_mut!((*state_ptr).trietro_storage).write(trietro_storage);
2312            core::ptr::addr_of_mut!((*state_ptr).src_spans).write(src_spans);
2313            core::ptr::addr_of_mut!((*state_ptr).src_dedup).write(src_dedup);
2314            core::ptr::addr_of_mut!((*state_ptr).node_src).write(node_src);
2315            core::ptr::addr_of_mut!((*state_ptr).src_native_offsets).write(src_native_offsets);
2316            core::ptr::addr_of_mut!((*state_ptr).src_stack_cells).write(src_stack_cells);
2317            core::ptr::addr_of_mut!((*state_ptr).node_stack).write(node_stack);
2318            let mut state = state.assume_init();
2319            state.refresh_runtime_pointers();
2320            state
2321        }
2322    }
2323
2324    fn refresh_runtime_pointers(&mut self) {
2325        self.zmem = pointer_or_null(&mut self.mem).wrapping_offset(-(self.memmin as isize));
2326        self.buffer = pointer_or_null(&mut self.buffer_storage);
2327        self.nest = pointer_or_null(&mut self.nest_storage);
2328        self.savestack = pointer_or_null(&mut self.savestack_storage);
2329        self.inputstack = pointer_or_null(&mut self.inputstack_storage);
2330        self.inputfile = pointer_or_null(&mut self.inputfile_storage);
2331        self.eofseen = pointer_or_null(&mut self.eofseen_storage);
2332        self.linestack = pointer_or_null(&mut self.linestack_storage);
2333        self.grpstack = pointer_or_null(&mut self.grpstack_storage);
2334        self.ifstack = pointer_or_null(&mut self.ifstack_storage);
2335        self.sourcefilenamestack = pointer_or_null(&mut self.sourcefilenamestack_storage);
2336        self.fullsourcefilenamestack =
2337            pointer_or_null(&mut self.fullsourcefilenamestack_storage);
2338        self.paramstack = pointer_or_null(&mut self.paramstack_storage);
2339        self.hyphword = pointer_or_null(&mut self.hyphword_storage);
2340        self.hyphlist = pointer_or_null(&mut self.hyphlist_storage);
2341        self.hyphlink = pointer_or_null(&mut self.hyphlink_storage);
2342        self.hash = PagedView::new(&mut self.hash_paged);
2343        self.zeqtb = PagedView::new(&mut self.eqtb_paged);
2344        self.strstart = pointer_or_null(&mut self.strstart_storage);
2345        self.strpool = pointer_or_null(&mut self.strpool_storage);
2346        self.fontinfo = pointer_or_null(&mut self.fontinfo_storage);
2347        self.bcharlabel = pointer_or_null(&mut self.bcharlabel_storage);
2348        self.charbase = pointer_or_null(&mut self.charbase_storage);
2349        self.widthbase = pointer_or_null(&mut self.widthbase_storage);
2350        self.heightbase = pointer_or_null(&mut self.heightbase_storage);
2351        self.depthbase = pointer_or_null(&mut self.depthbase_storage);
2352        self.italicbase = pointer_or_null(&mut self.italicbase_storage);
2353        self.ligkernbase = pointer_or_null(&mut self.ligkernbase_storage);
2354        self.kernbase = pointer_or_null(&mut self.kernbase_storage);
2355        self.extenbase = pointer_or_null(&mut self.extenbase_storage);
2356        self.parambase = pointer_or_null(&mut self.parambase_storage);
2357        self.fontarea = pointer_or_null(&mut self.fontarea_storage);
2358        self.fontname = pointer_or_null(&mut self.fontname_storage);
2359        self.fontbc = pointer_or_null(&mut self.fontbc_storage);
2360        self.fontec = pointer_or_null(&mut self.fontec_storage);
2361        self.fontbchar = pointer_or_null(&mut self.fontbchar_storage);
2362        self.fontfalsebchar = pointer_or_null(&mut self.fontfalsebchar_storage);
2363        self.fontcheck = pointer_or_null(&mut self.fontcheck_storage);
2364        self.fontdsize = pointer_or_null(&mut self.fontdsize_storage);
2365        self.fontsize = pointer_or_null(&mut self.fontsize_storage);
2366        self.fontflags = pointer_or_null(&mut self.fontflags_storage);
2367        self.fontglue = pointer_or_null(&mut self.fontglue_storage);
2368        self.fontlayoutengine = pointer_or_null(&mut self.fontlayoutengine_storage);
2369        self.fontletterspace = pointer_or_null(&mut self.fontletterspace_storage);
2370        self.fontmapping = pointer_or_null(&mut self.fontmapping_storage);
2371        self.fontparams = pointer_or_null(&mut self.fontparams_storage);
2372        self.fontused = pointer_or_null(&mut self.fontused_storage);
2373        self.nativetext = pointer_or_null(&mut self.nativetext_storage);
2374        self.hyphenchar = pointer_or_null(&mut self.hyphenchar_storage);
2375        self.skewchar = pointer_or_null(&mut self.skewchar_storage);
2376        self.triec = pointer_or_null(&mut self.triec_storage);
2377        self.triehash = pointer_or_null(&mut self.triehash_storage);
2378        self.triel = pointer_or_null(&mut self.triel_storage);
2379        self.trieo = pointer_or_null(&mut self.trieo_storage);
2380        self.trier = pointer_or_null(&mut self.trier_storage);
2381        self.trietaken = pointer_or_null(&mut self.trietaken_storage);
2382        self.trietrc = pointer_or_null(&mut self.trietrc_storage);
2383        self.trietrl = pointer_or_null(&mut self.trietrl_storage);
2384        self.trietro = pointer_or_null(&mut self.trietro_storage);
2385    }
2386
2387    fn seal_as_format_snapshot(&mut self) {
2388        self.curinput = instaterecord::default();
2389        self.inputptr = 0;
2390        self.inopen = 0;
2391        self.baseptr = 0;
2392        self.scannerstatus = 0;
2393        self.warningindex = 0;
2394        self.defref = -(268435455 as i64) as halfword;
2395        self.paramptr = 0;
2396        self.alignstate = 1000000 as integer;
2397        self.first = 0;
2398        self.last = 0;
2399        self.line = 0;
2400        self.openparens = 0;
2401        self.inputstack_storage.fill(instaterecord::default());
2402        self.inputfile_storage.fill(core::ptr::null_mut());
2403        self.eofseen_storage.fill(false_0);
2404        self.linestack_storage.fill(0);
2405        self.grpstack_storage.fill(-(268435455 as i64) as halfword);
2406        self.ifstack_storage.fill(-(268435455 as i64) as halfword);
2407        self.sourcefilenamestack_storage.fill(0);
2408        self.fullsourcefilenamestack_storage.fill(0);
2409        self.paramstack_storage.fill(0);
2410        // Drop every per-codepoint page that still holds only its band defaults,
2411        // so the sealed image (and anything cloned/serialized from it) keeps only
2412        // pages with real overrides — the bulk of the eqtb/hash savings.
2413        self.eqtb_paged.compact();
2414        self.hash_paged.compact();
2415        // Free the hyphenation-trie *construction scratch* — but only once the
2416        // trie has been packed (`trienotready == 0`). After packing, runtime
2417        // hyphenation reads only the packed trie (`trietr{c,l,o}`); these six
2418        // arrays are written solely by the trie builder, which never runs again.
2419        // If the trie is still unpacked (no `finalize_trie`), keep them: the lazy
2420        // runtime `inittrie` still needs them. Reclaims ~24 MB when packed.
2421        if self.trienotready == false_0 {
2422            self.triehash_storage = Vec::new();
2423            self.triel_storage = Vec::new();
2424            self.trier_storage = Vec::new();
2425            self.trieo_storage = Vec::new();
2426            self.triec_storage = Vec::new();
2427            self.trietaken_storage = Vec::new();
2428        }
2429        self.refresh_runtime_pointers();
2430    }
2431
2432    /// Serialize the engine state to a portable byte image (a dumped `.fmt`).
2433    ///
2434    /// The encoding mirrors [`Self::clone_boxed`]: a raw image of the whole
2435    /// struct (every POD scalar/array is valid; `Vec` headers and raw pointers
2436    /// are garbage that the loader overwrites/refreshes) followed by each
2437    /// heap-owning `Vec`'s raw element bytes in [`portable_owning_vecs`] order.
2438    ///
2439    /// This is **same-layout only**: the image is stamped with the pointer width
2440    /// and `size_of::<PortableTexState>()`, and [`Self::from_portable_bytes`]
2441    /// refuses any image whose stamps don't match the loading target. In
2442    /// practice the dump tool and the consumer must share a target triple
2443    /// (e.g. both `wasm32-unknown-unknown`).
2444    pub(crate) fn to_portable_bytes(&self) -> Vec<u8> {
2445        let mut out = Vec::new();
2446        out.extend_from_slice(&PORTABLE_FORMAT_MAGIC);
2447        out.push(core::mem::size_of::<usize>() as u8);
2448        out.extend_from_slice(&(core::mem::size_of::<PortableTexState>() as u64).to_le_bytes());
2449        // SAFETY: read-only view of `self`'s bytes; `PortableTexState` has no
2450        // padding we care about (POD scalars round-trip; owning Vecs follow).
2451        let image = unsafe {
2452            core::slice::from_raw_parts(
2453                self as *const Self as *const u8,
2454                core::mem::size_of::<Self>(),
2455            )
2456        };
2457        out.extend_from_slice(image);
2458        // `mem` is dumped as its two live regions (low/variable + high/single-word),
2459        // skipping the free gap between `lomemmax` and `himemmin` — exactly what a
2460        // `.fmt` dump preserves. Indices are relative to `memmin` (the Vec base).
2461        let lo_len = (self.lomemmax - self.memmin + 1).max(0) as usize;
2462        let hi_start = (self.himemmin - self.memmin).max(0) as usize;
2463        let hi_len = (self.memend - self.himemmin + 1).max(0) as usize;
2464        portable_write_vec_ranges::<memoryword>(
2465            &mut out,
2466            self.mem.as_slice(),
2467            &[(0, lo_len), (hi_start, hi_len)],
2468        );
2469        // Every other owning array: drop the trailing zero region.
2470        macro_rules! write_field {
2471            ($name:ident, $ty:ty) => {{
2472                let slice = self.$name.as_slice();
2473                let used = portable_used_prefix_len(slice);
2474                let ranges: &[(usize, usize)] = if used == 0 { &[] } else { &[(0, used)] };
2475                portable_write_vec_ranges::<$ty>(&mut out, slice, ranges);
2476            }};
2477        }
2478        portable_owning_vecs!(write_field);
2479        // Paged eqtb/hash: geometry + dense low + only the faulted override pages.
2480        portable_write_paged(&mut out, &self.eqtb_paged);
2481        portable_write_paged(&mut out, &self.hash_paged);
2482        out
2483    }
2484
2485    /// Reconstruct an engine state from [`Self::to_portable_bytes`] output.
2486    ///
2487    /// Returns `None` if the magic/stamps don't match the loading target or the
2488    /// buffer is truncated. The input-file pointer table is nulled (those are
2489    /// process-local OS handles). `fontlayoutengine`/`fontmapping` hold *integer
2490    /// font handles*, not addresses, so they are preserved verbatim and the
2491    /// caller re-binds them to a fresh font platform via
2492    /// [`FontPlatform::restore_font_table`] before rendering.
2493    pub(crate) fn from_portable_bytes(bytes: &[u8]) -> Option<Box<Self>> {
2494        let mut cursor = 0usize;
2495        if bytes.get(cursor..cursor + PORTABLE_FORMAT_MAGIC.len())? != &PORTABLE_FORMAT_MAGIC[..] {
2496            return None;
2497        }
2498        cursor += PORTABLE_FORMAT_MAGIC.len();
2499        if *bytes.get(cursor)? as usize != core::mem::size_of::<usize>() {
2500            return None;
2501        }
2502        cursor += 1;
2503        let struct_size =
2504            u64::from_le_bytes(bytes.get(cursor..cursor + 8)?.try_into().ok()?) as usize;
2505        cursor += 8;
2506        if struct_size != core::mem::size_of::<Self>() {
2507            return None;
2508        }
2509        let image = bytes.get(cursor..cursor + core::mem::size_of::<Self>())?;
2510        cursor += core::mem::size_of::<Self>();
2511
2512        let mut state = Box::<Self>::new_uninit();
2513        let state_ptr = state.as_mut_ptr();
2514        // SAFETY: mirrors `clone_boxed` — copy the POD struct image, then
2515        // overwrite every heap-owning field with a freshly-decoded `Vec`
2516        // (`ptr::write` does not drop the garbage header it overwrites), then
2517        // refresh all derived raw pointers from the new Vec bases.
2518        let state = unsafe {
2519            core::ptr::copy_nonoverlapping(
2520                image.as_ptr(),
2521                state_ptr as *mut u8,
2522                core::mem::size_of::<Self>(),
2523            );
2524            let decoded_mem: Vec<memoryword> = portable_read_vec_ranges(bytes, &mut cursor)?;
2525            core::ptr::addr_of_mut!((*state_ptr).mem).write(decoded_mem);
2526            macro_rules! read_field {
2527                ($name:ident, $ty:ty) => {
2528                    let decoded: Vec<$ty> = portable_read_vec_ranges(bytes, &mut cursor)?;
2529                    core::ptr::addr_of_mut!((*state_ptr).$name).write(decoded);
2530                };
2531            }
2532            portable_owning_vecs!(read_field);
2533            let eqtb_paged =
2534                portable_read_paged(bytes, &mut cursor, eqtb_codepoint_default, eqtb_sig_range)?;
2535            core::ptr::addr_of_mut!((*state_ptr).eqtb_paged).write(eqtb_paged);
2536            let hash_paged =
2537                portable_read_paged(bytes, &mut cursor, hash_codepoint_default, hash_sig_range)?;
2538            core::ptr::addr_of_mut!((*state_ptr).hash_paged).write(hash_paged);
2539            // Source-tracking tables are transient per-render parse state, never
2540            // serialized; overwrite the raw-image headers with fresh empties so
2541            // the reloaded state owns valid (empty) tables. `source_tracking`
2542            // itself round-trips as a POD scalar but is reset at each
2543            // `begin_primary_input`, so its dumped value is irrelevant.
2544            core::ptr::addr_of_mut!((*state_ptr).src_spans).write(Vec::new());
2545            core::ptr::addr_of_mut!((*state_ptr).src_dedup)
2546                .write(std::collections::HashMap::new());
2547            core::ptr::addr_of_mut!((*state_ptr).node_src)
2548                .write(PagedArray::new(0, 0, node_src_default, node_src_sig));
2549            core::ptr::addr_of_mut!((*state_ptr).src_native_offsets).write(Vec::new());
2550            core::ptr::addr_of_mut!((*state_ptr).src_stack_cells).write(Vec::new());
2551            core::ptr::addr_of_mut!((*state_ptr).cur_stack_head).write(0);
2552            core::ptr::addr_of_mut!((*state_ptr).node_stack)
2553                .write(PagedArray::new(0, 0, node_stack_default, node_stack_sig));
2554            // Input-file slots are process-local OS handles; null them. The font
2555            // handle tables (`fontlayoutengine`/`fontmapping`) are integer
2556            // handles, preserved for `restore_font_table` to rebind.
2557            for slot in (*state_ptr).inputfile_storage.iter_mut() {
2558                *slot = core::ptr::null_mut();
2559            }
2560            let mut state = state.assume_init();
2561            state.refresh_runtime_pointers();
2562            state
2563        };
2564        Some(state)
2565    }
2566
2567    /// Total bytes backing the engine's dynamic arrays (the dominant runtime
2568    /// footprint: `mem`, `eqtb`, `hash`, `fontinfo`, string pool, trie, …).
2569    /// Counts allocated capacity, so it reflects real resident memory.
2570    pub(crate) fn state_array_bytes(&self) -> usize {
2571        let mut total = self.mem.capacity() * core::mem::size_of::<memoryword>();
2572        macro_rules! accumulate {
2573            ($name:ident, $ty:ty) => {
2574                total += self.$name.capacity() * core::mem::size_of::<$ty>();
2575            };
2576        }
2577        portable_owning_vecs!(accumulate);
2578        total += self.eqtb_paged.resident_bytes() + self.hash_paged.resident_bytes();
2579        total
2580    }
2581
2582    fn allocate_initial_arrays(&mut self) {
2583        self.iniversion = true_0;
2584        self.membot = 0;
2585        self.memmin = self.membot;
2586        // Math-fragment workloads use a tiny fraction of TeX's worst-case main
2587        // memory. Sized down from the 5M default; the latex+amsmath+unicode-math
2588        // format build is the high-water mark and fits comfortably under 1M words.
2589        // Raise if a large document hits `! TeX capacity exceeded (main memory)`.
2590        self.memtop = 999_999;
2591        self.memmax = self.memtop;
2592        self.hashextra = 600_000;
2593        self.eqtbtop = xetex_eqtb_top + self.hashextra;
2594        self.bufsize = 200_000;
2595        self.nestsize = 1_000;
2596        self.maxinopen = 15;
2597        self.paramsize = 20_000;
2598        self.savesize = 200_000;
2599        self.stacksize = 10_000;
2600        self.dvibufsize = 16_384;
2601        self.poolsize = 6_250_000;
2602        self.maxstrings = 500_000;
2603        self.fontmemsize = 1_000_000;
2604        self.fontmax = 500;
2605        // Hyphenation trie construction peak — the production `\patterns` the real
2606        // `latex.ltx` loads need >700K nodes, so this stays at the worst case.
2607        // The six *construction-scratch* trie arrays (everything except the packed
2608        // trietr{c,l,o}) are freed in `seal_as_format_snapshot`, so this large
2609        // size only costs memory transiently during the one-time format build.
2610        self.triesize = 1_100_000;
2611        self.hyphsize = 8_191;
2612        self.primused = 2_100;
2613        self.errorline = 79;
2614        self.halferrorline = 50;
2615        self.maxprintline = 79;
2616        self.expanddepth = 10_000;
2617
2618        self.mem = zeroed_vec((self.memtop - self.memmin + 1) as usize);
2619        // These arrays are `array[0..N]` / `array[1..N]` in web2c, allocated via
2620        // `xmallocarray(T, N)` == `xmalloc((N+1)*sizeof(T))` (cpascal.h), i.e. N+1
2621        // elements so the top index N is valid. The c2rust output allocates each
2622        // as `(dim + 1)` accordingly. Allocating only `dim` here under-sizes every
2623        // one by a slot: in the original C arena the top index harmlessly aliased
2624        // adjacent memory, but this is a bounds-real Rust Vec, so e.g. show_context
2625        // reading `linestack[index+1]` at the top input level was an OOB read.
2626        self.buffer_storage = zeroed_vec((self.bufsize + 1) as usize);
2627        self.nest_storage = zeroed_vec((self.nestsize + 1) as usize);
2628        self.savestack_storage = zeroed_vec((self.savesize + 1) as usize);
2629        self.inputstack_storage = zeroed_vec((self.stacksize + 1) as usize);
2630        self.inputfile_storage = zeroed_vec((self.maxinopen + 1) as usize);
2631        self.eofseen_storage = zeroed_vec((self.maxinopen + 1) as usize);
2632        self.linestack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2633        self.grpstack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2634        self.ifstack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2635        self.sourcefilenamestack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2636        self.fullsourcefilenamestack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2637        self.paramstack_storage = zeroed_vec((self.paramsize + 1) as usize);
2638        self.hyphword_storage = zeroed_vec((self.hyphsize + 1) as usize);
2639        self.hyphlist_storage = zeroed_vec((self.hyphsize + 1) as usize);
2640        self.hyphlink_storage = zeroed_vec((self.hyphsize + 1) as usize);
2641        // eqtb/hash: dense low region [..CODEPOINT_LO), then the per-codepoint
2642        // bands paged lazily. `hash` starts at absolute index `hashoffset`
2643        // (its element 0); both run through absolute index `eqtbtop`.
2644        self.eqtb_paged = PagedArray::new(
2645            0,
2646            (self.eqtbtop + 1) as usize,
2647            eqtb_codepoint_default,
2648            eqtb_sig_range,
2649        );
2650        self.hash_paged = PagedArray::new(
2651            hashoffset as usize,
2652            (self.eqtbtop + 1) as usize,
2653            hash_codepoint_default,
2654            hash_sig_range,
2655        );
2656        self.strstart_storage = zeroed_vec((self.maxstrings + 1) as usize);
2657        self.strpool_storage = zeroed_vec((self.poolsize + 1) as usize);
2658        self.fontinfo_storage = zeroed_vec((self.fontmemsize + 1) as usize);
2659        let font_slots = (self.fontmax + 1) as usize;
2660        self.bcharlabel_storage = zeroed_vec(font_slots);
2661        self.charbase_storage = zeroed_vec(font_slots);
2662        self.widthbase_storage = zeroed_vec(font_slots);
2663        self.heightbase_storage = zeroed_vec(font_slots);
2664        self.depthbase_storage = zeroed_vec(font_slots);
2665        self.italicbase_storage = zeroed_vec(font_slots);
2666        self.ligkernbase_storage = zeroed_vec(font_slots);
2667        self.kernbase_storage = zeroed_vec(font_slots);
2668        self.extenbase_storage = zeroed_vec(font_slots);
2669        self.parambase_storage = zeroed_vec(font_slots);
2670        self.fontarea_storage = zeroed_vec(font_slots);
2671        self.fontname_storage = zeroed_vec(font_slots);
2672        self.fontbc_storage = zeroed_vec(font_slots);
2673        self.fontec_storage = zeroed_vec(font_slots);
2674        self.fontbchar_storage = zeroed_vec(font_slots);
2675        self.fontfalsebchar_storage = zeroed_vec(font_slots);
2676        self.fontcheck_storage = zeroed_vec(font_slots);
2677        self.fontdsize_storage = zeroed_vec(font_slots);
2678        self.fontsize_storage = zeroed_vec(font_slots);
2679        self.fontflags_storage = zeroed_vec(font_slots);
2680        self.fontglue_storage = zeroed_vec(font_slots);
2681        self.fontlayoutengine_storage = zeroed_vec(font_slots);
2682        self.fontletterspace_storage = zeroed_vec(font_slots);
2683        self.fontmapping_storage = zeroed_vec(font_slots);
2684        self.fontparams_storage = zeroed_vec(font_slots);
2685        self.fontused_storage = zeroed_vec(font_slots);
2686        self.nativetext_storage = Vec::new();
2687        self.hyphenchar_storage = zeroed_vec(font_slots);
2688        self.skewchar_storage = zeroed_vec(font_slots);
2689        let trie_slots = (self.triesize + 1) as usize;
2690        self.triec_storage = zeroed_vec(trie_slots);
2691        self.triehash_storage = zeroed_vec(trie_slots);
2692        self.triel_storage = zeroed_vec(trie_slots);
2693        self.trieo_storage = zeroed_vec(trie_slots);
2694        self.trier_storage = zeroed_vec(trie_slots);
2695        self.trietaken_storage = zeroed_vec(trie_slots);
2696        self.trietrc_storage = zeroed_vec(trie_slots);
2697        self.trietrl_storage = zeroed_vec(trie_slots);
2698        self.trietro_storage = zeroed_vec(trie_slots);
2699        self.refresh_runtime_pointers();
2700    }
2701}
2702
2703fn zeroed_vec<T>(len: usize) -> Vec<T>
2704where
2705    T: Clone + Default,
2706{
2707    vec![T::default(); len]
2708}
2709
2710fn pointer_or_null<T>(storage: &mut Vec<T>) -> *mut T {
2711    if storage.is_empty() {
2712        core::ptr::null_mut()
2713    } else {
2714        storage.as_mut_ptr()
2715    }
2716}
2717
2718#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2719pub enum EngineProfileKind {
2720    Tex,
2721    Etex,
2722    Xetex,
2723}
2724
2725#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2726pub struct EngineProfile {
2727    pub id: &'static str,
2728    pub kind: EngineProfileKind,
2729    pub etex: bool,
2730    pub xetex: bool,
2731    pub unicode_scalars: bool,
2732    pub unicode_math: bool,
2733    pub native_fonts: bool,
2734}
2735
2736#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2737struct WriteTokenConstants {
2738    open_group_token: halfword,
2739    end_write_token: halfword,
2740    close_group_token: halfword,
2741}
2742
2743impl EngineProfile {
2744    pub const fn tex() -> Self {
2745        Self {
2746            id: "tex",
2747            kind: EngineProfileKind::Tex,
2748            etex: false,
2749            xetex: false,
2750            unicode_scalars: false,
2751            unicode_math: false,
2752            native_fonts: false,
2753        }
2754    }
2755
2756    pub const fn etex() -> Self {
2757        Self {
2758            id: "etex",
2759            kind: EngineProfileKind::Etex,
2760            etex: true,
2761            xetex: false,
2762            unicode_scalars: false,
2763            unicode_math: false,
2764            native_fonts: false,
2765        }
2766    }
2767
2768    pub const fn xetex() -> Self {
2769        Self {
2770            id: "xetex",
2771            kind: EngineProfileKind::Xetex,
2772            etex: true,
2773            xetex: true,
2774            unicode_scalars: true,
2775            unicode_math: true,
2776            native_fonts: false,
2777        }
2778    }
2779
2780    const fn write_token_constants(self) -> WriteTokenConstants {
2781        match self.kind {
2782            EngineProfileKind::Tex | EngineProfileKind::Etex => WriteTokenConstants {
2783                open_group_token: 637,
2784                end_write_token: 19617,
2785                close_group_token: 379,
2786            },
2787            EngineProfileKind::Xetex => WriteTokenConstants {
2788                open_group_token: 4_194_429,
2789                end_write_token: 34_749_089,
2790                close_group_token: 2_097_275,
2791            },
2792        }
2793    }
2794}
2795
2796pub struct PortableFormatImage {
2797    state: Box<PortableTexState>,
2798}
2799
2800#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2801pub struct PortableNodeHandle(pub i32);
2802
2803#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2804pub enum PortableNodeKind {
2805    HorizontalBox,
2806    VerticalBox,
2807    Rule,
2808    Insertion,
2809    Mark,
2810    Adjustment,
2811    Ligature,
2812    Discretionary,
2813    OutputWhatsit,
2814    Whatsit,
2815    Math,
2816    Glue,
2817    Kern,
2818    Penalty,
2819    UnsetBox,
2820    Noad,
2821    Style,
2822    Choice,
2823    Character,
2824    NativeWord,
2825    NativeGlyph,
2826    Unknown(i32),
2827}
2828
2829#[derive(Clone, Debug, PartialEq)]
2830pub struct PortableNodeSnapshot {
2831    pub handle: PortableNodeHandle,
2832    pub kind: PortableNodeKind,
2833    pub subtype: i32,
2834    pub source: Option<PortableSourceSpan>,
2835    pub link: Option<PortableNodeHandle>,
2836    pub font: i32,
2837    pub character: i32,
2838    pub width: i32,
2839    pub height: i32,
2840    pub depth: i32,
2841    pub shift: i32,
2842    pub list: Option<PortableNodeHandle>,
2843    pub native_glyphs: Vec<PortableNativeGlyph>,
2844    /// Box glue-set ratio (from `hpack`/`vpack`); meaningful for hlist/vlist.
2845    pub glue_set: f64,
2846    /// Box glue sign: 0 normal, 1 stretching, 2 shrinking.
2847    pub glue_sign: i32,
2848    /// Box glue order (0..3) that participates in stretching/shrinking.
2849    pub glue_order: i32,
2850    /// Glue node's spec stretch amount (raw glue order in `glue_stretch_order`).
2851    pub glue_stretch: i32,
2852    /// Glue node's spec shrink amount.
2853    pub glue_shrink: i32,
2854    /// Order (0..3) of the glue node's stretch component.
2855    pub glue_stretch_order: i32,
2856    /// Order (0..3) of the glue node's shrink component.
2857    pub glue_shrink_order: i32,
2858}
2859
2860impl Clone for PortableFormatImage {
2861    fn clone(&self) -> Self {
2862        Self {
2863            state: self.state.clone_boxed(),
2864        }
2865    }
2866}
2867
2868impl PortableFormatImage {
2869    pub fn empty() -> Self {
2870        Self {
2871            state: PortableTexState::new_boxed_default(),
2872        }
2873    }
2874
2875    fn from_engine_state(state: &PortableTexState) -> Self {
2876        let mut state = state.clone_boxed();
2877        state.seal_as_format_snapshot();
2878        Self {
2879            state,
2880        }
2881    }
2882
2883    /// Wrap an already-sealed engine state, taking ownership without cloning.
2884    /// Used by [`PortableTexEngine::into_format`].
2885    fn from_sealed_state(state: Box<PortableTexState>) -> Self {
2886        Self { state }
2887    }
2888
2889    /// Serialize this format image to a portable byte buffer (a dumped `.fmt`)
2890    /// that [`Self::from_bytes`] can reload. Same-target only — see
2891    /// [`PortableTexState::to_portable_bytes`].
2892    #[must_use]
2893    pub fn to_bytes(&self) -> Vec<u8> {
2894        self.state.to_portable_bytes()
2895    }
2896
2897    /// Reload a format image previously produced by [`Self::to_bytes`]. Returns
2898    /// `None` if the buffer is not a format image for this build target.
2899    #[must_use]
2900    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
2901        Some(Self {
2902            state: PortableTexState::from_portable_bytes(bytes)?,
2903        })
2904    }
2905
2906    /// Resident bytes of the engine's dynamic arrays once instantiated from this
2907    /// image — the dominant runtime memory footprint.
2908    #[must_use]
2909    pub fn state_array_bytes(&self) -> usize {
2910        self.state.state_array_bytes()
2911    }
2912
2913}
2914
2915/// Size in bytes of one TeX `memory_word` in this build. Real XeTeX packs it to
2916/// 8; this engine uses a 16-byte word (the `four_quarters` view is `u16`, but
2917/// `two_halves`/`cint` keep the c2rust 32-bit alignment).
2918#[must_use]
2919pub fn memory_word_bytes() -> usize {
2920    core::mem::size_of::<memoryword>()
2921}
2922
2923pub struct PortableTexEngine<'resources> {
2924    pub(crate) state: Box<PortableTexState>,
2925    pub(crate) profile: EngineProfile,
2926    pub(crate) resources: Box<dyn ResourceProvider + 'resources>,
2927    pub(crate) fonts: Box<dyn FontPlatform + 'resources>,
2928    pub(crate) platform: Box<dyn PortablePlatform + 'resources>,
2929    pub(crate) nameoffile_storage: Vec<UTF8code>,
2930    native_glyph_infos: std::collections::BTreeMap<i32, PortableNativeGlyphInfo>,
2931    character_protrusions: std::collections::BTreeMap<(integer, u32, integer), integer>,
2932    pub(crate) resource_requests: usize,
2933    pub(crate) resource_request_records: Vec<PortableResourceRequestRecord>,
2934    pub(crate) virtual_files: std::collections::BTreeMap<String, Vec<u8>>,
2935    pub(crate) transcript_bytes: Vec<u8>,
2936    pub(crate) current_input_package_owner: Option<String>,
2937    pub(crate) stripped_page_builds: usize,
2938    pub(crate) stripped_shipouts: usize,
2939    pub(crate) stripped_special_outputs: usize,
2940    pub(crate) stripped_picture_loads: usize,
2941    pub(crate) stripped_source_specials: usize,
2942    pub(crate) stripped_write_whatsit_diagnostics: usize,
2943    pub(crate) stripped_pdf_extensions: usize,
2944    pub(crate) stripped_page_top_prunes: usize,
2945    pub(crate) last_stripped_shipout_box: Option<PortableNodeHandle>,
2946    pub(crate) fragment_capture_enabled: bool,
2947    pub(crate) format_initialization: bool,
2948    pub(crate) captured_fragment_root: Option<PortableNodeHandle>,
2949    pub(crate) last_abort_status: Option<integer>,
2950    /// Captured message from the most recent surfaced [`EngineError`] (mirrors
2951    /// `last_abort_status` for the error channel). The driver boundary stores it
2952    /// here so the host can read *what* went wrong after a failed run.
2953    pub(crate) last_error_message: Option<String>,
2954    /// When set, the engine enforces the "render one math expression" sandbox:
2955    /// breakout (`$`), job-control (`\end`), and IO (`\input`, `\write`, ...)
2956    /// tokens are rejected as [`EngineError`]s, and a work budget bounds runtime.
2957    /// Off during format construction (which legitimately uses those); the host
2958    /// sets it for fragment renders. See [`PortableTexEngine::sandbox_reject`].
2959    pub(crate) sandbox: bool,
2960    /// Sandbox bookkeeping: the live MATH nesting depth (`init_math` `+1`,
2961    /// `after_math` `-1`). The wrapper `$` opens depth 1 and the user content stays at
2962    /// depth >= 1; nested math inside a text block (`\hbox{$x$}`, `\text{$y$}`) opens
2963    /// depth 2+. Depth only returns to 0 when the wrapper math is CLOSED -- so a `$` that
2964    /// re-opens math at depth 0 (after `sandbox_math_opened`) is a breakout, while a `$`
2965    /// at depth >= 1 is legitimate nested math.
2966    pub(crate) sandbox_math_depth: i32,
2967    /// Sandbox: set once the wrapper math has opened, so the first depth-0 `init_math`
2968    /// (the wrapper) is allowed but any later depth-0 re-open (a user `$` breakout) is
2969    /// rejected.
2970    pub(crate) sandbox_math_opened: bool,
2971    /// Sandbox work budget: main-control iterations consumed this run, to bound
2972    /// runaway expansion / infinite loops (`\def\x{\x}\x`).
2973    pub(crate) sandbox_ops: u64,
2974}
2975
2976pub(crate) fn zround(value: real) -> integer {
2977    value.round() as integer
2978}
2979
2980impl<'resources> PortableTexEngine<'resources> {
2981    pub fn from_format<R>(
2982        profile: EngineProfile,
2983        format: &PortableFormatImage,
2984        resources: R,
2985    ) -> Self
2986    where
2987        R: ResourceProvider + 'resources,
2988    {
2989        Self {
2990            state: format.state.clone_boxed(),
2991            profile,
2992            resources: Box::new(resources),
2993            fonts: Box::<EmptyFontPlatform>::default(),
2994            platform: Box::<EmptyPlatform>::default(),
2995            nameoffile_storage: Vec::new(),
2996            native_glyph_infos: std::collections::BTreeMap::new(),
2997            character_protrusions: std::collections::BTreeMap::new(),
2998            resource_requests: 0,
2999            resource_request_records: Vec::new(),
3000            virtual_files: std::collections::BTreeMap::new(),
3001            transcript_bytes: Vec::new(),
3002            current_input_package_owner: None,
3003            stripped_page_builds: 0,
3004            stripped_shipouts: 0,
3005            stripped_special_outputs: 0,
3006            stripped_picture_loads: 0,
3007            stripped_source_specials: 0,
3008            stripped_write_whatsit_diagnostics: 0,
3009            stripped_pdf_extensions: 0,
3010            stripped_page_top_prunes: 0,
3011            last_stripped_shipout_box: None,
3012            fragment_capture_enabled: false,
3013            format_initialization: false,
3014            captured_fragment_root: None,
3015            last_abort_status: None,
3016            last_error_message: None,
3017            sandbox: false,
3018            sandbox_math_depth: 0,
3019            sandbox_math_opened: false,
3020            sandbox_ops: 0,
3021        }
3022    }
3023
3024    pub fn with_font_platform<F>(mut self, fonts: F) -> Self
3025    where
3026        F: FontPlatform + 'resources,
3027    {
3028        self.fonts = Box::new(fonts);
3029        self
3030    }
3031
3032    pub fn with_platform<P>(mut self, platform: P) -> Self
3033    where
3034        P: PortablePlatform + 'resources,
3035    {
3036        self.platform = Box::new(platform);
3037        self
3038    }
3039
3040    pub fn profile(&self) -> EngineProfile {
3041        self.profile
3042    }
3043
3044    pub fn initialize_format_state(self: &mut Self) -> bool {
3045        self.catch_engine_abort(|engine| unsafe {
3046            let this = engine as *mut PortableTexEngine<'_>;
3047            engine.state.allocate_initial_arrays();
3048            engine.initialize();
3049            if engine.supports_etex() {
3050                engine.state.eTeXmode = 1 as eightbits;
3051            }
3052            // `getstringsstarted`, `initprim`, and the two startup-primitive
3053            // methods are abort-reachable (`EngineFlow<..>`), so thread `?`. The
3054            // `abort_engine(this, 1)` path here is the "could not start strings"
3055            // error abort (status 1).
3056            if engine.getstringsstarted()? == 0 {
3057                Self::abort_engine(this, 1 as integer)?;
3058            }
3059            engine.initprim()?;
3060            engine.init_etex_startup_primitives()?;
3061            engine.init_xetex_startup_primitives()?;
3062            engine.state.initstrptr = engine.state.strptr;
3063            engine.state.initpoolptr = engine.state.poolptr;
3064            // Inter-element math spacing offset. The original computes this in
3065            // `mainbody` (`magicoffset = strstart[math_spacing] - 9*ord_noad`),
3066            // which the importer replaces with this init path, so the assignment
3067            // was lost and `magicoffset` stayed 0 -- making the spacing lookup in
3068            // mlist_to_hlist index arbitrary pool data and trip confusion("mlist4")
3069            // on the first binary operator. The engine loads xetex.pool for every
3070            // profile, so math_spacing is xetex string 784 (66320 - 65536) and
3071            // ord_noad is 16.
3072            engine.state.magicoffset =
3073                (*engine.state.strstart.offset(784) as integer - 9 * 16) as integer;
3074            engine.state.alignstate = 1000000 as integer;
3075            Ok(())
3076        })
3077    }
3078
3079    pub(crate) fn ensure_nativetext_capacity(
3080        engine: &mut PortableTexEngine<'_>,
3081        required: integer,
3082    ) {
3083        let required = required.max(0) as usize;
3084        if engine.state.nativetext_storage.len() < required {
3085            engine.state.nativetext_storage.resize(required, UTF16code::default());
3086        }
3087        engine.state.nativetext = pointer_or_null(&mut engine.state.nativetext_storage);
3088    }
3089
3090    unsafe fn init_etex_startup_primitives(&mut self) -> EngineFlow<()> {
3091        if !self.supports_etex() || self.is_xetex() {
3092            return Ok(());
3093        }
3094        self.state.nonewcontrolsequence = false_0 as boolean;
3095        (&mut *(self as *mut PortableTexEngine<'_>))
3096            .zprimitive(1360 as i32, 70 as i32 as quarterword, 3 as i32)?;
3097        (&mut *(self as *mut PortableTexEngine<'_>))
3098            .zprimitive(1361 as i32, 70 as i32 as quarterword, 6 as i32)?;
3099        (&mut *(self as *mut PortableTexEngine<'_>))
3100            .zprimitive(765 as i32, 108 as i32 as quarterword, 5 as i32)?;
3101        (&mut *(self as *mut PortableTexEngine<'_>))
3102            .zprimitive(1363 as i32, 72 as i32 as quarterword, 25067 as i32)?;
3103        (&mut *(self as *mut PortableTexEngine<'_>))
3104            .zprimitive(1364 as i32, 73 as i32 as quarterword, 27234 as i32)?;
3105        (&mut *(self as *mut PortableTexEngine<'_>))
3106            .zprimitive(1365 as i32, 73 as i32 as quarterword, 27235 as i32)?;
3107        (&mut *(self as *mut PortableTexEngine<'_>))
3108            .zprimitive(1366 as i32, 73 as i32 as quarterword, 27236 as i32)?;
3109        (&mut *(self as *mut PortableTexEngine<'_>))
3110            .zprimitive(1367 as i32, 73 as i32 as quarterword, 27237 as i32)?;
3111        (&mut *(self as *mut PortableTexEngine<'_>))
3112            .zprimitive(1368 as i32, 73 as i32 as quarterword, 27238 as i32)?;
3113        (&mut *(self as *mut PortableTexEngine<'_>))
3114            .zprimitive(1369 as i32, 73 as i32 as quarterword, 27239 as i32)?;
3115        (&mut *(self as *mut PortableTexEngine<'_>))
3116            .zprimitive(1370 as i32, 73 as i32 as quarterword, 27240 as i32)?;
3117        (&mut *(self as *mut PortableTexEngine<'_>))
3118            .zprimitive(1371 as i32, 73 as i32 as quarterword, 27241 as i32)?;
3119        (&mut *(self as *mut PortableTexEngine<'_>))
3120            .zprimitive(1372 as i32, 73 as i32 as quarterword, 27242 as i32)?;
3121        (&mut *(self as *mut PortableTexEngine<'_>))
3122            .zprimitive(1387 as i32, 70 as i32 as quarterword, 7 as i32)?;
3123        (&mut *(self as *mut PortableTexEngine<'_>))
3124            .zprimitive(1388 as i32, 70 as i32 as quarterword, 8 as i32)?;
3125        (&mut *(self as *mut PortableTexEngine<'_>))
3126            .zprimitive(1389 as i32, 70 as i32 as quarterword, 9 as i32)?;
3127        (&mut *(self as *mut PortableTexEngine<'_>))
3128            .zprimitive(1390 as i32, 70 as i32 as quarterword, 10 as i32)?;
3129        (&mut *(self as *mut PortableTexEngine<'_>))
3130            .zprimitive(1391 as i32, 70 as i32 as quarterword, 11 as i32)?;
3131        (&mut *(self as *mut PortableTexEngine<'_>))
3132            .zprimitive(1392 as i32, 70 as i32 as quarterword, 14 as i32)?;
3133        (&mut *(self as *mut PortableTexEngine<'_>))
3134            .zprimitive(1393 as i32, 70 as i32 as quarterword, 15 as i32)?;
3135        (&mut *(self as *mut PortableTexEngine<'_>))
3136            .zprimitive(1394 as i32, 70 as i32 as quarterword, 16 as i32)?;
3137        (&mut *(self as *mut PortableTexEngine<'_>))
3138            .zprimitive(1395 as i32, 70 as i32 as quarterword, 17 as i32)?;
3139        (&mut *(self as *mut PortableTexEngine<'_>))
3140            .zprimitive(1396 as i32, 70 as i32 as quarterword, 18 as i32)?;
3141        (&mut *(self as *mut PortableTexEngine<'_>))
3142            .zprimitive(1397 as i32, 70 as i32 as quarterword, 19 as i32)?;
3143        (&mut *(self as *mut PortableTexEngine<'_>))
3144            .zprimitive(1398 as i32, 70 as i32 as quarterword, 20 as i32)?;
3145        (&mut *(self as *mut PortableTexEngine<'_>))
3146            .zprimitive(1399 as i32, 19 as i32 as quarterword, 4 as i32)?;
3147        (&mut *(self as *mut PortableTexEngine<'_>))
3148            .zprimitive(1401 as i32, 19 as i32 as quarterword, 5 as i32)?;
3149        (&mut *(self as *mut PortableTexEngine<'_>))
3150            .zprimitive(1402 as i32, 109 as i32 as quarterword, 1 as i32)?;
3151        (&mut *(self as *mut PortableTexEngine<'_>))
3152            .zprimitive(1403 as i32, 109 as i32 as quarterword, 5 as i32)?;
3153        (&mut *(self as *mut PortableTexEngine<'_>))
3154            .zprimitive(1404 as i32, 19 as i32 as quarterword, 6 as i32)?;
3155        (&mut *(self as *mut PortableTexEngine<'_>))
3156            .zprimitive(1408 as i32, 82 as i32 as quarterword, 2 as i32)?;
3157        (&mut *(self as *mut PortableTexEngine<'_>))
3158            .zprimitive(908 as i32, 49 as i32 as quarterword, 1 as i32)?;
3159        (&mut *(self as *mut PortableTexEngine<'_>))
3160            .zprimitive(1412 as i32, 73 as i32 as quarterword, 27243 as i32)?;
3161        (&mut *(self as *mut PortableTexEngine<'_>))
3162            .zprimitive(1413 as i32, 33 as i32 as quarterword, 6 as i32)?;
3163        (&mut *(self as *mut PortableTexEngine<'_>))
3164            .zprimitive(1414 as i32, 33 as i32 as quarterword, 7 as i32)?;
3165        (&mut *(self as *mut PortableTexEngine<'_>))
3166            .zprimitive(1415 as i32, 33 as i32 as quarterword, 10 as i32)?;
3167        (&mut *(self as *mut PortableTexEngine<'_>))
3168            .zprimitive(1416 as i32, 33 as i32 as quarterword, 11 as i32)?;
3169        (&mut *(self as *mut PortableTexEngine<'_>))
3170            .zprimitive(1425 as i32, 104 as i32 as quarterword, 2 as i32)?;
3171        (&mut *(self as *mut PortableTexEngine<'_>))
3172            .zprimitive(1427 as i32, 96 as i32 as quarterword, 1 as i32)?;
3173        (&mut *(self as *mut PortableTexEngine<'_>))
3174            .zprimitive(799 as i32, 102 as i32 as quarterword, 1 as i32)?;
3175        (&mut *(self as *mut PortableTexEngine<'_>))
3176            .zprimitive(1428 as i32, 105 as i32 as quarterword, 17 as i32)?;
3177        (&mut *(self as *mut PortableTexEngine<'_>))
3178            .zprimitive(1429 as i32, 105 as i32 as quarterword, 18 as i32)?;
3179        (&mut *(self as *mut PortableTexEngine<'_>))
3180            .zprimitive(1430 as i32, 105 as i32 as quarterword, 19 as i32)?;
3181        (&mut *(self as *mut PortableTexEngine<'_>))
3182            .zprimitive(1217 as i32, 93 as i32 as quarterword, 8 as i32)?;
3183        (&mut *(self as *mut PortableTexEngine<'_>))
3184            .zprimitive(1436 as i32, 70 as i32 as quarterword, 25 as i32)?;
3185        (&mut *(self as *mut PortableTexEngine<'_>))
3186            .zprimitive(1437 as i32, 70 as i32 as quarterword, 26 as i32)?;
3187        (&mut *(self as *mut PortableTexEngine<'_>))
3188            .zprimitive(1438 as i32, 70 as i32 as quarterword, 27 as i32)?;
3189        (&mut *(self as *mut PortableTexEngine<'_>))
3190            .zprimitive(1439 as i32, 70 as i32 as quarterword, 28 as i32)?;
3191        (&mut *(self as *mut PortableTexEngine<'_>))
3192            .zprimitive(1443 as i32, 70 as i32 as quarterword, 12 as i32)?;
3193        (&mut *(self as *mut PortableTexEngine<'_>))
3194            .zprimitive(1444 as i32, 70 as i32 as quarterword, 13 as i32)?;
3195        (&mut *(self as *mut PortableTexEngine<'_>))
3196            .zprimitive(1445 as i32, 70 as i32 as quarterword, 21 as i32)?;
3197        (&mut *(self as *mut PortableTexEngine<'_>))
3198            .zprimitive(1446 as i32, 70 as i32 as quarterword, 22 as i32)?;
3199        (&mut *(self as *mut PortableTexEngine<'_>))
3200            .zprimitive(1447 as i32, 70 as i32 as quarterword, 23 as i32)?;
3201        (&mut *(self as *mut PortableTexEngine<'_>))
3202            .zprimitive(1448 as i32, 70 as i32 as quarterword, 24 as i32)?;
3203        (&mut *(self as *mut PortableTexEngine<'_>))
3204            .zprimitive(1449 as i32, 18 as i32 as quarterword, 5 as i32)?;
3205        (&mut *(self as *mut PortableTexEngine<'_>))
3206            .zprimitive(1450 as i32, 110 as i32 as quarterword, 5 as i32)?;
3207        (&mut *(self as *mut PortableTexEngine<'_>))
3208            .zprimitive(1451 as i32, 110 as i32 as quarterword, 6 as i32)?;
3209        (&mut *(self as *mut PortableTexEngine<'_>))
3210            .zprimitive(1452 as i32, 110 as i32 as quarterword, 7 as i32)?;
3211        (&mut *(self as *mut PortableTexEngine<'_>))
3212            .zprimitive(1453 as i32, 110 as i32 as quarterword, 8 as i32)?;
3213        (&mut *(self as *mut PortableTexEngine<'_>))
3214            .zprimitive(1454 as i32, 110 as i32 as quarterword, 9 as i32)?;
3215        (&mut *(self as *mut PortableTexEngine<'_>))
3216            .zprimitive(1458 as i32, 24 as i32 as quarterword, 2 as i32)?;
3217        (&mut *(self as *mut PortableTexEngine<'_>))
3218            .zprimitive(1459 as i32, 24 as i32 as quarterword, 3 as i32)?;
3219        (&mut *(self as *mut PortableTexEngine<'_>))
3220            .zprimitive(1460 as i32, 84 as i32 as quarterword, 25324 as i32)?;
3221        (&mut *(self as *mut PortableTexEngine<'_>))
3222            .zprimitive(1461 as i32, 84 as i32 as quarterword, 25325 as i32)?;
3223        (&mut *(self as *mut PortableTexEngine<'_>))
3224            .zprimitive(1462 as i32, 84 as i32 as quarterword, 25326 as i32)?;
3225        (&mut *(self as *mut PortableTexEngine<'_>))
3226            .zprimitive(1463 as i32, 84 as i32 as quarterword, 25327 as i32)?;
3227        self.state.eTeXmode = 1 as eightbits;
3228        Ok(())
3229    }
3230    unsafe fn init_xetex_startup_primitives(&mut self) -> EngineFlow<()> {
3231        if !self.is_xetex() {
3232            return Ok(());
3233        }
3234        self.state.nonewcontrolsequence = false_0 as boolean;
3235        (&mut *(self as *mut PortableTexEngine<'_>))
3236            .zprimitive(66755 as i64 as strnumber, 59 as i32 as quarterword, 41 as i32)?;
3237        (&mut *(self as *mut PortableTexEngine<'_>))
3238            .zprimitive(66756 as i64 as strnumber, 59 as i32 as quarterword, 42 as i32)?;
3239        (&mut *(self as *mut PortableTexEngine<'_>))
3240            .zprimitive(66757 as i64 as strnumber, 59 as i32 as quarterword, 43 as i32)?;
3241        (&mut *(self as *mut PortableTexEngine<'_>))
3242            .zprimitive(66758 as i64 as strnumber, 59 as i32 as quarterword, 46 as i32)?;
3243        (&mut *(self as *mut PortableTexEngine<'_>))
3244            .zprimitive(
3245                66759 as i64 as strnumber,
3246                73 as i32 as quarterword,
3247                1206306 as i64 as halfword,
3248            )?;
3249        (&mut *(self as *mut PortableTexEngine<'_>))
3250            .zprimitive(66760 as i64 as strnumber, 59 as i32 as quarterword, 23 as i32)?;
3251        (&mut *(self as *mut PortableTexEngine<'_>))
3252            .zprimitive(66816 as i64 as strnumber, 71 as i32 as quarterword, 3 as i32)?;
3253        (&mut *(self as *mut PortableTexEngine<'_>))
3254            .zprimitive(66817 as i64 as strnumber, 71 as i32 as quarterword, 19 as i32)?;
3255        (&mut *(self as *mut PortableTexEngine<'_>))
3256            .zprimitive(66115 as i64 as strnumber, 111 as i32 as quarterword, 5 as i32)?;
3257        (&mut *(self as *mut PortableTexEngine<'_>))
3258            .zprimitive(66818 as i64 as strnumber, 71 as i32 as quarterword, 27 as i32)?;
3259        (&mut *(self as *mut PortableTexEngine<'_>))
3260            .zprimitive(
3261                66819 as i64 as strnumber,
3262                111 as i32 as quarterword,
3263                33 as i32,
3264            )?;
3265        (&mut *(self as *mut PortableTexEngine<'_>))
3266            .zprimitive(66820 as i64 as strnumber, 71 as i32 as quarterword, 28 as i32)?;
3267        (&mut *(self as *mut PortableTexEngine<'_>))
3268            .zprimitive(66821 as i64 as strnumber, 71 as i32 as quarterword, 29 as i32)?;
3269        (&mut *(self as *mut PortableTexEngine<'_>))
3270            .zprimitive(66822 as i64 as strnumber, 71 as i32 as quarterword, 30 as i32)?;
3271        (&mut *(self as *mut PortableTexEngine<'_>))
3272            .zprimitive(66823 as i64 as strnumber, 71 as i32 as quarterword, 31 as i32)?;
3273        (&mut *(self as *mut PortableTexEngine<'_>))
3274            .zprimitive(66824 as i64 as strnumber, 71 as i32 as quarterword, 32 as i32)?;
3275        (&mut *(self as *mut PortableTexEngine<'_>))
3276            .zprimitive(66825 as i64 as strnumber, 71 as i32 as quarterword, 33 as i32)?;
3277        (&mut *(self as *mut PortableTexEngine<'_>))
3278            .zprimitive(66826 as i64 as strnumber, 71 as i32 as quarterword, 34 as i32)?;
3279        (&mut *(self as *mut PortableTexEngine<'_>))
3280            .zprimitive(66827 as i64 as strnumber, 71 as i32 as quarterword, 35 as i32)?;
3281        (&mut *(self as *mut PortableTexEngine<'_>))
3282            .zprimitive(66828 as i64 as strnumber, 71 as i32 as quarterword, 36 as i32)?;
3283        (&mut *(self as *mut PortableTexEngine<'_>))
3284            .zprimitive(66829 as i64 as strnumber, 71 as i32 as quarterword, 37 as i32)?;
3285        (&mut *(self as *mut PortableTexEngine<'_>))
3286            .zprimitive(66830 as i64 as strnumber, 71 as i32 as quarterword, 38 as i32)?;
3287        (&mut *(self as *mut PortableTexEngine<'_>))
3288            .zprimitive(66831 as i64 as strnumber, 71 as i32 as quarterword, 39 as i32)?;
3289        (&mut *(self as *mut PortableTexEngine<'_>))
3290            .zprimitive(66832 as i64 as strnumber, 71 as i32 as quarterword, 40 as i32)?;
3291        (&mut *(self as *mut PortableTexEngine<'_>))
3292            .zprimitive(66833 as i64 as strnumber, 71 as i32 as quarterword, 41 as i32)?;
3293        (&mut *(self as *mut PortableTexEngine<'_>))
3294            .zprimitive(66834 as i64 as strnumber, 71 as i32 as quarterword, 42 as i32)?;
3295        (&mut *(self as *mut PortableTexEngine<'_>))
3296            .zprimitive(
3297                66835 as i64 as strnumber,
3298                111 as i32 as quarterword,
3299                34 as i32,
3300            )?;
3301        (&mut *(self as *mut PortableTexEngine<'_>))
3302            .zprimitive(
3303                66836 as i64 as strnumber,
3304                111 as i32 as quarterword,
3305                35 as i32,
3306            )?;
3307        (&mut *(self as *mut PortableTexEngine<'_>))
3308            .zprimitive(
3309                66837 as i64 as strnumber,
3310                111 as i32 as quarterword,
3311                36 as i32,
3312            )?;
3313        (&mut *(self as *mut PortableTexEngine<'_>))
3314            .zprimitive(66838 as i64 as strnumber, 71 as i32 as quarterword, 43 as i32)?;
3315        (&mut *(self as *mut PortableTexEngine<'_>))
3316            .zprimitive(66839 as i64 as strnumber, 71 as i32 as quarterword, 44 as i32)?;
3317        (&mut *(self as *mut PortableTexEngine<'_>))
3318            .zprimitive(66840 as i64 as strnumber, 71 as i32 as quarterword, 45 as i32)?;
3319        (&mut *(self as *mut PortableTexEngine<'_>))
3320            .zprimitive(66841 as i64 as strnumber, 71 as i32 as quarterword, 46 as i32)?;
3321        (&mut *(self as *mut PortableTexEngine<'_>))
3322            .zprimitive(66842 as i64 as strnumber, 71 as i32 as quarterword, 47 as i32)?;
3323        (&mut *(self as *mut PortableTexEngine<'_>))
3324            .zprimitive(66843 as i64 as strnumber, 71 as i32 as quarterword, 48 as i32)?;
3325        (&mut *(self as *mut PortableTexEngine<'_>))
3326            .zprimitive(66844 as i64 as strnumber, 71 as i32 as quarterword, 49 as i32)?;
3327        (&mut *(self as *mut PortableTexEngine<'_>))
3328            .zprimitive(66845 as i64 as strnumber, 71 as i32 as quarterword, 50 as i32)?;
3329        (&mut *(self as *mut PortableTexEngine<'_>))
3330            .zprimitive(66846 as i64 as strnumber, 71 as i32 as quarterword, 55 as i32)?;
3331        (&mut *(self as *mut PortableTexEngine<'_>))
3332            .zprimitive(
3333                66847 as i64 as strnumber,
3334                111 as i32 as quarterword,
3335                37 as i32,
3336            )?;
3337        (&mut *(self as *mut PortableTexEngine<'_>))
3338            .zprimitive(66848 as i64 as strnumber, 71 as i32 as quarterword, 51 as i32)?;
3339        (&mut *(self as *mut PortableTexEngine<'_>))
3340            .zprimitive(66849 as i64 as strnumber, 71 as i32 as quarterword, 52 as i32)?;
3341        (&mut *(self as *mut PortableTexEngine<'_>))
3342            .zprimitive(66850 as i64 as strnumber, 71 as i32 as quarterword, 53 as i32)?;
3343        (&mut *(self as *mut PortableTexEngine<'_>))
3344            .zprimitive(66851 as i64 as strnumber, 71 as i32 as quarterword, 54 as i32)?;
3345        (&mut *(self as *mut PortableTexEngine<'_>))
3346            .zprimitive(
3347                66861 as i64 as strnumber,
3348                73 as i32 as quarterword,
3349                1206305 as i64 as halfword,
3350            )?;
3351        (&mut *(self as *mut PortableTexEngine<'_>))
3352            .zprimitive(
3353                66862 as i64 as strnumber,
3354                74 as i32 as quarterword,
3355                7892325 as i64 as halfword,
3356            )?;
3357        (&mut *(self as *mut PortableTexEngine<'_>))
3358            .zprimitive(
3359                66863 as i64 as strnumber,
3360                74 as i32 as quarterword,
3361                7892326 as i64 as halfword,
3362            )?;
3363        (&mut *(self as *mut PortableTexEngine<'_>))
3364            .zprimitive(
3365                66864 as i64 as strnumber,
3366                74 as i32 as quarterword,
3367                7892327 as i64 as halfword,
3368            )?;
3369        (&mut *(self as *mut PortableTexEngine<'_>))
3370            .zprimitive(
3371                66865 as i64 as strnumber,
3372                74 as i32 as quarterword,
3373                7892328 as i64 as halfword,
3374            )?;
3375        (&mut *(self as *mut PortableTexEngine<'_>))
3376            .zprimitive(
3377                66866 as i64 as strnumber,
3378                74 as i32 as quarterword,
3379                7892329 as i64 as halfword,
3380            )?;
3381        (&mut *(self as *mut PortableTexEngine<'_>))
3382            .zprimitive(
3383                66867 as i64 as strnumber,
3384                74 as i32 as quarterword,
3385                7892330 as i64 as halfword,
3386            )?;
3387        (&mut *(self as *mut PortableTexEngine<'_>))
3388            .zprimitive(
3389                66868 as i64 as strnumber,
3390                74 as i32 as quarterword,
3391                7892331 as i64 as halfword,
3392            )?;
3393        (&mut *(self as *mut PortableTexEngine<'_>))
3394            .zprimitive(
3395                66869 as i64 as strnumber,
3396                74 as i32 as quarterword,
3397                7892332 as i64 as halfword,
3398            )?;
3399        (&mut *(self as *mut PortableTexEngine<'_>))
3400            .zprimitive(
3401                66870 as i64 as strnumber,
3402                74 as i32 as quarterword,
3403                7892333 as i64 as halfword,
3404            )?;
3405        (&mut *(self as *mut PortableTexEngine<'_>))
3406            .zprimitive(
3407                66871 as i64 as strnumber,
3408                74 as i32 as quarterword,
3409                7892335 as i64 as halfword,
3410            )?;
3411        (&mut *(self as *mut PortableTexEngine<'_>))
3412            .zprimitive(66885 as i64 as strnumber, 71 as i32 as quarterword, 20 as i32)?;
3413        (&mut *(self as *mut PortableTexEngine<'_>))
3414            .zprimitive(66886 as i64 as strnumber, 71 as i32 as quarterword, 21 as i32)?;
3415        (&mut *(self as *mut PortableTexEngine<'_>))
3416            .zprimitive(66887 as i64 as strnumber, 71 as i32 as quarterword, 22 as i32)?;
3417        (&mut *(self as *mut PortableTexEngine<'_>))
3418            .zprimitive(66888 as i64 as strnumber, 71 as i32 as quarterword, 23 as i32)?;
3419        (&mut *(self as *mut PortableTexEngine<'_>))
3420            .zprimitive(66889 as i64 as strnumber, 71 as i32 as quarterword, 24 as i32)?;
3421        (&mut *(self as *mut PortableTexEngine<'_>))
3422            .zprimitive(66890 as i64 as strnumber, 71 as i32 as quarterword, 56 as i32)?;
3423        (&mut *(self as *mut PortableTexEngine<'_>))
3424            .zprimitive(66891 as i64 as strnumber, 71 as i32 as quarterword, 57 as i32)?;
3425        (&mut *(self as *mut PortableTexEngine<'_>))
3426            .zprimitive(66892 as i64 as strnumber, 71 as i32 as quarterword, 58 as i32)?;
3427        (&mut *(self as *mut PortableTexEngine<'_>))
3428            .zprimitive(66893 as i64 as strnumber, 71 as i32 as quarterword, 59 as i32)?;
3429        (&mut *(self as *mut PortableTexEngine<'_>))
3430            .zprimitive(66894 as i64 as strnumber, 71 as i32 as quarterword, 60 as i32)?;
3431        (&mut *(self as *mut PortableTexEngine<'_>))
3432            .zprimitive(66895 as i64 as strnumber, 71 as i32 as quarterword, 61 as i32)?;
3433        (&mut *(self as *mut PortableTexEngine<'_>))
3434            .zprimitive(66896 as i64 as strnumber, 71 as i32 as quarterword, 62 as i32)?;
3435        (&mut *(self as *mut PortableTexEngine<'_>))
3436            .zprimitive(66897 as i64 as strnumber, 19 as i32 as quarterword, 4 as i32)?;
3437        (&mut *(self as *mut PortableTexEngine<'_>))
3438            .zprimitive(66899 as i64 as strnumber, 19 as i32 as quarterword, 5 as i32)?;
3439        (&mut *(self as *mut PortableTexEngine<'_>))
3440            .zprimitive(66900 as i64 as strnumber, 112 as i32 as quarterword, 1 as i32)?;
3441        (&mut *(self as *mut PortableTexEngine<'_>))
3442            .zprimitive(66901 as i64 as strnumber, 112 as i32 as quarterword, 5 as i32)?;
3443        (&mut *(self as *mut PortableTexEngine<'_>))
3444            .zprimitive(66902 as i64 as strnumber, 19 as i32 as quarterword, 6 as i32)?;
3445        (&mut *(self as *mut PortableTexEngine<'_>))
3446            .zprimitive(66906 as i64 as strnumber, 83 as i32 as quarterword, 2 as i32)?;
3447        (&mut *(self as *mut PortableTexEngine<'_>))
3448            .zprimitive(66288 as i64 as strnumber, 49 as i32 as quarterword, 1 as i32)?;
3449        (&mut *(self as *mut PortableTexEngine<'_>))
3450            .zprimitive(
3451                66910 as i64 as strnumber,
3452                74 as i32 as quarterword,
3453                7892334 as i64 as halfword,
3454            )?;
3455        (&mut *(self as *mut PortableTexEngine<'_>))
3456            .zprimitive(
3457                66911 as i64 as strnumber,
3458                74 as i32 as quarterword,
3459                7892339 as i64 as halfword,
3460            )?;
3461        (&mut *(self as *mut PortableTexEngine<'_>))
3462            .zprimitive(
3463                66912 as i64 as strnumber,
3464                74 as i32 as quarterword,
3465                7892341 as i64 as halfword,
3466            )?;
3467        (&mut *(self as *mut PortableTexEngine<'_>))
3468            .zprimitive(
3469                66913 as i64 as strnumber,
3470                74 as i32 as quarterword,
3471                7892342 as i64 as halfword,
3472            )?;
3473        (&mut *(self as *mut PortableTexEngine<'_>))
3474            .zprimitive(
3475                66914 as i64 as strnumber,
3476                74 as i32 as quarterword,
3477                7892343 as i64 as halfword,
3478            )?;
3479        (&mut *(self as *mut PortableTexEngine<'_>))
3480            .zprimitive(
3481                66915 as i64 as strnumber,
3482                74 as i32 as quarterword,
3483                7892340 as i64 as halfword,
3484            )?;
3485        (&mut *(self as *mut PortableTexEngine<'_>))
3486            .zprimitive(
3487                66916 as i64 as strnumber,
3488                74 as i32 as quarterword,
3489                7892344 as i64 as halfword,
3490            )?;
3491        (&mut *(self as *mut PortableTexEngine<'_>))
3492            .zprimitive(
3493                66917 as i64 as strnumber,
3494                74 as i32 as quarterword,
3495                7892347 as i64 as halfword,
3496            )?;
3497        (&mut *(self as *mut PortableTexEngine<'_>))
3498            .zprimitive(
3499                66918 as i64 as strnumber,
3500                74 as i32 as quarterword,
3501                7892348 as i64 as halfword,
3502            )?;
3503        (&mut *(self as *mut PortableTexEngine<'_>))
3504            .zprimitive(
3505                66919 as i64 as strnumber,
3506                74 as i32 as quarterword,
3507                7892349 as i64 as halfword,
3508            )?;
3509        (&mut *(self as *mut PortableTexEngine<'_>))
3510            .zprimitive(
3511                66920 as i64 as strnumber,
3512                74 as i32 as quarterword,
3513                7892350 as i64 as halfword,
3514            )?;
3515        (&mut *(self as *mut PortableTexEngine<'_>))
3516            .zprimitive(66761 as i64 as strnumber, 59 as i32 as quarterword, 44 as i32)?;
3517        (&mut *(self as *mut PortableTexEngine<'_>))
3518            .zprimitive(66762 as i64 as strnumber, 59 as i32 as quarterword, 45 as i32)?;
3519        (&mut *(self as *mut PortableTexEngine<'_>))
3520            .zprimitive(66921 as i64 as strnumber, 33 as i32 as quarterword, 6 as i32)?;
3521        (&mut *(self as *mut PortableTexEngine<'_>))
3522            .zprimitive(66922 as i64 as strnumber, 33 as i32 as quarterword, 7 as i32)?;
3523        (&mut *(self as *mut PortableTexEngine<'_>))
3524            .zprimitive(66923 as i64 as strnumber, 33 as i32 as quarterword, 10 as i32)?;
3525        (&mut *(self as *mut PortableTexEngine<'_>))
3526            .zprimitive(66924 as i64 as strnumber, 33 as i32 as quarterword, 11 as i32)?;
3527        (&mut *(self as *mut PortableTexEngine<'_>))
3528            .zprimitive(66933 as i64 as strnumber, 107 as i32 as quarterword, 2 as i32)?;
3529        (&mut *(self as *mut PortableTexEngine<'_>))
3530            .zprimitive(66935 as i64 as strnumber, 98 as i32 as quarterword, 1 as i32)?;
3531        (&mut *(self as *mut PortableTexEngine<'_>))
3532            .zprimitive(66164 as i64 as strnumber, 105 as i32 as quarterword, 1 as i32)?;
3533        (&mut *(self as *mut PortableTexEngine<'_>))
3534            .zprimitive(
3535                66936 as i64 as strnumber,
3536                108 as i32 as quarterword,
3537                17 as i32,
3538            )?;
3539        (&mut *(self as *mut PortableTexEngine<'_>))
3540            .zprimitive(
3541                66937 as i64 as strnumber,
3542                108 as i32 as quarterword,
3543                18 as i32,
3544            )?;
3545        (&mut *(self as *mut PortableTexEngine<'_>))
3546            .zprimitive(
3547                66938 as i64 as strnumber,
3548                108 as i32 as quarterword,
3549                19 as i32,
3550            )?;
3551        (&mut *(self as *mut PortableTexEngine<'_>))
3552            .zprimitive(
3553                66939 as i64 as strnumber,
3554                108 as i32 as quarterword,
3555                20 as i32,
3556            )?;
3557        (&mut *(self as *mut PortableTexEngine<'_>))
3558            .zprimitive(66623 as i64 as strnumber, 95 as i32 as quarterword, 8 as i32)?;
3559        (&mut *(self as *mut PortableTexEngine<'_>))
3560            .zprimitive(66945 as i64 as strnumber, 71 as i32 as quarterword, 67 as i32)?;
3561        (&mut *(self as *mut PortableTexEngine<'_>))
3562            .zprimitive(66946 as i64 as strnumber, 71 as i32 as quarterword, 68 as i32)?;
3563        (&mut *(self as *mut PortableTexEngine<'_>))
3564            .zprimitive(66947 as i64 as strnumber, 71 as i32 as quarterword, 69 as i32)?;
3565        (&mut *(self as *mut PortableTexEngine<'_>))
3566            .zprimitive(66948 as i64 as strnumber, 71 as i32 as quarterword, 70 as i32)?;
3567        (&mut *(self as *mut PortableTexEngine<'_>))
3568            .zprimitive(66952 as i64 as strnumber, 71 as i32 as quarterword, 25 as i32)?;
3569        (&mut *(self as *mut PortableTexEngine<'_>))
3570            .zprimitive(66953 as i64 as strnumber, 71 as i32 as quarterword, 26 as i32)?;
3571        (&mut *(self as *mut PortableTexEngine<'_>))
3572            .zprimitive(66954 as i64 as strnumber, 71 as i32 as quarterword, 63 as i32)?;
3573        (&mut *(self as *mut PortableTexEngine<'_>))
3574            .zprimitive(66955 as i64 as strnumber, 71 as i32 as quarterword, 64 as i32)?;
3575        (&mut *(self as *mut PortableTexEngine<'_>))
3576            .zprimitive(66956 as i64 as strnumber, 71 as i32 as quarterword, 65 as i32)?;
3577        (&mut *(self as *mut PortableTexEngine<'_>))
3578            .zprimitive(66957 as i64 as strnumber, 71 as i32 as quarterword, 66 as i32)?;
3579        (&mut *(self as *mut PortableTexEngine<'_>))
3580            .zprimitive(66958 as i64 as strnumber, 18 as i32 as quarterword, 5 as i32)?;
3581        (&mut *(self as *mut PortableTexEngine<'_>))
3582            .zprimitive(66959 as i64 as strnumber, 113 as i32 as quarterword, 5 as i32)?;
3583        (&mut *(self as *mut PortableTexEngine<'_>))
3584            .zprimitive(66960 as i64 as strnumber, 113 as i32 as quarterword, 6 as i32)?;
3585        (&mut *(self as *mut PortableTexEngine<'_>))
3586            .zprimitive(66961 as i64 as strnumber, 113 as i32 as quarterword, 7 as i32)?;
3587        (&mut *(self as *mut PortableTexEngine<'_>))
3588            .zprimitive(66962 as i64 as strnumber, 113 as i32 as quarterword, 8 as i32)?;
3589        (&mut *(self as *mut PortableTexEngine<'_>))
3590            .zprimitive(66963 as i64 as strnumber, 113 as i32 as quarterword, 9 as i32)?;
3591        (&mut *(self as *mut PortableTexEngine<'_>))
3592            .zprimitive(66968 as i64 as strnumber, 24 as i32 as quarterword, 2 as i32)?;
3593        (&mut *(self as *mut PortableTexEngine<'_>))
3594            .zprimitive(66969 as i64 as strnumber, 24 as i32 as quarterword, 3 as i32)?;
3595        (&mut *(self as *mut PortableTexEngine<'_>))
3596            .zprimitive(
3597                66970 as i64 as strnumber,
3598                85 as i32 as quarterword,
3599                1206563 as i64 as halfword,
3600            )?;
3601        (&mut *(self as *mut PortableTexEngine<'_>))
3602            .zprimitive(
3603                66971 as i64 as strnumber,
3604                85 as i32 as quarterword,
3605                1206564 as i64 as halfword,
3606            )?;
3607        (&mut *(self as *mut PortableTexEngine<'_>))
3608            .zprimitive(
3609                66972 as i64 as strnumber,
3610                85 as i32 as quarterword,
3611                1206565 as i64 as halfword,
3612            )?;
3613        (&mut *(self as *mut PortableTexEngine<'_>))
3614            .zprimitive(
3615                66973 as i64 as strnumber,
3616                85 as i32 as quarterword,
3617                1206566 as i64 as halfword,
3618            )?;
3619        if *self.state.buffer.offset(self.state.curinput.locfield as isize) == 42 as i32
3620        {
3621            self.state.curinput.locfield += 1;
3622        }
3623        self.state.eTeXmode = 1 as eightbits;
3624        self.state.maxregnum = 32767 as i32 as halfword;
3625        self.state.maxreghelpline = 66965 as i64 as strnumber;
3626        Ok(())
3627    }
3628
3629    pub fn begin_primary_input(self: &mut Self, name: &str, bytes: Vec<u8>) -> bool {
3630        // Catch point: `begin_primary_input_raw` now propagates aborts as
3631        // `Err(EngineAbort)` (it threads `beginfilereading`/`firmuptheline`).
3632        // The public API stays a plain `bool`, so consume the `Result` here: an
3633        // abort during input setup means the input could not be started.
3634        self.last_abort_status = None;
3635        self.last_error_message = None;
3636        match unsafe { self.begin_primary_input_raw(name, bytes) } {
3637            Ok(started) => started != 0,
3638            Err(EngineBreak::Abort(EngineAbort { status })) => {
3639                self.last_abort_status = Some(status);
3640                false
3641            }
3642            Err(EngineBreak::Error(error)) => {
3643                self.last_error_message = Some(error.message);
3644                false
3645            }
3646        }
3647    }
3648
3649    pub fn run_main_control(self: &mut Self) -> bool {
3650        self.catch_engine_abort(|engine| unsafe { engine.maincontrol() })
3651    }
3652
3653    pub fn run_format_initialization(self: &mut Self) -> bool {
3654        self.format_initialization = true;
3655        let completed = self.catch_engine_abort(|engine| unsafe { engine.maincontrol() });
3656        self.format_initialization = false;
3657        completed
3658    }
3659
3660    pub fn begin_fragment_capture(self: &mut Self) {
3661        self.fragment_capture_enabled = true;
3662        self.captured_fragment_root = None;
3663    }
3664
3665    pub fn end_fragment_capture(self: &mut Self) {
3666        self.fragment_capture_enabled = false;
3667    }
3668
3669    fn catch_engine_abort<F>(self: &mut Self, run: F) -> bool
3670    where
3671        F: FnOnce(&mut Self) -> EngineFlow<()>,
3672    {
3673        // Non-unwinding abort boundary. The driver closure threads any fatal
3674        // `jump_out`/`fatal_error`/`overflow` back as `Err(EngineAbort)` via the
3675        // `?` operator instead of `panic_any`, so the engine runs under
3676        // `panic=abort`. Status mapping is identical to the old `catch_unwind`
3677        // path: status 0 (normal `\end`/dump) => "completed"; nonzero => abort.
3678        self.last_abort_status = None;
3679        self.last_error_message = None;
3680        match run(self) {
3681            Ok(()) => self.last_abort_status.is_none(),
3682            Err(EngineBreak::Abort(EngineAbort { status: 0 })) => {
3683                self.last_abort_status = None;
3684                true
3685            }
3686            Err(EngineBreak::Abort(EngineAbort { status })) => {
3687                self.last_abort_status = Some(status);
3688                false
3689            }
3690            // A surfaced TeX error (or sandbox violation): record its message so
3691            // the host can report it, and treat the run as not completed.
3692            Err(EngineBreak::Error(error)) => {
3693                self.last_error_message = Some(error.message);
3694                false
3695            }
3696        }
3697    }
3698
3699    pub fn snapshot_format(&self) -> PortableFormatImage {
3700        PortableFormatImage::from_engine_state(self.state.as_ref())
3701    }
3702
3703    /// Consume this engine and seal its state *in place* as a format snapshot,
3704    /// moving the `Box<PortableTexState>` instead of deep-cloning it the way
3705    /// [`Self::snapshot_format`] does. Building a format cache normally holds the
3706    /// freshly-initialized engine (~hundreds of MB of `mem`/`eqtb`/`hash`) and a
3707    /// full clone of it at the same time — a transient ~2x spike. When the caller
3708    /// owns the engine and discards it right after snapshotting (the
3709    /// `GeneratedFormatCache::initialized` / preload paths), moving the state
3710    /// avoids the clone and halves that peak.
3711    #[must_use]
3712    pub fn into_format(self) -> PortableFormatImage {
3713        // NOTE: do NOT `finalize_trie()` here. `into_format` is also used for the
3714        // base format that further packages (`\patterns`) are loaded on top of;
3715        // packing the trie is a one-way door ("! Too late for \patterns"). Callers
3716        // that have produced the *final* format call `finalize_trie()` explicitly
3717        // first (see `generated_format_for`, wasm `build_format`).
3718        let mut state = self.state;
3719        state.seal_as_format_snapshot();
3720        PortableFormatImage::from_sealed_state(state)
3721    }
3722
3723    /// Pack the hyphenation trie now, so its construction scratch can be freed
3724    /// when the format is sealed. This engine packs the trie lazily on the first
3725    /// runtime hyphenation (`inittrie` is a no-op while `format_initialization`);
3726    /// doing it here — once the *final* format is built, exactly like TeX's
3727    /// `\dump` — yields an identical packed trie and lets `seal_as_format_snapshot`
3728    /// drop the ~24 MB of builder scratch arrays. Only call this when no further
3729    /// `\patterns` will be loaded.
3730    pub fn finalize_trie(self: &mut Self) {
3731        // Only when the trie is unpacked AND its scratch is still present.
3732        if self.state.trienotready != 0 && !self.state.triehash.is_null() {
3733            let saved = self.format_initialization;
3734            self.format_initialization = false;
3735            let _ = unsafe { self.inittrie() };
3736            self.format_initialization = saved;
3737        }
3738    }
3739
3740    pub fn resource_request_count(&self) -> usize {
3741        self.resource_requests
3742    }
3743
3744    pub fn resource_request_records(&self) -> &[PortableResourceRequestRecord] {
3745        self.resource_request_records.as_slice()
3746    }
3747
3748    pub fn transcript_bytes(&self) -> &[u8] {
3749        self.transcript_bytes.as_slice()
3750    }
3751
3752    /// The interned span keyed to the primary input's source name, if any —
3753    /// the first recorded span whose name is the primary input file.
3754    pub fn primary_input_source_span(&self) -> Option<PortableSourceSpan> {
3755        if !self.state.source_tracking {
3756            return None;
3757        }
3758        let primary = self.state.src_primary_name;
3759        let raw = self
3760            .state
3761            .src_spans
3762            .iter()
3763            .find(|raw| raw.name == primary)?;
3764        let name = unsafe { self.pool_string(raw.name) }?;
3765        Some(PortableSourceSpan {
3766            name,
3767            start: raw.start,
3768            end: raw.end,
3769            role: raw.role,
3770        })
3771    }
3772
3773    /// Stamped node→span pairs. The `node_src` shadow is paged-sparse and not
3774    /// cheaply enumerable by node address, so callers that need per-node spans
3775    /// use [`Self::resolve_node_src`] / `snapshot_node` on the live node graph;
3776    /// this restored accessor returns an empty slice rather than scanning `mem`.
3777    pub fn node_source_spans(&self) -> &[PortableNodeSourceSpan] {
3778        &[]
3779    }
3780
3781    pub fn stripped_page_build_count(&self) -> usize {
3782        self.stripped_page_builds
3783    }
3784
3785    pub fn stripped_shipout_count(&self) -> usize {
3786        self.stripped_shipouts
3787    }
3788
3789    pub fn stripped_special_output_count(&self) -> usize {
3790        self.stripped_special_outputs
3791    }
3792
3793    pub fn stripped_picture_load_count(&self) -> usize {
3794        self.stripped_picture_loads
3795    }
3796
3797    pub fn stripped_source_special_count(&self) -> usize {
3798        self.stripped_source_specials
3799    }
3800
3801    pub fn stripped_write_whatsit_diagnostic_count(&self) -> usize {
3802        self.stripped_write_whatsit_diagnostics
3803    }
3804
3805    pub fn stripped_pdf_extension_count(&self) -> usize {
3806        self.stripped_pdf_extensions
3807    }
3808
3809    pub fn stripped_page_top_prune_count(&self) -> usize {
3810        self.stripped_page_top_prunes
3811    }
3812
3813    pub fn last_stripped_shipout_box(&self) -> Option<PortableNodeHandle> {
3814        self.last_stripped_shipout_box
3815    }
3816
3817    pub fn captured_fragment_root(&self) -> Option<PortableNodeHandle> {
3818        self.captured_fragment_root
3819    }
3820
3821    /// At-size (scaled points) a font was loaded at, by internal font number.
3822    /// Used by the IR builder to carry the real glyph-run font size.
3823    pub fn font_at_size(&self, font: integer) -> integer {
3824        if font < 0 {
3825            return 0;
3826        }
3827        self.state
3828            .fontsize_storage
3829            .get(font as usize)
3830            .copied()
3831            .unwrap_or(0)
3832    }
3833
3834    /// The interned `\font` name for a font number. For native fonts this is the
3835    /// XeTeX spec the font was loaded with (e.g.
3836    /// `[latinmodern-math.otf]:script=math;ssty=1`); for TFM fonts it is the
3837    /// `.tfm` name. Lets the IR carry a real font identity so a renderer can
3838    /// resolve per-run glyph outlines to the originating font file.
3839    pub fn font_name(&self, font: integer) -> Option<String> {
3840        if font < 0 {
3841            return None;
3842        }
3843        let name = self.state.fontname_storage.get(font as usize).copied()?;
3844        // SAFETY: decodes the engine string pool, identical to every other
3845        // `pool_string` read elsewhere in the boundary layer.
3846        unsafe { self.pool_string(name) }
3847    }
3848
3849    /// The `\font` spec a native font number was loaded with, recovered from the
3850    /// font platform (`[file]:features`). For native fonts this is the reliable
3851    /// identity (TFM `\fontname` is empty for them). Read-only.
3852    pub fn native_font_spec(&self, font: integer) -> Option<String> {
3853        let handle = Self::font_handle_for_number(self, font)?;
3854        self.fonts.font_spec(handle)
3855    }
3856
3857    /// Snapshot the native-font table `(handle, spec, size)` from the attached
3858    /// font platform, for packaging alongside a serialized format image.
3859    pub fn native_font_table(&self) -> Vec<(PortableFontHandle, String, i32)> {
3860        self.fonts.font_table()
3861    }
3862
3863    /// Re-bind native fonts on a cold-loaded format image: rebuilds the attached
3864    /// platform's handle→font map from a [`Self::native_font_table`] snapshot so
3865    /// the `fontlayoutengine` handles preserved in the image resolve again.
3866    /// Returns `false` if any font failed to reload.
3867    pub fn restore_native_font_table(
3868        self: &mut Self,
3869        table: &[(PortableFontHandle, String, i32)],
3870    ) -> bool {
3871        self.fonts.restore_font_table(table)
3872    }
3873
3874    pub fn last_abort_status(&self) -> Option<integer> {
3875        self.last_abort_status
3876    }
3877
3878    /// The message from the most recent surfaced [`EngineError`], if the last
3879    /// run failed with a TeX error (rather than a fatal abort). `None` after a
3880    /// clean run or a bare abort.
3881    pub fn last_error_message(&self) -> Option<&str> {
3882        self.last_error_message.as_deref()
3883    }
3884
3885    /// Enable/disable the fragment sandbox (see [`PortableTexEngine::sandbox`]),
3886    /// resetting the per-run bookkeeping so each render starts clean. Uses the
3887    /// `self: &mut Self` receiver form the patcher's passes expect for prelude
3888    /// methods (the `&mut self` shorthand gets its receiver stripped).
3889    pub fn set_sandbox(self: &mut Self, on: bool) {
3890        self.sandbox = on;
3891        self.sandbox_math_depth = 0;
3892        self.sandbox_math_opened = false;
3893        self.sandbox_ops = 0;
3894    }
3895
3896    /// Extract the message from the most recent `! ...` line in the transcript --
3897    /// what `error()` printed for the diagnostic now being surfaced -- trimming
3898    /// the trailing period `error()` appends. Diagnostics are printed with
3899    /// `selector = term_and_log`, so each byte reaches the transcript twice (the
3900    /// headless terminal and the log both feed it); [`collapse_doubled_line`]
3901    /// undoes that. Returns a generic label if no well-formed error line exists.
3902    pub(crate) fn capture_last_error_message(&self) -> String {
3903        let transcript = core::str::from_utf8(&self.transcript_bytes).unwrap_or("");
3904        for raw in transcript.lines().rev() {
3905            let collapsed = collapse_doubled_line(raw.trim());
3906            let line = collapsed.as_deref().unwrap_or(raw).trim();
3907            if let Some(rest) = line.strip_prefix("! ") {
3908                return rest.trim_end_matches('.').trim().into();
3909            }
3910        }
3911        "TeX error".into()
3912    }
3913
3914    pub fn snapshot_node(&self, handle: PortableNodeHandle) -> Option<PortableNodeSnapshot> {
3915        let node = handle.0 as halfword;
3916        let word = self.node_word(node, 0)?;
3917        let raw_kind = unsafe { word.hh.u.B0 as i32 };
3918        let subtype = unsafe { word.hh.u.B1 as i32 };
3919        let kind = self.node_kind(raw_kind, node, subtype);
3920        let link = self.node_link(node);
3921        let is_character = node >= self.state.himemmin;
3922        let native_word4 = if matches!(kind, PortableNodeKind::NativeWord | PortableNodeKind::NativeGlyph) {
3923            self.node_word(node, 4)
3924        } else {
3925            None
3926        };
3927        let font = if is_character {
3928            raw_kind
3929        } else {
3930            native_word4.map_or(0, |word| unsafe { word.v.QQQQ.u.B1 as i32 })
3931        };
3932        let character = if is_character {
3933            subtype
3934        } else {
3935            native_word4.map_or(0, |word| unsafe { word.v.QQQQ.u.B2 as i32 })
3936        };
3937        let (width, height, depth, shift, list) = match raw_kind {
3938            _ if is_character => (
3939                self.character_width(font, character).unwrap_or_default(),
3940                0,
3941                0,
3942                0,
3943                None,
3944            ),
3945            0 | 1 | 13 => (
3946                self.node_scaled(node, 1).unwrap_or_default(),
3947                self.node_scaled(node, 3).unwrap_or_default(),
3948                self.node_scaled(node, 2).unwrap_or_default(),
3949                self.node_scaled(node, 4).unwrap_or_default(),
3950                self.node_field_link(node, 5),
3951            ),
3952            2 => (
3953                self.node_scaled(node, 1).unwrap_or_default(),
3954                self.node_scaled(node, 3).unwrap_or_default(),
3955                self.node_scaled(node, 2).unwrap_or_default(),
3956                0,
3957                None,
3958            ),
3959            10 => (
3960                self.glue_amount(node).unwrap_or_default(),
3961                0,
3962                0,
3963                0,
3964                None,
3965            ),
3966            11 => (
3967                self.node_scaled(node, 1).unwrap_or_default(),
3968                0,
3969                0,
3970                0,
3971                None,
3972            ),
3973            8 if matches!(kind, PortableNodeKind::NativeWord | PortableNodeKind::NativeGlyph) => (
3974                self.node_scaled(node, 1).unwrap_or_default(),
3975                self.node_scaled(node, 3).unwrap_or_default(),
3976                self.node_scaled(node, 2).unwrap_or_default(),
3977                0,
3978                None,
3979            ),
3980            _ => (0, 0, 0, 0, None),
3981        };
3982        let native_glyphs = if matches!(kind, PortableNodeKind::NativeWord | PortableNodeKind::NativeGlyph) {
3983            self.native_glyph_infos
3984                .get(&node)
3985                .map(|info| info.glyphs.clone())
3986                .unwrap_or_default()
3987        } else {
3988            Vec::new()
3989        };
3990
3991        // Box glue-set state (`hlist_out`/`vlist_out` read these to turn each
3992        // glue node's natural width into its SET width): `glue_set` is the float
3993        // ratio from `hpack`/`vpack`, `glue_sign` (0 normal / 1 stretching /
3994        // 2 shrinking) and `glue_order` (0..3) select which order participates.
3995        let (glue_set, glue_sign, glue_order) = match raw_kind {
3996            0 | 1 | 13 => (
3997                self.node_word(node, 6).map_or(0.0, |word| unsafe { word.gr }),
3998                self.node_word(node, 5)
3999                    .map_or(0, |word| unsafe { word.hh.u.B0 as i32 }),
4000                self.node_word(node, 5)
4001                    .map_or(0, |word| unsafe { word.hh.u.B1 as i32 }),
4002            ),
4003            _ => (0.0, 0, 0),
4004        };
4005        // Glue node's spec stretch/shrink and their orders (raw_kind 10).
4006        let (glue_stretch, glue_shrink, glue_stretch_order, glue_shrink_order) = if raw_kind == 10 {
4007            match self.node_word(node, 1).map(|word| unsafe { word.hh.v.LH }) {
4008                Some(spec) => (
4009                    self.node_scaled(spec, 2).unwrap_or_default(),
4010                    self.node_scaled(spec, 3).unwrap_or_default(),
4011                    self.node_word(spec, 0)
4012                        .map_or(0, |word| unsafe { word.hh.u.B0 as i32 }),
4013                    self.node_word(spec, 0)
4014                        .map_or(0, |word| unsafe { word.hh.u.B1 as i32 }),
4015                ),
4016                None => (0, 0, 0, 0),
4017            }
4018        } else {
4019            (0, 0, 0, 0)
4020        };
4021
4022        Some(PortableNodeSnapshot {
4023            handle,
4024            kind,
4025            subtype,
4026            source: self.resolve_node_src(node),
4027            link,
4028            font,
4029            character,
4030            width,
4031            height,
4032            depth,
4033            shift,
4034            list,
4035            native_glyphs,
4036            glue_set,
4037            glue_sign,
4038            glue_order,
4039            glue_stretch,
4040            glue_shrink,
4041            glue_stretch_order,
4042            glue_shrink_order,
4043        })
4044    }
4045
4046    fn node_kind(&self, raw_kind: i32, node: halfword, subtype: i32) -> PortableNodeKind {
4047        if node >= self.state.himemmin {
4048            return PortableNodeKind::Character;
4049        }
4050
4051        match raw_kind {
4052            0 => PortableNodeKind::HorizontalBox,
4053            1 => PortableNodeKind::VerticalBox,
4054            2 => PortableNodeKind::Rule,
4055            3 => PortableNodeKind::Insertion,
4056            4 => PortableNodeKind::Mark,
4057            5 => PortableNodeKind::Adjustment,
4058            6 => PortableNodeKind::Ligature,
4059            7 => PortableNodeKind::Discretionary,
4060            8 if matches!(subtype, 40 | 41) => PortableNodeKind::NativeWord,
4061            8 if subtype == 42 => PortableNodeKind::NativeGlyph,
4062            8 if matches!(subtype, 0 | 1 | 2 | 3) => PortableNodeKind::OutputWhatsit,
4063            8 => PortableNodeKind::Whatsit,
4064            9 => PortableNodeKind::Math,
4065            10 => PortableNodeKind::Glue,
4066            11 => PortableNodeKind::Kern,
4067            12 => PortableNodeKind::Penalty,
4068            13 => PortableNodeKind::UnsetBox,
4069            16 => PortableNodeKind::Noad,
4070            14 => PortableNodeKind::Style,
4071            15 => PortableNodeKind::Choice,
4072            other => PortableNodeKind::Unknown(other),
4073        }
4074    }
4075
4076    pub(crate) fn copy_native_glyph_info(
4077        this: &mut Self,
4078        src: halfword,
4079        dest: halfword,
4080    ) -> quarterword {
4081        if let Some(info) = this.native_glyph_infos.get(&src).cloned() {
4082            let glyph_count = info.glyphs.len().min(i32::MAX as usize) as quarterword;
4083            this.native_glyph_infos.insert(dest, info);
4084            glyph_count
4085        } else {
4086            this.native_glyph_infos.remove(&dest);
4087            0
4088        }
4089    }
4090
4091    fn font_handle_for_number(this: &Self, font: integer) -> Option<FontHandle> {
4092        if font < 0 || this.state.fontlayoutengine.is_null() {
4093            return None;
4094        }
4095        let handle = unsafe { *this.state.fontlayoutengine.offset(font as isize) as FontHandle };
4096        (handle != 0).then_some(handle)
4097    }
4098
4099    unsafe fn node_index_for_pointer(
4100        engine: &PortableTexEngine<'_>,
4101        node: voidpointer,
4102    ) -> Option<halfword> {
4103        if node.is_null() || engine.state.zmem.is_null() {
4104            return None;
4105        }
4106        let base = engine.state.zmem as isize;
4107        let address = node as isize;
4108        let word_size = core::mem::size_of::<memoryword>() as isize;
4109        if word_size == 0 || address < base {
4110            return None;
4111        }
4112        let bytes = address - base;
4113        if bytes % word_size != 0 {
4114            return None;
4115        }
4116        let index = (bytes / word_size) as halfword;
4117        if index < engine.state.memmin || index > engine.state.memmax {
4118            None
4119        } else {
4120            Some(index)
4121        }
4122    }
4123
4124    unsafe fn native_node_font(mem: *mut memoryword, node: halfword) -> integer {
4125        (*mem.offset((node + 4) as isize)).v.QQQQ.u.B1 as integer
4126    }
4127
4128    unsafe fn native_node_text<'a>(mem: *mut memoryword, node: halfword) -> &'a [u16] {
4129        let len = (*mem.offset((node + 4) as isize)).v.QQQQ.u.B2.max(0) as usize;
4130        if len == 0 {
4131            return &[];
4132        }
4133        core::slice::from_raw_parts(
4134            mem.offset((node + native_node_size) as isize) as *const memoryword as *const u16,
4135            len,
4136        )
4137    }
4138
4139    unsafe fn write_native_node_metrics(
4140        mem: *mut memoryword,
4141        node: halfword,
4142        width: i32,
4143        height: i32,
4144        depth: i32,
4145    ) {
4146        (*mem.offset((node + 1) as isize)).u.CINT = width;
4147        (*mem.offset((node + 2) as isize)).u.CINT = depth;
4148        (*mem.offset((node + 3) as isize)).u.CINT = height;
4149    }
4150
4151    fn node_word(&self, node: halfword, offset: halfword) -> Option<memoryword> {
4152        let index = node.checked_add(offset)?;
4153        if node as i64 == -(268435455 as i64) {
4154            return None;
4155        }
4156        if self.state.zmem.is_null() || index < self.state.memmin || index > self.state.memmax {
4157            return None;
4158        }
4159        unsafe { Some(*self.state.zmem.offset(index as isize)) }
4160    }
4161
4162    fn node_link(&self, node: halfword) -> Option<PortableNodeHandle> {
4163        let word = self.node_word(node, 0)?;
4164        let link = unsafe { word.hh.v.RH };
4165        Self::node_handle_from_raw(link)
4166    }
4167
4168    fn node_field_link(&self, node: halfword, offset: halfword) -> Option<PortableNodeHandle> {
4169        let word = self.node_word(node, offset)?;
4170        let link = unsafe { word.hh.v.RH };
4171        Self::node_handle_from_raw(link)
4172    }
4173
4174    fn node_scaled(&self, node: halfword, offset: halfword) -> Option<i32> {
4175        let word = self.node_word(node, offset)?;
4176        Some(unsafe { word.u.CINT })
4177    }
4178
4179    fn glue_amount(&self, node: halfword) -> Option<i32> {
4180        let word = self.node_word(node, 1)?;
4181        let glue_spec = unsafe { word.hh.v.LH };
4182        self.node_scaled(glue_spec, 1)
4183    }
4184
4185    fn character_width(&self, font: i32, character: i32) -> Option<i32> {
4186        if font < 0
4187            || character < 0
4188            || self.state.fontinfo.is_null()
4189            || self.state.charbase.is_null()
4190            || self.state.widthbase.is_null()
4191        {
4192            return None;
4193        }
4194        let char_info_index = unsafe {
4195            *self.state.charbase.offset(font as isize) + character
4196        };
4197        let char_info = unsafe {
4198            (*self.state.fontinfo.offset(char_info_index as isize)).v.QQQQ
4199        };
4200        let width_index = unsafe {
4201            *self.state.widthbase.offset(font as isize) as i32 + char_info.u.B0 as i32
4202        };
4203        Some(unsafe { (*self.state.fontinfo.offset(width_index as isize)).u.CINT })
4204    }
4205
4206    fn node_handle_from_raw(raw: halfword) -> Option<PortableNodeHandle> {
4207        if raw as i64 == -(268435455 as i64) {
4208            None
4209        } else {
4210            Some(PortableNodeHandle(raw))
4211        }
4212    }
4213
4214
4215    pub(crate) fn is_xetex(&self) -> bool {
4216        self.profile.xetex
4217    }
4218
4219    pub(crate) fn supports_etex(&self) -> bool {
4220        self.profile.etex
4221    }
4222
4223    pub(crate) fn supports_unicode_scalars(&self) -> bool {
4224        self.profile.unicode_scalars
4225    }
4226
4227    pub(crate) fn supports_unicode_math(&self) -> bool {
4228        self.profile.unicode_math
4229    }
4230
4231    pub(crate) fn supports_native_fonts(&self) -> bool {
4232        self.profile.native_fonts
4233    }
4234
4235    pub(crate) unsafe fn getinputnormalizationstate(&self) -> integer {
4236        if !self.is_xetex() {
4237            return 0;
4238        }
4239        let eqtb = self.state.zeqtb.as_mut_ptr();
4240        if eqtb.is_null() {
4241            return 0;
4242        }
4243        (*eqtb.offset(7892344 as i64 as isize)).u.CINT
4244    }
4245
4246    pub(crate) unsafe fn gettracingfontsstate(&self) -> integer {
4247        if !self.is_xetex() {
4248            return 0;
4249        }
4250        let eqtb = self.state.zeqtb.as_mut_ptr();
4251        if eqtb.is_null() {
4252            return 0;
4253        }
4254        (*eqtb.offset(7892347 as i64 as isize)).u.CINT
4255    }
4256
4257    unsafe fn current_resource_name(engine: *mut PortableTexEngine<'_>) -> Option<String> {
4258        let engine = engine.as_ref()?;
4259        if engine.state.nameoffile.is_null() || engine.state.namelength <= 0 {
4260            return None;
4261        }
4262
4263        let mut bytes = Vec::with_capacity(engine.state.namelength as usize);
4264        for index in 1..=engine.state.namelength {
4265            let value = *engine.state.nameoffile.offset(index as isize);
4266            if value <= 0 {
4267                continue;
4268            }
4269            bytes.push(value as u8);
4270        }
4271        Some(String::from_utf8_lossy(bytes.as_slice()).into_owned())
4272    }
4273
4274    /// Resolve the `\XeTeXinputencoding "<name>"` encoding just scanned into
4275    /// `nameoffile`, returning the XeTeX mode (AUTO=0, UTF8=1, UTF16BE=2,
4276    /// UTF16LE=3, RAW=4) and zeroing `*info`. Faithful port of XeTeX's
4277    /// `getencodingmodeandinfo` (`XeTeX_ext.c`), minus ICU: unknown names degrade
4278    /// to RAW (read as raw bytes) rather than opening an ICU converter.
4279    pub(crate) unsafe fn get_encoding_mode_and_info(
4280        engine: *mut PortableTexEngine<'_>,
4281        info: *mut integer,
4282    ) -> integer {
4283        if !info.is_null() {
4284            *info = 0;
4285        }
4286        let name = Self::current_resource_name(engine).unwrap_or_default();
4287        let lowered = name.trim().to_ascii_lowercase();
4288        match lowered.as_str() {
4289            "auto" => 0,                       // AUTO
4290            "utf8" | "utf-8" => 1,             // UTF8
4291            // `utf16` is host-endian; treat as big-endian (xetex default name).
4292            "utf16" | "utf-16" | "utf16be" | "utf-16be" => 2, // UTF16BE
4293            "utf16le" | "utf-16le" => 3,       // UTF16LE
4294            "bytes" => 4,                      // RAW
4295            // Unknown / ICU encoding names: read as raw bytes (no ICU support).
4296            _ => 4,
4297        }
4298    }
4299
4300    unsafe fn mode_string(mode: const_string) -> String {
4301        if mode.is_null() {
4302            return String::new();
4303        }
4304
4305        let mut bytes = Vec::new();
4306        let mut cursor = mode;
4307        while *cursor != 0 {
4308            bytes.push(*cursor as u8);
4309            cursor = cursor.add(1);
4310        }
4311        String::from_utf8_lossy(bytes.as_slice()).into_owned()
4312    }
4313
4314    unsafe fn pool_string(&self, string: strnumber) -> Option<String> {
4315        if string < 0 || self.state.strstart.is_null() || self.state.strpool.is_null() {
4316            return None;
4317        }
4318        let index = Self::pool_string_index(string)?;
4319        let start = *self.state.strstart.offset(index);
4320        let end = *self.state.strstart.offset(index + 1);
4321        if start < 0 || end < start {
4322            return None;
4323        }
4324
4325        let len = usize::try_from(end - start).ok()?;
4326        let mut units = Vec::with_capacity(len);
4327        for offset in 0..len {
4328            units.push(*self.state.strpool.offset((start as usize + offset) as isize));
4329        }
4330        Some(
4331            char::decode_utf16(units)
4332                .map(|codepoint| codepoint.unwrap_or(char::REPLACEMENT_CHARACTER))
4333                .collect(),
4334        )
4335    }
4336
4337    pub(crate) fn pool_string_index(string: strnumber) -> Option<isize> {
4338        if string < 0 {
4339            return None;
4340        }
4341        let index = if string >= 65536 { string - 65536 } else { string };
4342        isize::try_from(index).ok()
4343    }
4344
4345    fn resource_kind(name: &str, format: integer) -> ResourceKind {
4346        match format {
4347            resource_format_tex_input | 0 => Self::tex_resource_kind(name),
4348            resource_format_tfm | resource_format_font => ResourceKind::Font,
4349            resource_format_encoding => ResourceKind::Encoding,
4350            resource_format_font_map => ResourceKind::Map,
4351            resource_format_config => ResourceKind::Config,
4352            resource_format_format_image => ResourceKind::FormatImage,
4353            other => ResourceKind::Other(other),
4354        }
4355    }
4356
4357    fn tex_resource_kind(name: &str) -> ResourceKind {
4358        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4359        let name = name.to_ascii_lowercase();
4360        if name.ends_with(".sty") {
4361            return ResourceKind::Package;
4362        }
4363        if name.ends_with(".cls") {
4364            return ResourceKind::Class;
4365        }
4366        if name.ends_with(".fd") {
4367            return ResourceKind::FontDefinition;
4368        }
4369        if name.ends_with(".clo")
4370            || name.ends_with(".def")
4371            || name.ends_with(".ldf")
4372            || name.ends_with(".cfg")
4373        {
4374            return ResourceKind::PackageSupport;
4375        }
4376        ResourceKind::TexInput
4377    }
4378
4379    fn resource_kind_for_open(
4380        engine: &PortableTexEngine<'_>,
4381        name: &str,
4382        format: integer,
4383    ) -> ResourceKind {
4384        let kind = Self::resource_kind(name, format);
4385        if kind == ResourceKind::TexInput
4386            && Self::active_package_owner(engine).is_some()
4387            && Self::looks_like_package_asset(name)
4388        {
4389            ResourceKind::Asset
4390        } else {
4391            kind
4392        }
4393    }
4394
4395    fn resource_package_owner(
4396        engine: &PortableTexEngine<'_>,
4397        name: &str,
4398        kind: ResourceKind,
4399    ) -> Option<String> {
4400        match kind {
4401            ResourceKind::Package | ResourceKind::Class => Self::resource_stem(name),
4402            ResourceKind::PackageSupport | ResourceKind::FontDefinition | ResourceKind::Asset => {
4403                Self::active_package_owner(engine)
4404            }
4405            _ => None,
4406        }
4407    }
4408
4409    fn active_package_owner(engine: &PortableTexEngine<'_>) -> Option<String> {
4410        engine.current_input_package_owner.clone()
4411    }
4412
4413    fn looks_like_package_asset(name: &str) -> bool {
4414        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4415        let name = name.to_ascii_lowercase();
4416        !(name.ends_with(".tex")
4417            || name.ends_with(".ltx")
4418            || name.ends_with(".sty")
4419            || name.ends_with(".cls")
4420            || name.ends_with(".fd")
4421            || name.ends_with(".clo")
4422            || name.ends_with(".def")
4423            || name.ends_with(".ldf")
4424            || name.ends_with(".cfg"))
4425    }
4426
4427    fn resource_stem(name: &str) -> Option<String> {
4428        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4429        let stem = name.rsplit_once('.').map_or(name, |(stem, _)| stem);
4430        if stem.is_empty() {
4431            None
4432        } else {
4433            Some(stem.to_string())
4434        }
4435    }
4436
4437    fn source_index(value: usize) -> u32 {
4438        value.min(u32::MAX as usize) as u32
4439    }
4440
4441    fn virtual_file_key(name: &str) -> String {
4442        let mut name = name;
4443        while let Some(stripped) = name.strip_prefix("./") {
4444            name = stripped;
4445        }
4446        name.to_string()
4447    }
4448
4449    fn normalized_runtime_resource_name(mut name: &str) -> &str {
4450        while let Some(stripped) = name.strip_prefix("./") {
4451            name = stripped;
4452        }
4453        name
4454    }
4455
4456    // Hand-edited bridge: returns `EngineFlow<Option<strnumber>>` so the final
4457    // `makestring` (abort-reachable on pool overflow) propagates via `?`, while
4458    // the internal `Option` early-returns (a `None` means "did not intern", not
4459    // an abort) stay explicit `Ok(None)`. The `?`-on-`Option` shorthand used
4460    // before would not survive the return-type change, so this one is NOT
4461    // auto-rewritten by the flow pass.
4462    unsafe fn intern_static_pool_string(
4463        engine: &mut PortableTexEngine<'_>,
4464        text: &str,
4465    ) -> EngineFlow<Option<strnumber>> {
4466        if engine.state.strpool.is_null() || engine.state.strstart.is_null() {
4467            return Ok(None);
4468        }
4469
4470        let Ok(needed) = integer::try_from(text.encode_utf16().count()) else {
4471            return Ok(None);
4472        };
4473        let Some(next_pool) = engine.state.poolptr.checked_add(needed) else {
4474            return Ok(None);
4475        };
4476        if next_pool > engine.state.poolsize {
4477            return Ok(None);
4478        }
4479
4480        for unit in text.encode_utf16() {
4481            *engine.state.strpool.offset(engine.state.poolptr as isize) = unit as packedUTF16code;
4482            engine.state.poolptr += 1;
4483        }
4484
4485        Ok(Some(engine.makestring()?))
4486    }
4487
4488    unsafe fn append_text_to_pool(engine: &mut PortableTexEngine<'_>, text: &str) -> boolean {
4489        if engine.state.strpool.is_null() {
4490            return false_0;
4491        }
4492        let needed = match integer::try_from(text.encode_utf16().count()) {
4493            Ok(needed) => needed,
4494            Err(_) => return false_0,
4495        };
4496        let Some(next_pool) = engine.state.poolptr.checked_add(needed) else {
4497            return false_0;
4498        };
4499        if next_pool > engine.state.poolsize {
4500            return false_0;
4501        }
4502        for unit in text.encode_utf16() {
4503            *engine.state.strpool.offset(engine.state.poolptr as isize) = unit as packedUTF16code;
4504            engine.state.poolptr += 1;
4505        }
4506        true_0
4507    }
4508
4509    unsafe fn resource_bytes_for_name(
4510        engine: &mut PortableTexEngine<'_>,
4511        name: &str,
4512    ) -> Option<Vec<u8>> {
4513        let name = Self::normalized_runtime_resource_name(name);
4514        let kind = Self::resource_kind_for_open(engine, name, resource_format_tex_input);
4515        let package = Self::resource_package_owner(engine, name, kind);
4516        let request = ResourceRequest {
4517            name,
4518            kind,
4519            package: package.as_deref(),
4520            format: resource_format_tex_input,
4521            mode: "rb",
4522            source: None,
4523        };
4524        let virtual_key = Self::virtual_file_key(name);
4525        if let Some(bytes) = engine.virtual_files.get(&virtual_key) {
4526            Some(bytes.clone())
4527        } else {
4528            engine.resources.read(request)
4529        }
4530    }
4531
4532    pub(crate) unsafe fn boundary_get_file_size(
4533        engine: *mut PortableTexEngine<'_>,
4534        string: integer,
4535    ) {
4536        let Some(engine) = engine.as_mut() else {
4537            return;
4538        };
4539        let Some(name) = engine.pool_string(string as strnumber) else {
4540            return;
4541        };
4542        // expl3's file layer (`\file_full_name:n`) decides a file EXISTS solely
4543        // by whether `\filesize` expands to a non-empty value. We have no host
4544        // filesystem (wasm target): answer from the ResourceProvider. When the
4545        // provider serves the resource, report its real byte length; otherwise
4546        // (the in-memory job fragment we feed, which has no backing file, or a
4547        // probe for an asset we don't carry) report a nonzero placeholder so the
4548        // existence check still passes. Returning nothing here makes expl3
4549        // conclude the file is missing, which silently aborts data-file loads
4550        // like unicode-math's `\file_get {unicode-math-table.tex}`.
4551        const PLACEHOLDER_FILE_SIZE: usize = 4096;
4552        let size = Self::resource_bytes_for_name(engine, name.as_str())
4553            .map_or(PLACEHOLDER_FILE_SIZE, |bytes| bytes.len());
4554        Self::append_text_to_pool(engine, size.to_string().as_str());
4555    }
4556
4557    pub(crate) unsafe fn load_pool_strings(
4558        engine: &mut PortableTexEngine<'_>,
4559        spare_size: integer,
4560    ) -> EngineFlow<integer> {
4561        if engine.state.strpool.is_null() || engine.state.strstart.is_null()
4562            || spare_size <= 0
4563        {
4564            return Ok(0);
4565        }
4566        let mut used = 0_i32;
4567        let mut last = 0_i32;
4568        for line in include_str!("../pool/xetex.pool").lines() {
4569            if line.starts_with('*') {
4570                break;
4571            }
4572            let bytes = line.as_bytes();
4573            let text = if bytes.len() >= 2 && bytes[0].is_ascii_digit()
4574                && bytes[1].is_ascii_digit()
4575            {
4576                &line[2..]
4577            } else {
4578                line
4579            };
4580            let units = text.encode_utf16().count().min(i32::MAX as usize) as integer;
4581            used = used.saturating_add(units);
4582            if used >= spare_size
4583                || engine.state.poolptr.saturating_add(units) > engine.state.poolsize
4584            {
4585                return Ok(0);
4586            }
4587            for unit in text.encode_utf16() {
4588                *engine.state.strpool.offset(engine.state.poolptr as isize) = unit
4589                    as packedUTF16code;
4590                engine.state.poolptr += 1;
4591            }
4592            last = engine.makestring()?;
4593        }
4594        Ok(last)
4595    }
4596
4597    pub(crate) unsafe fn boundary_open_log_file(
4598        engine: *mut PortableTexEngine<'_>,
4599    ) -> EngineFlow<()> {
4600        let Some(engine) = engine.as_mut() else {
4601            return Ok(());
4602        };
4603        let old_setting = engine.state.selector;
4604        if engine.state.jobname == 0 {
4605            if let Some(jobname) = Self::intern_static_pool_string(engine, "texput")? {
4606                engine.state.jobname = jobname;
4607            }
4608        }
4609        engine.state.logopened = true_0 as boolean;
4610        engine.state.selector = (old_setting as i32 + 2).clamp(0, 21) as eightbits;
4611        Ok(())
4612    }
4613
4614    pub(crate) unsafe fn boundary_jump_out(
4615        engine: *mut PortableTexEngine<'_>,
4616    ) -> EngineFlow<core::convert::Infallible> {
4617        // Status arithmetic is LOAD-BEARING and unchanged: history<=1 is the
4618        // normal `\end`/dump termination (status 0 => "completed"); anything
4619        // else is an error abort (status 1). Every passing conformance test
4620        // funnels its normal end through here, so this must stay byte-exact.
4621        let status = if let Some(engine) = engine.as_ref() {
4622            if engine.state.history as i32 <= 1 {
4623                0 as integer
4624            } else {
4625                1 as integer
4626            }
4627        } else {
4628            1 as integer
4629        };
4630        Self::abort_engine(engine, status)
4631    }
4632
4633    pub(crate) unsafe fn boundary_shipout(
4634        engine: *mut PortableTexEngine<'_>,
4635        box_node: halfword,
4636    ) {
4637        if let Some(engine) = engine.as_mut() {
4638            engine.stripped_shipouts = engine.stripped_shipouts.saturating_add(1);
4639            engine.last_stripped_shipout_box = Some(PortableNodeHandle(box_node));
4640        }
4641    }
4642
4643    pub(crate) unsafe fn boundary_capture_fragment_box(
4644        engine: *mut PortableTexEngine<'_>,
4645        box_node: halfword,
4646        mode: integer,
4647        boxcontext: integer,
4648    ) -> boolean {
4649        if let Some(engine) = engine.as_mut() {
4650            if engine.fragment_capture_enabled {
4651                engine.captured_fragment_root = Some(PortableNodeHandle(box_node));
4652                let absolute_mode = if mode >= 0 { mode } else { -mode };
4653                if absolute_mode == 1 && boxcontext < 1_073_741_824 {
4654                    return true_0;
4655                }
4656            }
4657        }
4658        false_0
4659    }
4660
4661    pub(crate) unsafe fn boundary_build_page(
4662        engine: *mut PortableTexEngine<'_>,
4663    ) -> EngineFlow<()> {
4664        if let Some(engine) = engine.as_mut() {
4665            if engine.format_initialization
4666                && engine.state.curcmd as i32 == 15
4667                && engine.state.curchr == 1
4668            {
4669                // Dump during format build: status-0 abort (normal end of the
4670                // format-initialization run). Propagate via `?` so the engine
4671                // does not unwind.
4672                Self::abort_engine(engine as *mut PortableTexEngine<'_>, 0 as integer)?;
4673            }
4674            engine.stripped_page_builds = engine.stripped_page_builds.saturating_add(1);
4675        }
4676        Ok(())
4677    }
4678
4679    pub(crate) unsafe fn boundary_prune_page_top(
4680        engine: *mut PortableTexEngine<'_>,
4681        node: halfword,
4682        _saving: boolean,
4683    ) -> halfword {
4684        if let Some(engine) = engine.as_mut() {
4685            engine.stripped_page_top_prunes =
4686                engine.stripped_page_top_prunes.saturating_add(1);
4687        }
4688        node
4689    }
4690
4691    pub(crate) unsafe fn boundary_special_out(
4692        engine: *mut PortableTexEngine<'_>,
4693        node: halfword,
4694    ) -> EngineFlow<()> {
4695        let Some(engine) = engine.as_mut() else {
4696            return Ok(());
4697        };
4698        if node < engine.state.memmin || node > engine.state.memend {
4699            engine.stripped_special_outputs = engine
4700                .stripped_special_outputs
4701                .saturating_add(1);
4702            return Ok(());
4703        }
4704        let mem = engine.state.zmem.as_mut_ptr();
4705        if (*mem.offset(node as isize)).hh.u.B0 as i32 == 8 {
4706            match (*mem.offset(node as isize)).hh.u.B1 as i32 {
4707                0 => {
4708                    Self::boundary_open_write_whatsit(engine, node);
4709                    return Ok(());
4710                }
4711                1 => {
4712                    Self::boundary_write_whatsit(engine, node)?;
4713                    return Ok(());
4714                }
4715                2 => {
4716                    Self::boundary_close_write_whatsit(engine, node);
4717                    return Ok(());
4718                }
4719                _ => {}
4720            }
4721        }
4722        engine.stripped_special_outputs = engine
4723            .stripped_special_outputs
4724            .saturating_add(1);
4725        Ok(())
4726    }
4727
4728    unsafe fn boundary_open_write_whatsit(engine: &mut PortableTexEngine<'_>, node: halfword) {
4729        let mem = engine.state.zmem.as_mut_ptr();
4730        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4731        if stream >= 16 {
4732            return;
4733        }
4734
4735        if engine.state.writeopen[stream] != 0 {
4736            Self::boundary_close_write_stream(engine, stream);
4737        }
4738
4739        engine.state.curname = (*mem.offset((node + 1) as isize)).hh.v.RH as strnumber;
4740        engine.state.curarea = (*mem.offset((node + 2) as isize)).hh.v.LH as strnumber;
4741        engine.state.curext = (*mem.offset((node + 2) as isize)).hh.v.RH as strnumber;
4742        if engine.state.curext == 335 {
4743            engine.state.curext = 799;
4744        }
4745        engine.zpackfilename(engine.state.curname, engine.state.curarea, engine.state.curext);
4746        let Some(name) = Self::current_resource_name(engine as *mut PortableTexEngine<'_>) else {
4747            return;
4748        };
4749        let handle = Box::new(PortableFileHandle::new(
4750            name,
4751            ResourceKind::TexInput,
4752            None,
4753            resource_format_tex_input,
4754            Vec::new(),
4755        ));
4756        engine.state.writefile[stream] = Box::into_raw(handle);
4757        engine.state.writeopen[stream] = true_0;
4758    }
4759
4760    unsafe fn boundary_write_whatsit(
4761        engine: &mut PortableTexEngine<'_>,
4762        node: halfword,
4763    ) -> EngineFlow<()> {
4764        let mem = engine.state.zmem.as_mut_ptr();
4765        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4766        if stream >= 16 || engine.state.writeopen[stream] == 0 {
4767            return Ok(());
4768        }
4769        let write_tokens = engine.profile.write_token_constants();
4770        let q = engine.getavail()?;
4771        (*mem.offset(q as isize)).hh.v.LH = write_tokens.open_group_token;
4772        let r = engine.getavail()?;
4773        (*mem.offset(q as isize)).hh.v.RH = r;
4774        (*mem.offset(r as isize)).hh.v.LH = write_tokens.end_write_token;
4775        engine.zbegintokenlist(q, 4)?;
4776        engine.zbegintokenlist((*mem.offset((node + 1) as isize)).hh.v.RH, 15)?;
4777        let q = engine.getavail()?;
4778        (*mem.offset(q as isize)).hh.v.LH = write_tokens.close_group_token;
4779        engine.zbegintokenlist(q, 4)?;
4780        let old_mode = engine.state.curlist.modefield;
4781        engine.state.curlist.modefield = 0;
4782        engine.state.curcs = engine.state.writeloc;
4783        engine.zscantoks(false_0, true_0)?;
4784        engine.state.curlist.modefield = old_mode;
4785        engine.gettoken()?;
4786        if engine.state.curtok != write_tokens.end_write_token {
4787            while engine.state.curtok != write_tokens.end_write_token {
4788                engine.gettoken()?;
4789            }
4790        }
4791        engine.endtokenlist()?;
4792        let old_setting = engine.state.selector;
4793        engine.state.selector = stream as eightbits;
4794        engine.ztokenshow(engine.state.defref);
4795        engine.println();
4796        engine.state.selector = old_setting;
4797        engine.zflushlist(engine.state.defref);
4798        Ok(())
4799    }
4800
4801    unsafe fn boundary_close_write_whatsit(engine: &mut PortableTexEngine<'_>, node: halfword) {
4802        let mem = engine.state.zmem.as_mut_ptr();
4803        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4804        if stream < 16 {
4805            Self::boundary_close_write_stream(engine, stream);
4806        }
4807    }
4808
4809    unsafe fn boundary_close_write_stream(engine: &mut PortableTexEngine<'_>, stream: usize) {
4810        if stream >= 16 || engine.state.writeopen[stream] == 0 {
4811            return;
4812        }
4813        let file = engine.state.writefile[stream];
4814        engine.state.writefile[stream] = core::ptr::null_mut();
4815        engine.state.writeopen[stream] = false_0;
4816        if file.is_null() {
4817            return;
4818        }
4819        let handle = Box::from_raw(file);
4820        let key = Self::virtual_file_key(handle.name.as_str());
4821        engine.virtual_files.insert(key, handle.bytes);
4822    }
4823
4824    pub(crate) unsafe fn boundary_load_picture(
4825        engine: *mut PortableTexEngine<'_>,
4826        _is_pdf: boolean,
4827    ) {
4828        if let Some(engine) = engine.as_mut() {
4829            engine.stripped_picture_loads = engine.stripped_picture_loads.saturating_add(1);
4830        }
4831    }
4832
4833    pub(crate) fn record_stripped_source_special(engine: *mut PortableTexEngine<'_>) {
4834        if let Some(engine) = unsafe { engine.as_mut() } {
4835            engine.stripped_source_specials = engine.stripped_source_specials.saturating_add(1);
4836        }
4837    }
4838
4839    pub(crate) fn record_stripped_write_whatsit_diagnostic(engine: *mut PortableTexEngine<'_>) {
4840        if let Some(engine) = unsafe { engine.as_mut() } {
4841            engine.stripped_write_whatsit_diagnostics =
4842                engine.stripped_write_whatsit_diagnostics.saturating_add(1);
4843        }
4844    }
4845
4846    pub(crate) fn record_stripped_pdf_extension(engine: *mut PortableTexEngine<'_>) {
4847        if let Some(engine) = unsafe { engine.as_mut() } {
4848            engine.stripped_pdf_extensions = engine.stripped_pdf_extensions.saturating_add(1);
4849        }
4850    }
4851
4852    /// Resolve a freshly opened file's input encoding.
4853    ///
4854    /// Mirrors XeTeX's `u_open_in`, which only AUTO-sniffs UNICODE text inputs:
4855    /// the default `\XeTeXinputencoding` is `auto`, applied to `read`/`\input`
4856    /// text streams. Binary inputs (`tfm`/`font`/`fmt`/…) are opened as raw byte
4857    /// files in the original engine and must NOT be sniffed (a stray `FE FF` at
4858    /// the start of a TFM must not skip bytes). The UTF-8/UTF-16 decoders are
4859    /// also XeTeX-only; under the non-XeTeX (`tex`/`etex`) profiles every input
4860    /// is read raw, byte == scalar, matching 8-bit TeX.
4861    fn resolve_input_encoding(engine: &PortableTexEngine<'_>, handle: &mut PortableFileHandle) {
4862        let is_text = handle.format == resource_format_tex_input || handle.format == 0;
4863        if engine.is_xetex() && is_text {
4864            handle.resolve_text_encoding_auto();
4865        } else {
4866            handle.encoding = InputEncoding::Bytes;
4867        }
4868    }
4869
4870    pub(crate) unsafe fn boundary_open_input(
4871        engine: *mut PortableTexEngine<'_>,
4872        file: *mut NativeFileHandle,
4873        format: integer,
4874        mode: const_string,
4875    ) -> boolean {
4876        let Some(engine) = engine.as_mut() else {
4877            if !file.is_null() {
4878                *file = core::ptr::null_mut();
4879            }
4880            return false_0;
4881        };
4882        let Some(name) = Self::current_resource_name(engine as *mut PortableTexEngine<'_>) else {
4883            if !file.is_null() {
4884                *file = core::ptr::null_mut();
4885            }
4886            return false_0;
4887        };
4888        let mode = Self::mode_string(mode);
4889        let request_kind = Self::resource_kind_for_open(engine, name.as_str(), format);
4890        let request_package = Self::resource_package_owner(engine, name.as_str(), request_kind);
4891        let source: Option<PortableSourceSpan> = None;
4892        let request = ResourceRequest {
4893            name: name.as_str(),
4894            kind: request_kind,
4895            package: request_package.as_deref(),
4896            format,
4897            mode: mode.as_str(),
4898            source: source.clone(),
4899        };
4900        engine.resource_requests = engine.resource_requests.saturating_add(1);
4901        let virtual_key = Self::virtual_file_key(name.as_str());
4902        let bytes = if let Some(bytes) = engine.virtual_files.get(&virtual_key) {
4903            bytes.clone()
4904        } else if let Some(bytes) = engine.resources.read(request) {
4905            bytes
4906        } else {
4907            engine.resource_request_records.push(PortableResourceRequestRecord {
4908                name,
4909                kind: request_kind,
4910                package: request_package,
4911                format,
4912                mode,
4913                source,
4914                byte_len: None,
4915            });
4916            if !file.is_null() {
4917                *file = core::ptr::null_mut();
4918            }
4919            return false_0;
4920        };
4921        let byte_len = Self::source_index(bytes.len());
4922        engine.resource_request_records.push(PortableResourceRequestRecord {
4923            name: name.clone(),
4924            kind: request_kind,
4925            package: request_package.clone(),
4926            format,
4927            mode: mode.clone(),
4928            source,
4929            byte_len: Some(byte_len),
4930        });
4931        let mut handle = Box::new(PortableFileHandle::new(
4932            name,
4933            request_kind,
4934            request_package,
4935            format,
4936            bytes,
4937        ));
4938        // Resolve the input encoding (XeTeX `u_open_in` AUTO sniff) for text
4939        // inputs under the XeTeX profile; binary inputs and non-XeTeX profiles
4940        // stay raw `Bytes` with no BOM consumption.
4941        Self::resolve_input_encoding(engine, &mut handle);
4942        if format == resource_format_tfm {
4943            engine.state.tfmtemp = handle.read_byte().map_or(-1, |byte| byte as integer);
4944        }
4945        if !file.is_null() {
4946            *file = Box::into_raw(handle);
4947            return true_0;
4948        }
4949        drop(handle);
4950        false_0
4951    }
4952
4953    unsafe fn begin_primary_input_raw(
4954        self: &mut Self,
4955        name: &str,
4956        bytes: Vec<u8>,
4957    ) -> EngineFlow<boolean> {
4958        if self.state.inputfile.is_null() || self.state.sourcefilenamestack.is_null()
4959            || self.state.fullsourcefilenamestack.is_null()
4960            || self.state.buffer.is_null()
4961        {
4962            return Ok(false_0);
4963        }
4964        let Some(source_name) = Self::intern_static_pool_string(self, name)? else {
4965            return Ok(false_0);
4966        };
4967        self.beginfilereading()?;
4968        let slot = self.state.curinput.indexfield as isize;
4969        let mut handle = Box::new(
4970            PortableFileHandle::new(
4971                name.to_string(),
4972                ResourceKind::TexInput,
4973                None,
4974                resource_format_tex_input,
4975                bytes,
4976            ),
4977        );
4978        Self::resolve_input_encoding(self, &mut handle);
4979        *self.state.inputfile.offset(slot) = Box::into_raw(handle);
4980        self.state.curinput.namefield = source_name as halfword;
4981        *self.state.sourcefilenamestack.offset(slot) = source_name;
4982        *self.state.fullsourcefilenamestack.offset(slot) = source_name;
4983        self.state.curinput.statefield = 33 as quarterword;
4984        self.state.line = 1 as integer;
4985        self.state.src_primary_name = source_name;
4986        self.state.cmd_span = 0;
4987        self.state.pending_call_span = 0;
4988        self.state.src_user_cmd_span = 0;
4989        self.state.src_tok_span = 0;
4990        self.state.src_anchor_cmd = 0;
4991        self.state.src_grp_stack.clear();
4992        self.state.src_grp_closing = 0;
4993        self.state.src_call_user_span = 0;
4994        self.state.src_line_base = 0;
4995        self.state.src_line_buf_start = 0;
4996        self.state.src_prev_line_len = 0;
4997        self.state.src_line_initialized = false;
4998        self.state.curinput.spanfield = 0;
4999        self.state.src_native_offsets.clear();
5000        self.state.src_stack_cells.clear();
5001        self.state.cur_stack_head = 0;
5002        if Self::boundary_input_line(
5003            self as *mut PortableTexEngine<'_>,
5004            *self.state.inputfile.offset(slot) as NativeFileHandle,
5005        ) == 0
5006        {
5007            self.endfilereading();
5008            return Ok(false_0);
5009        }
5010        self.firmuptheline()?;
5011        let eqtb = self.state.zeqtb.as_mut_ptr();
5012        let endline_char_index = if self.is_xetex() && self.state.eqtbtop >= 7_892_312 {
5013            7_892_312_i64
5014        } else {
5015            27_212_i64
5016        };
5017        let endline_char = if eqtb.is_null() {
5018            -1
5019        } else {
5020            (*eqtb.offset(endline_char_index as isize)).u.CINT
5021        };
5022        if !(0..=255).contains(&endline_char) {
5023            self.state.curinput.limitfield -= 1;
5024        } else {
5025            *self.state.buffer.offset(self.state.curinput.limitfield as isize) = endline_char
5026                as UnicodeScalar;
5027        }
5028        self.state.first = (self.state.curinput.limitfield as i32 + 1) as integer;
5029        self.state.curinput.locfield = self.state.curinput.startfield;
5030        Ok(true_0)
5031    }
5032
5033    pub(crate) unsafe fn boundary_input_line(
5034        engine: *mut PortableTexEngine<'_>,
5035        file: NativeFileHandle,
5036    ) -> boolean {
5037        let Some(engine) = engine.as_mut() else {
5038            return false_0;
5039        };
5040        if file.is_null() || engine.state.buffer.is_null() {
5041            return false_0;
5042        }
5043
5044        let handle = &mut *file;
5045        if !handle.has_remaining() {
5046            return false_0;
5047        }
5048
5049        let first = engine.state.first.max(0) as usize;
5050        let limit = engine.state.bufsize.max(0) as usize;
5051        let mut last = first;
5052        // XeTeX `input_line`: decode Unicode scalars via `get_uni_c`, terminating
5053        // on EOF / LF (0x0A) / CR (0x0D). A CR coalesces a following LF (the
5054        // `skipNextLF` logic), so CRLF reads as a single line break.
5055        while last < limit {
5056            let Some(scalar) = handle.next_input_scalar() else {
5057                break;
5058            };
5059            match scalar {
5060                0x0A => break,
5061                0x0D => {
5062                    // Peek the next scalar; consume it only if it is LF. The peek
5063                    // must be restorable (the decoder may have advanced the cursor
5064                    // and/or set saved_char), so snapshot both before decoding.
5065                    let saved_cursor = handle.cursor;
5066                    let saved_lookahead = handle.saved_char;
5067                    let was_eof = handle.eof_after_failed_read;
5068                    match handle.next_input_scalar() {
5069                        Some(0x0A) => {} // CRLF: consume the LF.
5070                        _ => {
5071                            // Not LF (or EOF): restore the peeked state.
5072                            handle.cursor = saved_cursor;
5073                            handle.saved_char = saved_lookahead;
5074                            handle.eof_after_failed_read = was_eof;
5075                        }
5076                    }
5077                    break;
5078                }
5079                scalar => {
5080                    *engine.state.buffer.offset(last as isize) = scalar as UnicodeScalar;
5081                    last += 1;
5082                }
5083            }
5084        }
5085
5086        while last > first && *engine.state.buffer.offset((last - 1) as isize) == b' ' as UnicodeScalar {
5087            last -= 1;
5088        }
5089        if handle.format == resource_format_tex_input || handle.format == 0 {
5090            engine.current_input_package_owner = handle.package.clone();
5091        }
5092        // Source tracking multi-line accumulator (HOOK 7): each line of the
5093        // primary input reloads at the same buffer base, so `loc - first` is a
5094        // within-line column. Track the absolute char offset of the current
5095        // line's first slot (`src_line_base`), advancing it by the previous
5096        // line's length + 1 (the line break) at each refill. The buffer holds
5097        // `[first, last)` file characters for this line.
5098        if engine.state.source_tracking
5099            && engine.state.curinput.namefield as strnumber == engine.state.src_primary_name
5100        {
5101            if engine.state.src_line_initialized {
5102                engine.state.src_line_base += engine.state.src_prev_line_len + 1;
5103            } else {
5104                engine.state.src_line_base = 0;
5105                engine.state.src_line_initialized = true;
5106            }
5107            engine.state.src_line_buf_start = first as integer;
5108            engine.state.src_prev_line_len = last.saturating_sub(first) as u32;
5109            // The token that straddles the line break is read in the same
5110            // `get_next` call as this refill, so the top-of-loop token-start
5111            // snapshot is stale (it points at the previous line). Re-anchor it to
5112            // the new line's start so its span is measured in the new line's
5113            // coordinates.
5114            engine.state.src_token_start = first as integer;
5115        }
5116        engine.state.last = last as integer;
5117        true_0
5118    }
5119
5120    pub(crate) unsafe fn boundary_read_byte(_file: NativeFileHandle) -> integer {
5121        if _file.is_null() {
5122            return -1;
5123        }
5124        (&mut *_file).read_byte().map_or(-1, |byte| byte as integer)
5125    }
5126
5127    pub(crate) unsafe fn boundary_end_of_file(_file: NativeFileHandle) -> integer {
5128        if _file.is_null() || (&*_file).is_eof() {
5129            1
5130        } else {
5131            0
5132        }
5133    }
5134
5135    pub(crate) unsafe fn boundary_flush_file(_file: NativeFileHandle) -> integer {
5136        0
5137    }
5138
5139    pub(crate) unsafe fn boundary_write_byte(
5140        engine: *mut PortableTexEngine<'_>,
5141        character: integer,
5142        file: NativeFileHandle,
5143    ) -> integer {
5144        if !file.is_null() {
5145            if let Ok(byte) = u8::try_from(character) {
5146                (*file).bytes.push(byte);
5147            }
5148            return character;
5149        }
5150        if let Some(engine) = engine.as_mut() {
5151            if let Ok(byte) = u8::try_from(character) {
5152                engine.transcript_bytes.push(byte);
5153            }
5154        }
5155        character
5156    }
5157
5158    pub(crate) unsafe fn boundary_close_file(_file: NativeFileHandle) {
5159        if !_file.is_null() {
5160            drop(Box::from_raw(_file));
5161        }
5162    }
5163
5164    pub(crate) unsafe fn get_seconds_and_micros(
5165        engine: *mut PortableTexEngine<'_>,
5166        seconds: *mut integer,
5167        micros: *mut integer,
5168    ) {
5169        let clock = engine
5170            .as_mut()
5171            .map(|engine| engine.platform.clock())
5172            .unwrap_or_default();
5173        if !seconds.is_null() {
5174            *seconds = clock.seconds;
5175        }
5176        if !micros.is_null() {
5177            *micros = clock.micros;
5178        }
5179    }
5180
5181    pub(crate) unsafe fn linebreak_start(
5182        engine: *mut PortableTexEngine<'_>,
5183        font: integer,
5184        locale: integer,
5185        text: *mut uint16_t,
5186        text_length: integer,
5187    ) {
5188        let Some(engine) = engine.as_mut() else {
5189            return;
5190        };
5191        let text = if text.is_null() || text_length <= 0 {
5192            &[]
5193        } else {
5194            core::slice::from_raw_parts(text as *const uint16_t, text_length as usize)
5195        };
5196        engine
5197            .platform
5198            .linebreak_start(PortableLinebreakRequest { font, locale, text });
5199    }
5200
5201    pub(crate) unsafe fn linebreak_next(engine: *mut PortableTexEngine<'_>) -> integer {
5202        engine
5203            .as_mut()
5204            .and_then(|engine| engine.platform.linebreak_next())
5205            .unwrap_or(-1)
5206    }
5207
5208    pub(crate) unsafe fn abort_engine(
5209        engine: *mut PortableTexEngine<'_>,
5210        status: integer,
5211    ) -> EngineFlow<core::convert::Infallible> {
5212        if let Some(engine) = engine.as_mut() {
5213            engine.last_abort_status = Some(status);
5214        }
5215        Err(EngineBreak::Abort(EngineAbort { status }))
5216    }
5217
5218    /// Reject the current fragment with `message` when the sandbox is active; a
5219    /// no-op (e.g. during format construction) otherwise. The sandbox analogue of
5220    /// [`abort_engine`]: it breaks the run via the `?` chain with an [`EngineError`].
5221    pub(crate) unsafe fn sandbox_reject(
5222        engine: *mut PortableTexEngine<'_>,
5223        message: &str,
5224    ) -> EngineFlow<()> {
5225        if let Some(engine) = engine.as_ref() {
5226            if engine.sandbox {
5227                return Err(EngineBreak::Error(EngineError {
5228                    message: message.into(),
5229                }));
5230            }
5231        }
5232        Ok(())
5233    }
5234
5235    /// Sandbox `$`/math-shift guard, called from `init_math` (entering math). The
5236    /// fragment legitimately enters math via the wrapper `$` (depth 0 -> 1) and may NEST
5237    /// more math inside a text block -- `\hbox{$x$}`, `\text{$y$}` -- which opens at depth
5238    /// >= 1 and is allowed. A BREAKOUT is a user `$` that, in text mode, re-opens math at
5239    /// depth 0 AFTER the wrapper already opened (the wrapper's math was closed back to the
5240    /// outer level); reject only that. No-op outside the sandbox.
5241    pub(crate) unsafe fn sandbox_open_math(
5242        engine: *mut PortableTexEngine<'_>,
5243    ) -> EngineFlow<()> {
5244        if let Some(engine) = engine.as_mut() {
5245            if engine.sandbox {
5246                if engine.sandbox_math_depth == 0 && engine.sandbox_math_opened {
5247                    return Err(EngineBreak::Error(EngineError {
5248                        message: "math shift ($) is not allowed inside a math expression"
5249                            .into(),
5250                    }));
5251                }
5252                engine.sandbox_math_opened = true;
5253                engine.sandbox_math_depth += 1;
5254            }
5255        }
5256        Ok(())
5257    }
5258
5259    /// Sandbox companion to [`sandbox_open_math`], called from `after_math` (leaving
5260    /// math): pop one MATH nesting level. Depth returns to 0 only when the wrapper math
5261    /// closes, so a nested `$x$` closing (depth 2 -> 1) does NOT mark the expression
5262    /// finished and later math stays allowed. No-op off-sandbox.
5263    pub(crate) unsafe fn sandbox_close_math(
5264        engine: *mut PortableTexEngine<'_>,
5265    ) -> EngineFlow<()> {
5266        if let Some(engine) = engine.as_mut() {
5267            if engine.sandbox && engine.sandbox_math_depth > 0 {
5268                engine.sandbox_math_depth -= 1;
5269            }
5270        }
5271        Ok(())
5272    }
5273
5274    /// Sandbox work-budget tick, called once per `main_control` iteration. Rejects
5275    /// the fragment once [`SANDBOX_OP_BUDGET`] iterations are exceeded, bounding
5276    /// runaway expansion / infinite loops (`\def\x{\x}\x`). No-op outside sandbox.
5277    pub(crate) unsafe fn sandbox_tick(
5278        engine: *mut PortableTexEngine<'_>,
5279    ) -> EngineFlow<()> {
5280        if let Some(engine) = engine.as_mut() {
5281            if engine.sandbox {
5282                engine.sandbox_ops = engine.sandbox_ops.saturating_add(1);
5283                if engine.sandbox_ops > SANDBOX_OP_BUDGET {
5284                    return Err(EngineBreak::Error(EngineError {
5285                        message: "expression is too complex or did not terminate".into(),
5286                    }));
5287                }
5288            }
5289        }
5290        Ok(())
5291    }
5292
5293    /// Surfacing hook called from `error()` right after it prints the diagnostic
5294    /// and its context: turn the TeX error into a breaking [`EngineError`]
5295    /// carrying the captured message, threaded back to the driver via the `?`
5296    /// chain like an abort. TeX's normal log-and-recover never runs inside an
5297    /// equation render -- an error is always real and is reported, not swallowed.
5298    /// Declared `EngineFlow<()>` (not `<Infallible>`) so the unreachable tail of
5299    /// `error()` stays warning-free.
5300    pub(crate) unsafe fn surface_error(
5301        engine: *mut PortableTexEngine<'_>,
5302    ) -> EngineFlow<()> {
5303        let message = engine
5304            .as_ref()
5305            .map(|engine| engine.capture_last_error_message())
5306            .unwrap_or_else(|| "TeX error".into());
5307        Err(EngineBreak::Error(EngineError { message }))
5308    }
5309
5310    // =====================================================================
5311    // Source tracking (SpanField + CmdLatch). All of this is gated on the
5312    // runtime `source_tracking` flag; with it false every hook below is a
5313    // cheap predictable no-op and the default render path allocates nothing.
5314    // The only "inheritance" is the two principled rules: (a) a macro body
5315    // inherits its INVOCATION span (the call-site baseline), and (b) math
5316    // noads re-point `cmd_span` from their own parse-time `node_src`. There
5317    // is no byte-matching, input-stack scanning, nearest-macro guessing, or
5318    // blanket parent inheritance.
5319    // =====================================================================
5320
5321    /// Enable/disable source tracking, lazily (re)allocating the `node_src`
5322    /// shadow (sized to `mem`) and clearing the intern tables. Resets all the
5323    /// transient registers so a render starts clean. Default off.
5324    pub fn set_source_tracking(self: &mut Self, on: bool) {
5325        self.state.source_tracking = on;
5326        self.state.cmd_span = 0;
5327        self.state.pending_call_span = 0;
5328        self.state.src_token_start = 0;
5329        self.state.src_line_base = 0;
5330        self.state.src_line_buf_start = 0;
5331        self.state.src_prev_line_len = 0;
5332        self.state.src_line_initialized = false;
5333        self.state.src_call_start = 0;
5334        self.state.src_call_name = 0;
5335        self.state.src_call_state = 0;
5336        self.state.src_call_index = 0;
5337        self.state.src_call_span = 0;
5338        self.state.src_call_argspan = 0;
5339        self.state.src_user_cmd_span = 0;
5340        self.state.src_tok_span = 0;
5341        self.state.src_anchor_cmd = 0;
5342        self.state.src_grp_stack.clear();
5343        self.state.src_grp_closing = 0;
5344        self.state.src_call_user_span = 0;
5345        self.state.curinput.spanfield = 0;
5346        self.state.src_spans.clear();
5347        self.state.src_dedup.clear();
5348        self.state.src_native_offsets.clear();
5349        self.state.src_stack_cells.clear();
5350        self.state.cur_stack_head = 0;
5351        let end = if on { self.state.mem.len() } else { 0 };
5352        self.state.node_src = PagedArray::new(0, end, node_src_default, node_src_sig);
5353        self.state.node_stack = PagedArray::new(0, end, node_stack_default, node_stack_sig);
5354    }
5355
5356    /// Whether source tracking is currently enabled.
5357    pub fn source_tracking_enabled(&self) -> bool {
5358        self.state.source_tracking
5359    }
5360
5361    /// Intern a span into the dedup table, returning a stable first-touch
5362    /// `SrcId` (1-based; `0` is NONE). Uses the explicit `self: &mut Self`
5363    /// receiver form the patcher's passes expect (the `&mut self` shorthand is
5364    /// stripped).
5365    fn intern_span_raw(self: &mut Self, name: strnumber, start: u32, end: u32, role: u8) -> SrcId {
5366        let span = RawSpan { name, start, end, role };
5367        if let Some(&id) = self.state.src_dedup.get(&span) {
5368            return id;
5369        }
5370        self.state.src_spans.push(span);
5371        let id = self.state.src_spans.len() as SrcId;
5372        self.state.src_dedup.insert(span, id);
5373        id
5374    }
5375
5376    /// Absolute character offset, in the primary input's own coordinates, of a
5377    /// buffer position `loc`: `line_base + (loc - line_start)`.
5378    fn src_buf_offset(&self, loc: integer) -> u32 {
5379        let col = loc as i64 - self.state.src_line_buf_start as i64;
5380        (self.state.src_line_base as i64 + col).max(0) as u32
5381    }
5382
5383    /// Resolve a node's stamped span to a [`PortableSourceSpan`] in source-own
5384    /// coordinates, or `None` when the node is unstamped (SrcId 0) or its source
5385    /// name cannot be read. `&self`: safe on the read-only IR snapshot path.
5386    pub(crate) fn resolve_node_src(&self, node: halfword) -> Option<PortableSourceSpan> {
5387        if !self.state.source_tracking || node < 0 {
5388            return None;
5389        }
5390        let id = self.state.node_src.get_copy(node as usize);
5391        if id == 0 {
5392            return None;
5393        }
5394        let raw = *self.state.src_spans.get((id - 1) as usize)?;
5395        let name = unsafe { self.pool_string(raw.name) }?;
5396        Some(PortableSourceSpan {
5397            name,
5398            start: raw.start,
5399            end: raw.end,
5400            role: raw.role,
5401        })
5402    }
5403
5404    /// HOOK 1a (`get_next`, top of the outer loop): snapshot the buffer position
5405    /// of the token about to be lexed. Re-run each loop iteration so leading
5406    /// skipped material (comments / ignored chars) is excluded from the span.
5407    pub(crate) unsafe fn src_mark_token_start(engine: *mut Self) {
5408        let Some(engine) = engine.as_mut() else {
5409            return;
5410        };
5411        if engine.state.source_tracking && engine.state.curinput.statefield as i32 != 0 {
5412            engine.state.src_token_start = engine.state.curinput.locfield as integer;
5413        }
5414    }
5415
5416    /// HOOK 1b (`get_next` tail): the SOLE buffer producer. For real buffer
5417    /// input set the ambient `spanfield` to the just-lexed TOKEN range (so a
5418    /// control word spans backslash..last letter). Token-list input is handled
5419    /// by [`Self::src_tokenlist_span`], not here.
5420    pub(crate) unsafe fn src_record_buffer_span(engine: *mut Self) {
5421        let Some(engine) = engine.as_mut() else {
5422            return;
5423        };
5424        if !engine.state.source_tracking || engine.state.curinput.statefield as i32 == 0 {
5425            return;
5426        }
5427        let a = engine.src_buf_offset(engine.state.src_token_start);
5428        let b = engine.src_buf_offset(engine.state.curinput.locfield as integer);
5429        let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
5430        let name = engine.state.curinput.namefield as strnumber;
5431        let id = engine.intern_span_raw(name, lo, hi, 0);
5432        engine.state.curinput.spanfield = id;
5433        engine.state.src_tok_span = id;
5434        // When the just-lexed buffer token is a CONTROL SEQUENCE (`curcs != 0`) of
5435        // the primary fragment, it is a user-typed command; remember it as the
5436        // active user command. Kernel helper macros reached through its expansion
5437        // are read from token lists (not the buffer), so they never reset this, and
5438        // it survives token-list pops — letting a helper invoked from the buffer
5439        // after a `\futurelet`/`\@ifnextchar` peek recover the user command start.
5440        //
5441        // Gate on `scannerstatus == 0` (normal): a cs lexed while the scanner is
5442        // MATCHING/ABSORBING another macro's arguments is being COLLECTED, not
5443        // executed -- it belongs to that outer command's argument, not the active
5444        // command line. In `\sqrt[\phantom{x}]{x}` the `\phantom` is absorbed into
5445        // `\@sqrt`'s optional `[..]` while matching, so without this gate it
5446        // overwrites the real user command `\sqrt`; the later radicand helper then
5447        // anchors its buffer baseline to the stale `\phantom` start, framing the
5448        // degree box as `[\phantom .. radicand]` = the `[6,21)` overshoot. A digit
5449        // degree (`\sqrt[3]{x}`) has no cs to hijack, which is why it never bit.
5450        if engine.state.curcs != 0
5451            && name == engine.state.src_primary_name
5452            && engine.state.scannerstatus as i32 == 0
5453        {
5454            engine.state.src_user_cmd_span = id;
5455            // A buffer read means we have left any token-list replay context, so the
5456            // replay-tracked anchor command is now stale: clear it. (`src_user_cmd_span`,
5457            // by contrast, intentionally persists so a `\futurelet`-peeked helper can
5458            // still recover the buffer command.)
5459            engine.state.src_anchor_cmd = 0;
5460        }
5461    }
5462
5463    /// HOOK 1c (`get_next` token-list branch): when re-reading from an ARGUMENT
5464    /// / template / backed-up / inserted level (token type < `macro`=5), surface
5465    /// the re-read cell's own scan-time span so a macro argument recovers its
5466    /// typed position. Macro-body (`macro`=5) and `every_*` (>=6) levels keep the
5467    /// inherited call-site baseline, so synthesized body content maps to the
5468    /// invocation.
5469    pub(crate) unsafe fn src_tokenlist_span(engine: *mut Self) {
5470        let Some(engine) = engine.as_mut() else {
5471            return;
5472        };
5473        if !engine.state.source_tracking {
5474            return;
5475        }
5476        if engine.state.curinput.indexfield as i32 >= 5 {
5477            return;
5478        }
5479        // Freeze the token's OWN origin as it is read, so a later `back_input` can
5480        // re-stamp it with this (not the ambient, look-ahead-advanced span). Only for
5481        // NON-macro-body levels (`idx < 5`): a macro body's tokens map to their
5482        // invocation (rule a), never to their definition site, so letting their cell
5483        // span leak here would make a backed-up body token (e.g. `\frac`'s `\over`)
5484        // carry its definition position instead of the call-site baseline.
5485        {
5486            let lf = engine.state.curinput.locfield as i32;
5487            if lf >= 0 {
5488                let cid = engine.state.node_src.get_copy(lf as usize);
5489                if cid != 0 {
5490                    engine.state.src_tok_span = cid;
5491                }
5492            }
5493        }
5494        let cell = engine.state.curinput.locfield as i32;
5495        if cell < 0 {
5496            return;
5497        }
5498        let id = engine.state.node_src.get_copy(cell as usize);
5499        if id != 0 {
5500            engine.state.curinput.spanfield = id;
5501        }
5502    }
5503
5504    /// HOOK 1d (`back_input`): when a token is pushed back onto the input, stamp the
5505    /// freshly-allocated backed-up cell with the token's OWN origin (`src_tok_span`,
5506    /// frozen at the token's last read) rather than the ambient `spanfield`. The two
5507    /// diverge whenever the lexer has read PAST the token before backing it up -- the
5508    /// `\let`/`\futurelet` (and thus `\@ifnextchar`) two-token look-ahead reads a
5509    /// second token, advancing `spanfield`, then re-emits the first. Without this the
5510    /// re-emitted token (e.g. the single-char optional `[a]` degree of `\sqrt`, which
5511    /// LaTeX's `\@ifnextchar`-driven `\sqrt`/`\root` machinery shuttles through such a
5512    /// look-ahead) would inherit the look-ahead's span -- the construct baseline --
5513    /// and the typed char's byte-span would be lost. The token's real origin is still
5514    /// live in `src_tok_span`, so the leaf is recoverable here, at the re-emission.
5515    pub(crate) unsafe fn src_back_input_stamp(engine: *mut Self, p: halfword) {
5516        let Some(engine) = engine.as_mut() else {
5517            return;
5518        };
5519        if !engine.state.source_tracking || p < 0 {
5520            return;
5521        }
5522        let id = engine.state.src_tok_span;
5523        if id != 0 {
5524            engine.state.node_src.set(p as usize, id);
5525        }
5526    }
5527
5528    /// HOOK 2 (`main_control` dispatch, right after `get_x_token`): freeze the
5529    /// commanding token's span before it runs any argument sub-scan. This single
5530    /// site solves `\char`/`\mathchar`/`\accent` scan-loss for free.
5531    pub(crate) unsafe fn src_latch_cmd_span(engine: *mut Self) {
5532        let Some(engine) = engine.as_mut() else {
5533            return;
5534        };
5535        if engine.state.source_tracking {
5536            engine.state.cmd_span = engine.state.curinput.spanfield;
5537        }
5538    }
5539
5540    /// HOOK 3 (`get_avail`): stamp every single-word cell with the ambient span
5541    /// — token cells (so re-read arguments recover their typed position) and TFM
5542    /// char nodes provisionally (overwritten by [`Self::src_stamp_char`]). Also
5543    /// snapshots the live enclosing-construct stack head onto `node_stack`.
5544    pub(crate) unsafe fn src_stamp_avail(engine: *mut Self, node: halfword) {
5545        let Some(engine) = engine.as_mut() else {
5546            return;
5547        };
5548        if engine.state.source_tracking && node >= 0 {
5549            let id = engine.state.curinput.spanfield;
5550            engine.state.node_src.set(node as usize, id);
5551            engine.state.node_stack.set(node as usize, engine.state.cur_stack_head);
5552        }
5553    }
5554
5555    /// HOOK 4 (`get_node`): stamp every variable-size node AND every noad over
5556    /// its whole address range with the current construct span, so the nucleus
5557    /// subfield inherits the atom's span with no separate math-field writer. Also
5558    /// snapshots the live enclosing-construct stack head onto `node_stack` over the
5559    /// same range (independent of `cmd_span`, so a node allocated inside a
5560    /// construct still records its enclosure even when its primary is unstamped).
5561    pub(crate) unsafe fn src_stamp_node_range(engine: *mut Self, node: halfword, size: integer) {
5562        let Some(engine) = engine.as_mut() else {
5563            return;
5564        };
5565        if !engine.state.source_tracking || node < 0 || size <= 0 {
5566            return;
5567        }
5568        let id = engine.state.cmd_span;
5569        let head = engine.state.cur_stack_head;
5570        let base = node as usize;
5571        for i in 0..size as usize {
5572            if id != 0 {
5573                engine.state.node_src.set(base + i, id);
5574            }
5575            if head != 0 {
5576                engine.state.node_stack.set(base + i, head);
5577            }
5578        }
5579    }
5580
5581    /// HOOK (`copy_node_list`, after each node is duplicated): a COPY has the same
5582    /// source origin as its original, so propagate the original's tracked span (and
5583    /// enclosing-construct chain) onto the copy. Without this the copy keeps only the
5584    /// ambient `cmd_span` that `get_node` stamped at copy time — which for a
5585    /// `\mathchoice`-replicated `\sqrt[#1]{}` degree is the whole construct hull, so the
5586    /// degree digit `3` maps to `\sqrt[3]{..}` instead of its own `"3"`. Only overrides
5587    /// when the original is stamped (id != 0); an unstamped source leaves the copy's
5588    /// `get_node` stamp intact. Uniform across every copied node, by closure.
5589    pub(crate) unsafe fn src_carry_copy(engine: *mut Self, src: halfword, dst: halfword) {
5590        let Some(engine) = engine.as_mut() else {
5591            return;
5592        };
5593        if !engine.state.source_tracking || src < 0 || dst < 0 {
5594            return;
5595        }
5596        let id = engine.state.node_src.get_copy(src as usize);
5597        if id != 0 {
5598            engine.state.node_src.set(dst as usize, id);
5599            let head = engine.state.node_stack.get_copy(src as usize);
5600            engine.state.node_stack.set(dst as usize, head);
5601        }
5602    }
5603
5604    /// HOOK (token COPY): the analogue of [`Self::src_carry_copy`] for the TOKEN
5605    /// (not node) memory. Token-list copies go through `store_new_token(info(src))`,
5606    /// which copies only the token VALUE -- so the new cell `dest` would keep the
5607    /// ambient `get_avail` stamp and lose `src`'s real origin. Carry `src`'s tracked
5608    /// span (and enclosing chain) onto `dest`, so a source byte-span rides token
5609    /// copies the same way it rides the input stack. This is what lets a SINGLE-token
5610    /// macro argument (e.g. the optional `[a]` degree of `\sqrt`) keep its own leaf
5611    /// span through expl3's argument re-tokenisation, instead of collapsing to the
5612    /// `\sqrt[a]` construct hull. Uniform across every token copy, by closure: the
5613    /// anchor is the WEB-layer `src_token_copy` marker (added in the change file at
5614    /// every `store_new_token(info(..))` site), not a fragile inlined Rust pattern --
5615    /// so it holds for tex, etex and xetex identically.
5616    pub(crate) unsafe fn src_carry_token_span(engine: *mut Self, dest: halfword, src: halfword) {
5617        let Some(engine) = engine.as_mut() else {
5618            return;
5619        };
5620        if !engine.state.source_tracking || dest < 0 || src < 0 {
5621            return;
5622        }
5623        let id = engine.state.node_src.get_copy(src as usize);
5624        if id != 0 {
5625            engine.state.node_src.set(dest as usize, id);
5626            let head = engine.state.node_stack.get_copy(src as usize);
5627            engine.state.node_stack.set(dest as usize, head);
5628        }
5629    }
5630
5631    /// HOOK 5 (`new_character`): overwrite the provisional get_avail stamp on a
5632    /// TFM char glyph with the construct span, so `\char98` maps to the command,
5633    /// not the scanned digits. Also records the live enclosing-construct stack head
5634    /// (overridden for `make_ord` nuclei by [`Self::src_carry_nucleus`]).
5635    pub(crate) unsafe fn src_stamp_char(engine: *mut Self, node: halfword) {
5636        let Some(engine) = engine.as_mut() else {
5637            return;
5638        };
5639        if !engine.state.source_tracking || node < 0 {
5640            return;
5641        }
5642        let id = engine.state.cmd_span;
5643        if id != 0 {
5644            engine.state.node_src.set(node as usize, id);
5645        }
5646        engine.state.node_stack.set(node as usize, engine.state.cur_stack_head);
5647    }
5648
5649    /// HOOK 6 (`mlist_to_hlist` noad-loop head): re-point `cmd_span` to noad
5650    /// `q`'s own parse-time span, so every bar/surd/delimiter/kern synthesized
5651    /// for `q` inherits it — defeating end-of-math `$` staleness with one read.
5652    pub(crate) unsafe fn src_mlist_repoint(engine: *mut Self, q: halfword) {
5653        let Some(engine) = engine.as_mut() else {
5654            return;
5655        };
5656        if !engine.state.source_tracking || q < 0 {
5657            return;
5658        }
5659        let id = engine.state.node_src.get_copy(q as usize);
5660        if id != 0 {
5661            engine.state.cmd_span = id;
5662        }
5663    }
5664
5665    /// HOOK (`scan_math` field commit): a math FIELD (nucleus/sub/sup/accent/radical)
5666    /// holding a single math-char is filled by `scan_math` directly into the field
5667    /// word -- it is NOT a separately-allocated noad, so it carries the enclosing
5668    /// noad's stamp, not its own char's. Record the field char's tracked span (the
5669    /// ambient `spanfield` of the token that produced it, captured at the commit
5670    /// before any look-ahead moves it) keyed on the field ADDRESS, so `clean_box`
5671    /// can carry it onto the fresh noad it builds. Uniform: every scanned single-char
5672    /// field, by closure -- no per-construct code.
5673    pub(crate) unsafe fn src_stamp_field(engine: *mut Self, field: halfword) {
5674        let Some(engine) = engine.as_mut() else {
5675            return;
5676        };
5677        if !engine.state.source_tracking || field < 0 {
5678            return;
5679        }
5680        let id = engine.state.curinput.spanfield;
5681        if id != 0 {
5682            engine.state.node_src.set(field as usize, id);
5683        }
5684    }
5685
5686    /// HOOK (`clean_box` math-char case): when `clean_box` packages a single-math-char
5687    /// FIELD it allocates a FRESH noad and copies the field word into its nucleus; the
5688    /// fresh noad was stamped by `get_node` with the ambient (enclosing atom's)
5689    /// `cmd_span`, so the mlist re-point would map the cleaned glyph to the BASE. Carry
5690    /// the field's own tracked source (recorded at `src_stamp_field`) onto the fresh
5691    /// noad so the re-point yields the field char's real origin (fixes `x^2`->`2`,
5692    /// `^{\infty}`->`\infty`). Uniform copy-carrier; no glyph/construct logic.
5693    pub(crate) unsafe fn src_carry_field(engine: *mut Self, field: halfword, noad: halfword) {
5694        let Some(engine) = engine.as_mut() else {
5695            return;
5696        };
5697        if !engine.state.source_tracking || field < 0 || noad < 0 {
5698            return;
5699        }
5700        let id = engine.state.node_src.get_copy(field as usize);
5701        let head = engine.state.node_stack.get_copy(field as usize);
5702        if id != 0 {
5703            // noad_size = 4; stamp the whole noad range so the loop-head re-point
5704            // (reads node_src[noad]) and the nucleus both see the field's source.
5705            for i in 0..4usize {
5706                engine.state.node_src.set(noad as usize + i, id);
5707            }
5708        }
5709        // Carry the field's enclosing-construct chain too, so the cleaned glyph's
5710        // enclosing entries match the field char's nesting, not the fresh noad's.
5711        if head != 0 {
5712            for i in 0..4usize {
5713                engine.state.node_stack.set(noad as usize + i, head);
5714            }
5715        }
5716    }
5717
5718    /// HOOK (`mlist_to_hlist` make_ord nucleus attach): a directly-built math-char
5719    /// nucleus glyph is the non-`clean_box` analogue of [`Self::src_carry_field`].
5720    /// `get_node`/`new_character` stamped it with the enclosing noad's construct
5721    /// span, so a typed char wrapped in an atom (`\mathbin{+}` -> `+`) would map to
5722    /// the construct. Carry the nucleus FIELD's own leaf span -- recorded at
5723    /// `scan_math` by [`Self::src_stamp_field`], or by the brace-collapse carry --
5724    /// onto the freshly-built glyph node, so it maps to its own char. Per-glyph
5725    /// only; never touches `cmd_span`, so a structural rule built for the same noad
5726    /// afterward still inherits the construct span via the loop-head re-point.
5727    /// Uniform: every directly-built ord-like nucleus glyph, by closure -- no
5728    /// per-construct code. When the field carries no leaf span (`\char`/`\mathchar`,
5729    /// no `scan_math`) the carry is a no-op and the construct stamp stands.
5730    pub(crate) unsafe fn src_carry_nucleus(engine: *mut Self, noad: halfword, glyph: halfword) {
5731        let Some(engine) = engine.as_mut() else {
5732            return;
5733        };
5734        if !engine.state.source_tracking || noad < 0 || glyph < 0 {
5735            return;
5736        }
5737        let id = engine.state.node_src.get_copy(noad as usize + 1);
5738        if id != 0 {
5739            engine.state.node_src.set(glyph as usize, id);
5740        }
5741        // Carry the nucleus field's enclosing-construct chain onto the glyph, so a
5742        // typed char's enclosing entries are its parse-time nesting (e.g. the
5743        // `\mathbin{+}` group frame) rather than the layout-time stack.
5744        let head = engine.state.node_stack.get_copy(noad as usize + 1);
5745        engine.state.node_stack.set(glyph as usize, head);
5746    }
5747
5748    /// HOOK (`handle_right_brace` math-group collapse): when a braced sub-formula
5749    /// `^{\infty}` / `_{y}` reduces to a SINGLE math-char noad, its nucleus is copied
5750    /// into the saved FIELD word and the noad is freed -- losing the noad's tracked
5751    /// source. Carry `node_src[noad]` onto the field FIRST, so the later `clean_box`
5752    /// (via `src_carry_field`) maps the cleaned glyph to the braced char's own origin
5753    /// rather than the enclosing atom's. Uniform; no glyph/construct logic.
5754    pub(crate) unsafe fn src_carry_collapse(engine: *mut Self, noad: halfword, field: halfword) {
5755        let Some(engine) = engine.as_mut() else {
5756            return;
5757        };
5758        if !engine.state.source_tracking || noad < 0 || field < 0 {
5759            return;
5760        }
5761        let id = engine.state.node_src.get_copy(noad as usize);
5762        if id != 0 {
5763            engine.state.node_src.set(field as usize, id);
5764        }
5765        // Carry the collapsing noad's enclosing chain (e.g. the math-group frame
5766        // built between its `{`/`}`) onto the field, so the later nucleus carry
5767        // gives the cleaned glyph its braced construct as an enclosing entry.
5768        let head = engine.state.node_stack.get_copy(noad as usize);
5769        if head != 0 {
5770            engine.state.node_stack.set(field as usize, head);
5771        }
5772    }
5773
5774    /// HOOK 7-aux save (`clean_box` entry): snapshot `cmd_span` so it can be
5775    /// restored across recursive sub-box cleaning.
5776    pub(crate) unsafe fn src_save_cmd_span(engine: *mut Self) -> u32 {
5777        engine.as_ref().map_or(0, |engine| engine.state.cmd_span)
5778    }
5779
5780    /// HOOK 7-aux restore (`clean_box` exit): put the enclosing construct's span
5781    /// back so the structural rule built afterward inherits it.
5782    pub(crate) unsafe fn src_restore_cmd_span(engine: *mut Self, saved: u32) {
5783        if let Some(engine) = engine.as_mut() {
5784            if engine.state.source_tracking {
5785                engine.state.cmd_span = saved;
5786            }
5787        }
5788    }
5789
5790    // --- Enclosing-construct stack (source-tracking inc2) -------------------
5791    // A small arena of parent-linked frames snapshotting the construct nesting
5792    // (macro invocations + delimited primitive argument groups). `cur_stack_head`
5793    // is the live top; each node records it in `node_stack` at allocation. The
5794    // frames are resolved at IR-emit time into role-tagged EnclosingConstruct
5795    // entries, so a consumer can pick any altitude from the leaf primary up.
5796
5797    /// Push a finalized construct frame (its span already known, e.g. a macro
5798    /// invocation hull) and make it the live top. Returns the new 1-based head.
5799    fn src_stack_push_span(self: &mut Self, span: SrcId) -> u32 {
5800        self.state.src_stack_cells.push(SrcStackCell {
5801            span,
5802            parent: self.state.cur_stack_head,
5803            start: 0,
5804            name: 0,
5805            pending: false,
5806        });
5807        let head = self.state.src_stack_cells.len() as u32;
5808        self.state.cur_stack_head = head;
5809        head
5810    }
5811
5812    /// Push a PENDING group frame: its start offset + source name are known at the
5813    /// `{` open, its end is finalized at the matching `}` close. Made the live top.
5814    fn src_stack_push_pending(self: &mut Self, start: u32, name: strnumber) -> u32 {
5815        self.state.src_stack_cells.push(SrcStackCell {
5816            span: 0,
5817            parent: self.state.cur_stack_head,
5818            start,
5819            name,
5820            pending: true,
5821        });
5822        let head = self.state.src_stack_cells.len() as u32;
5823        self.state.cur_stack_head = head;
5824        head
5825    }
5826
5827    /// Pop the live top frame (LIFO), restoring its parent as the head.
5828    fn src_stack_pop(self: &mut Self) {
5829        let head = self.state.cur_stack_head;
5830        if head != 0 {
5831            if let Some(cell) = self.state.src_stack_cells.get((head - 1) as usize) {
5832                self.state.cur_stack_head = cell.parent;
5833            }
5834        }
5835    }
5836
5837    /// HOOK 8b (`end_token_list`): pop the macro-body frame when a macro-body level
5838    /// (token type 6) ends, keeping the stack symmetric with HOOK 8a. Also captures
5839    /// the FURTHEST-reaching last-token span across the levels popped after a
5840    /// `macro_call` (`src_call_argspan`, reset to 0 at `src_macro_begin`) -- the
5841    /// closing `}` of the macro's final brace argument -- for the argument hull in
5842    /// [`Self::src_macro_set_pending`]. The MAX-end choice (not just the first pop)
5843    /// recovers the trailing `}` for a `\mathchoice`-replayed robust `\frac␣`, whose
5844    /// pop order surfaces the cs span first and the closing brace later.
5845    pub(crate) unsafe fn src_end_token_list(engine: *mut Self, token_type: i32) {
5846        let Some(engine) = engine.as_mut() else {
5847            return;
5848        };
5849        if !engine.state.source_tracking {
5850            return;
5851        }
5852        let cand = engine.state.curinput.spanfield;
5853        if cand != 0 {
5854            let cand_end = engine
5855                .state
5856                .src_spans
5857                .get((cand - 1) as usize)
5858                .map(|r| r.end)
5859                .unwrap_or(0);
5860            let cur_end = if engine.state.src_call_argspan != 0 {
5861                engine
5862                    .state
5863                    .src_spans
5864                    .get((engine.state.src_call_argspan - 1) as usize)
5865                    .map(|r| r.end)
5866                    .unwrap_or(0)
5867            } else {
5868                0
5869            };
5870            if engine.state.src_call_argspan == 0 || cand_end > cur_end {
5871                engine.state.src_call_argspan = cand;
5872            }
5873        }
5874        if token_type == 6 {
5875            engine.src_stack_pop();
5876        }
5877    }
5878
5879    /// HOOK (`scan_math` `{`-argument open): push a PENDING enclosing frame for a
5880    /// delimited primitive argument (`\mathbin{+}`, `\sqrt[..]{..}` radicand, ...).
5881    /// Its extent starts at the enclosing command's own start (the live `cmd_span`,
5882    /// e.g. `\mathbin`) and is finalized at the matching `}` close to span the whole
5883    /// `cmd{...}` construct. When no command is active the frame is empty (skipped
5884    /// at resolve). General: every scan_math braced field, by closure.
5885    pub(crate) unsafe fn src_scan_math_group_open(engine: *mut Self) {
5886        let Some(engine) = engine.as_mut() else {
5887            return;
5888        };
5889        if !engine.state.source_tracking {
5890            return;
5891        }
5892        let cmd = engine.state.cmd_span;
5893        let (start, name) = if cmd != 0 {
5894            match engine.state.src_spans.get((cmd - 1) as usize) {
5895                Some(raw) => (raw.start, raw.name),
5896                None => (0, 0),
5897            }
5898        } else {
5899            (0, 0)
5900        };
5901        engine.src_stack_push_pending(start, name);
5902        // Consumed-extent: push the group's OPENING `{` token span (the live spanfield
5903        // of the brace just scanned) for `src_construct_extent`. Distinct from the
5904        // enclosing-frame start above (which is the enclosing COMMAND): `min(noad, {)`
5905        // lets a bare `{n\choose k}` group pull its fraction's start to the `{`.
5906        engine.state.src_grp_stack.push(engine.state.curinput.spanfield);
5907    }
5908
5909    /// HOOK (`handle_right_brace` math-group close): finalize the PENDING group
5910    /// frame (end = the post-`}` buffer offset, same source as the start) and pop
5911    /// it. The interned `[start,end)` is the full `cmd{...}` extent.
5912    pub(crate) unsafe fn src_scan_math_group_close(engine: *mut Self) {
5913        let eptr = engine;
5914        let Some(e) = engine.as_mut() else {
5915            return;
5916        };
5917        if !e.state.source_tracking {
5918            return;
5919        }
5920        // Consumed-extent: pop this group's opening `{` span, hand it to
5921        // `src_construct_extend_to_loc` (later in the same `9 =>` arm) as the construct's
5922        // group-open for the `min(noad, {)` start.
5923        e.state.src_grp_closing = e.state.src_grp_stack.pop().unwrap_or(0);
5924        let grp = e.state.src_grp_closing;
5925        // A `\over`/`\atop`/`\choose` in this group leaves the in-progress generalized
5926        // fraction noad in `curlist.auxfield` (still set here, before `fin_mlist`). Apply
5927        // the SAME group consumed-extent to it so its bar / `\atopwithdelims` delimiters
5928        // map to the whole `{..\choose..}` group -- the one rule reaches the fraction too.
5929        let aux = e.state.curlist.auxfield.u.CINT;
5930        let frac = if aux > 0 && aux != -(268435455 as i32) { aux } else { -1 };
5931        if e.state.cur_stack_head != 0 {
5932            let idx = (e.state.cur_stack_head - 1) as usize;
5933            if let Some(cell) = e.state.src_stack_cells.get(idx).copied() {
5934                // Only finalize a still-pending group frame whose source matches the live
5935                // buffer; otherwise just pop (defensive against any non-group top).
5936                if cell.pending
5937                    && cell.name != 0
5938                    && cell.name == e.state.curinput.namefield as strnumber
5939                {
5940                    let end = e.src_buf_offset(e.state.curinput.locfield as integer);
5941                    let (lo, hi) = if cell.start <= end {
5942                        (cell.start, end)
5943                    } else {
5944                        (end, cell.start)
5945                    };
5946                    let id = e.intern_span_raw(cell.name, lo, hi, 1);
5947                    if let Some(c) = e.state.src_stack_cells.get_mut(idx) {
5948                        c.span = id;
5949                        c.pending = false;
5950                    }
5951                }
5952                e.state.cur_stack_head = cell.parent;
5953            }
5954        }
5955        if frac >= 0 {
5956            Self::src_construct_extent(eptr, frac, grp);
5957        }
5958    }
5959
5960    /// HOOK (`math_radical` / `math_ac`, right after the construct noad is
5961    /// allocated): anchor its source START to the in-fragment USER command. The
5962    /// noad was just stamped by `get_node` with the ambient `cmd_span`, which for a
5963    /// `\@ifnextchar`-peeked construct (`\sqrt{y}` -> `\sqrtsign` dispatched while
5964    /// the buffer `spanfield` still points at the peeked `{`) is the radicand brace,
5965    /// not the command. The noad is allocated BEFORE its field is scanned, so the
5966    /// live `src_user_cmd_span` is exactly the command the user typed (no inner
5967    /// construct lexed yet). Pull the START left to it (same source, only leftward),
5968    /// keeping the end; the matching `src_construct_extend_to_loc` then grows the end
5969    /// past the field. Uniform: every mark-synthesizing construct primitive, by
5970    /// closure -- no per-construct code, no heuristic (the command<->noad link is
5971    /// the tracked user-command register, not source adjacency).
5972    pub(crate) unsafe fn src_construct_anchor(engine: *mut Self) {
5973        let Some(engine) = engine.as_mut() else {
5974            return;
5975        };
5976        if !engine.state.source_tracking {
5977            return;
5978        }
5979        let noad = engine.state.curlist.tailfield as i32;
5980        if noad < 0 {
5981            return;
5982        }
5983        let id = engine.state.node_src.get_copy(noad as usize);
5984        if id == 0 {
5985            return;
5986        }
5987        let Some(raw) = engine.state.src_spans.get((id - 1) as usize).copied() else {
5988            return;
5989        };
5990        // Prefer the replay-aware anchor command (set while a NESTED construct was
5991        // replayed from a token list); fall back to the buffer user command ONLY when
5992        // the anchor is absent or from a different source. `src_anchor_cmd` is reset
5993        // on every buffer read, so it never leaks across sibling constructs.
5994        //
5995        // The anchor and the fallback are NOT interchangeable candidates to pick
5996        // whichever "wins" a leftward-pull check: a valid, same-source anchor means
5997        // this noad IS the replayed nested construct, so the buffer user command (the
5998        // OUTER construct enclosing the replay, e.g. Cardano's outer `\sqrt[3]{..}`)
5999        // must never be consulted for it -- not even as a fallback -- regardless of
6000        // whether the anchor itself happens to already equal the noad's own start (no
6001        // pull needed: the noad is already correctly anchored to itself, not to the
6002        // buffer command of note). Only ever pulls the START leftward.
6003        let anchor = (engine.state.src_anchor_cmd != 0)
6004            .then(|| engine.state.src_spans.get((engine.state.src_anchor_cmd - 1) as usize).copied())
6005            .flatten()
6006            .filter(|u| u.name == raw.name);
6007        let chosen = match anchor {
6008            Some(u) => {
6009                if u.start < raw.start {
6010                    Some(u)
6011                } else {
6012                    None
6013                }
6014            }
6015            None => (engine.state.src_user_cmd_span != 0)
6016                .then(|| engine.state.src_spans.get((engine.state.src_user_cmd_span - 1) as usize).copied())
6017                .flatten()
6018                .filter(|u| u.name == raw.name && u.start < raw.start),
6019        };
6020        let Some(u) = chosen else {
6021            return;
6022        };
6023        let newid = engine.intern_span_raw(raw.name, u.start, raw.end, raw.role);
6024        engine.state.node_src.set(noad as usize, newid);
6025    }
6026
6027    /// HOOK (`handle_right_brace` math-group close, after the nucleus field is
6028    /// filled): the spec's "extend to loc at noad commit" half of the construct
6029    /// rule. A construct primitive (`\radical`, `\mathaccent`, hence `\sqrt{y}`,
6030    /// `\hat{x}`, bare `\radical..{y}`) scans its nucleus `{..}` AFTER its command:
6031    /// `scan_math` RETURNS at the opening `{` and the field is filled only when the
6032    /// group closes here, so the noad stamped at allocation covers only the command.
6033    /// Extend the construct noad's source END to the post-`}` buffer loc, giving the
6034    /// surd/vinculum/accent the FULL `cmd{..}` extent. The START (the latched
6035    /// command) is kept, so this is pure right-extension; the nucleus field span is
6036    /// never touched, so the radicand glyph keeps its own char. A token-list-replayed
6037    /// nucleus (a NESTED `\sqrt{..}`'s radicand inside a degree-form outer) has a mem-ptr
6038    /// loc, so the end comes from the just-closed `}` token's own span instead of the
6039    /// buffer loc. Uniform across every construct whose nucleus is its `+1` field, by
6040    /// closure -- no per-construct code.
6041    pub(crate) unsafe fn src_construct_extend_to_loc(engine: *mut Self) {
6042        let e = match engine.as_ref() {
6043            Some(e) => e,
6044            None => return,
6045        };
6046        if !e.state.source_tracking {
6047            return;
6048        }
6049        // Only the construct's OWN nucleus (its `+1` field): a sub/superscript field
6050        // (`+2`/`+3`) closing must not extend the base atom.
6051        let field = (*e
6052            .state
6053            .savestack
6054            .offset((e.state.saveptr as i32 + 0 as i32) as isize))
6055            .u
6056            .CINT;
6057        let noad = e.state.curlist.tailfield as i32;
6058        let grp = e.state.src_grp_closing;
6059        if noad < 0 || field != noad + 1 {
6060            return;
6061        }
6062        Self::src_construct_extent(engine, noad, grp);
6063    }
6064
6065    /// THE general construct-extent rule (replaces the per-construct delimiter/accent/
6066    /// nucleus extenders). Map a construct noad's SYNTHESIZED marks (radical surd +
6067    /// vinculum, fraction bar/delimiters, accent glyph, `\left/\right` delimiters) to the
6068    /// construct's full CONSUMED-SOURCE extent `[min(noad command start, group open),
6069    /// consumed end]`:
6070    /// - `group_open` = the opening-token span of the construct's group (`{` of a
6071    ///   `scan_math` field / bare math group, or `\left`), from `src_grp_stack`. `min`
6072    ///   keeps a PREFIX command's earlier start (`\sqrt{x}` -> `\sqrt`) and pulls an
6073    ///   ENCLOSING group's start to the bracket (`\left(..\right)`, `{n\choose k}`). `0`
6074    ///   means no group (END-only: an unbraced `\dot q`).
6075    /// - END = the live post-close buffer loc, or, when the close is replayed from a token
6076    ///   list (a nested construct), the just-closed token's OWN interned span end.
6077    /// Pure extension of `node_src[noad]`; the nucleus FIELD / leaf spans are never
6078    /// touched, so content chars keep their own origin. One rule, no per-construct code.
6079    pub(crate) unsafe fn src_construct_extent(engine: *mut Self, noad: halfword, group_open: SrcId) {
6080        let Some(engine) = engine.as_mut() else {
6081            return;
6082        };
6083        if !engine.state.source_tracking || noad < 0 {
6084            return;
6085        }
6086        let id = engine.state.node_src.get_copy(noad as usize);
6087        if id == 0 {
6088            return;
6089        }
6090        let Some(raw) = engine.state.src_spans.get((id - 1) as usize).copied() else {
6091            return;
6092        };
6093        let (end, name) = if engine.state.curinput.statefield as i32 != 0 {
6094            (
6095                engine.src_buf_offset(engine.state.curinput.locfield as integer),
6096                engine.state.curinput.namefield as strnumber,
6097            )
6098        } else {
6099            let sid = engine.state.curinput.spanfield;
6100            if sid == 0 {
6101                return;
6102            }
6103            let Some(braw) = engine.state.src_spans.get((sid - 1) as usize).copied() else {
6104                return;
6105            };
6106            (braw.end, braw.name)
6107        };
6108        if raw.name != name {
6109            return;
6110        }
6111        let end = end.max(raw.end);
6112        let mut start = raw.start;
6113        if group_open != 0 {
6114            if let Some(g) = engine.state.src_spans.get((group_open - 1) as usize).copied() {
6115                if g.name == name && g.start < start {
6116                    start = g.start;
6117                }
6118            }
6119        }
6120        if start == raw.start && end == raw.end {
6121            return;
6122        }
6123        let newid = engine.intern_span_raw(name, start, end, raw.role);
6124        engine.state.node_src.set(noad as usize, newid);
6125    }
6126
6127    /// HOOK (`math_left_right`, after `scan_delimiter`): drive the GENERAL extent for a
6128    /// `\left..\right` group, which is NOT a `scan_math` `{}` field so does not pass
6129    /// through `src_scan_math_group_*`. `\left` (t==30) pushes its command span as the
6130    /// group open; `\right` (t==31) pops it and applies `src_construct_extent` to the
6131    /// right delimiter noad `p` (from which `make_left_right` builds BOTH delimiter
6132    /// glyphs), giving them the whole `[\left, )]` extent through the same one rule.
6133    pub(crate) unsafe fn src_leftright(engine: *mut Self, p: halfword, t: integer) {
6134        let Some(eng) = engine.as_mut() else {
6135            return;
6136        };
6137        if !eng.state.source_tracking {
6138            return;
6139        }
6140        if t == 30 as i32 {
6141            let cmd = eng.state.cmd_span;
6142            eng.state.src_grp_stack.push(cmd);
6143        } else if t == 31 as i32 {
6144            let open = eng.state.src_grp_stack.pop().unwrap_or(0);
6145            Self::src_construct_extent(engine, p, open);
6146        }
6147    }
6148
6149    /// Resolve a node's enclosing-construct chain (innermost first) to display
6150    /// spans, gated to the node's own source and to frames that strictly enclose
6151    /// the node's primary range (a real enclosing construct contains the node),
6152    /// with consecutive duplicates and the primary-equal innermost frame dropped.
6153    /// `&self`: safe on the read-only IR snapshot path. Empty when tracking off.
6154    /// Consumed by the IR builder to emit role-tagged EnclosingConstruct entries.
6155    pub fn node_enclosing_spans(&self, handle: PortableNodeHandle) -> Vec<PortableSourceSpan> {
6156        let node = handle.0 as halfword;
6157        let mut out: Vec<PortableSourceSpan> = Vec::new();
6158        if !self.state.source_tracking || node < 0 {
6159            return out;
6160        }
6161        let primary = self.resolve_node_src(node);
6162        let mut head = self.state.node_stack.get_copy(node as usize);
6163        let mut guard = 0u32;
6164        while head != 0 {
6165            guard += 1;
6166            if guard > 4096 {
6167                break;
6168            }
6169            let Some(cell) = self.state.src_stack_cells.get((head - 1) as usize) else {
6170                break;
6171            };
6172            let parent = cell.parent;
6173            let span_id = cell.span;
6174            head = parent;
6175            if span_id == 0 {
6176                continue;
6177            }
6178            let Some(raw) = self.state.src_spans.get((span_id - 1) as usize) else {
6179                continue;
6180            };
6181            let Some(name) = (unsafe { self.pool_string(raw.name) }) else {
6182                continue;
6183            };
6184            if let Some(p) = primary.as_ref() {
6185                // Same-source + containment: an enclosing construct's range must
6186                // contain the node's primary range; drop the frame equal to it.
6187                if name != p.name || raw.start > p.start || raw.end < p.end {
6188                    continue;
6189                }
6190                if raw.start == p.start && raw.end == p.end {
6191                    continue;
6192                }
6193            }
6194            if let Some(last) = out.last() {
6195                if last.name == name && last.start == raw.start && last.end == raw.end {
6196                    continue;
6197                }
6198            }
6199            out.push(PortableSourceSpan {
6200                name,
6201                start: raw.start,
6202                end: raw.end,
6203                role: 1,
6204            });
6205        }
6206        out
6207    }
6208
6209    /// HOOK 8 (`begin_token_list`, after the input-stack push): set the new
6210    /// level's BASELINE. A macro body (`t == macro`) adopts the call-site span
6211    /// captured at `macro_call`; every other level keeps the parent's span (the
6212    /// generated push already copied it into `curinput`, so that is free).
6213    pub(crate) unsafe fn src_begin_token_list(engine: *mut Self, t: quarterword) {
6214        let Some(engine) = engine.as_mut() else {
6215            return;
6216        };
6217        if !engine.state.source_tracking {
6218            return;
6219        }
6220        // `macro` token type is 6 in this build's (XeTeX) numbering.
6221        if t as i32 == 6 {
6222            let call = engine.state.pending_call_span;
6223            if call != 0 {
6224                engine.state.curinput.spanfield = call;
6225            }
6226            engine.state.pending_call_span = 0;
6227            // HOOK 8a: push this macro invocation as an enclosing-construct frame
6228            // (popped at the matching end_token_list, HOOK 8b). Nodes the body
6229            // allocates record it on `node_stack`.
6230            engine.src_stack_push_span(call);
6231        }
6232    }
6233
6234    /// HOOK 12a (`\let`/`\futurelet` 2-token look-ahead, `prefixed_command`
6235    /// case `let` with `n != normal`, right after `q := cur_tok`): capture
6236    /// token A's (the first peeked token, held in `q`) OWN origin, frozen in
6237    /// `src_tok_span` by the `get_token` that just read it.
6238    ///
6239    /// tex.web: `get_token; q:=cur_tok; get_token; back_input; cur_tok:=q;
6240    /// back_input;`. The SECOND `get_token` (reading token B) overwrites
6241    /// `src_tok_span` with B's own origin; `q := cur_tok`/`cur_tok := q` are
6242    /// plain register copies that never re-freeze it. So without this capture
6243    /// (paired with [`Self::src_restore_tok_span`] right before the SECOND
6244    /// `back_input`), that `back_input` -- which re-emits token A -- fires
6245    /// with `src_tok_span` still pointing at B, stamping A's backed-up cell
6246    /// with B's origin instead of its own. `\@ifnextchar` (built on
6247    /// `\futurelet`) hits this in `\@tabularcr`'s `array`/`tabular`
6248    /// row-boundary check and in `\sqrt`'s degree scan, where token A is the
6249    /// token right after the command (e.g. the `{` of a degree-less
6250    /// `\sqrt{..}`) and B is unrelated look-ahead content.
6251    pub(crate) unsafe fn src_capture_tok_span(engine: *mut Self) -> u32 {
6252        engine.as_ref().map_or(0, |engine| engine.state.src_tok_span)
6253    }
6254
6255    /// HOOK 12b: the restore half of [`Self::src_capture_tok_span`].
6256    pub(crate) unsafe fn src_restore_tok_span(engine: *mut Self, saved: u32) {
6257        if let Some(engine) = engine.as_mut() {
6258            if engine.state.source_tracking {
6259                engine.state.src_tok_span = saved;
6260            }
6261        }
6262    }
6263
6264    /// HOOK 9a (`macro_call` entry): stash the invoking control sequence's
6265    /// origin so [`Self::src_macro_set_pending`] can build the whole-invocation
6266    /// span after the arguments are scanned.
6267    pub(crate) unsafe fn src_macro_begin(engine: *mut Self) {
6268        let Some(engine) = engine.as_mut() else {
6269            return;
6270        };
6271        if !engine.state.source_tracking {
6272            return;
6273        }
6274        let span = engine.state.curinput.spanfield;
6275        engine.state.src_call_span = span;
6276        engine.state.src_call_state = engine.state.curinput.statefield as integer;
6277        engine.state.src_call_index = engine.state.curinput.indexfield as integer;
6278        engine.state.src_call_name = engine.state.curinput.namefield as strnumber;
6279        engine.state.src_call_argspan = 0;
6280        // Capture the enclosing user command NOW (before this macro reads its
6281        // arguments, so a construct lexed while scanning the args does not become
6282        // the anchor). The buffer-branch baseline anchors its START here.
6283        engine.state.src_call_user_span = engine.state.src_user_cmd_span;
6284        engine.state.src_call_start = if span != 0 {
6285            engine
6286                .state
6287                .src_spans
6288                .get((span - 1) as usize)
6289                .map(|s| s.start)
6290                .unwrap_or(0)
6291        } else {
6292            0
6293        };
6294        // A macro whose cs was REPLAYED at PARAMETER level (`state == 0 && index < 3`,
6295        // span in the user fragment) is genuinely user-typed ARGUMENT content — e.g. a
6296        // nested `\sqrt{..}` replayed inside a degree-form radicand. Record it as the
6297        // anchor command (consumed ONLY by `src_construct_anchor`, never the arg-hull).
6298        // The gate is `< 3` (parameter/template levels) rather than `< 5`
6299        // (backed_up/inserted too): this hook only needs to see the macro CALL itself
6300        // (e.g. the inner `\sqrt`), which is read at parameter level; widening it to
6301        // also match backed_up/inserted reads (e.g. a `\futurelet`-peeked helper token)
6302        // would let unrelated look-ahead plumbing overwrite a real anchor. Macro BODIES
6303        // (index 6) are excluded for the usual definition-vs-invocation-site reason.
6304        if engine.state.src_call_state == 0 && engine.state.src_call_index < 3 && span != 0 {
6305            if let Some(raw) = engine.state.src_spans.get((span - 1) as usize).copied() {
6306                if raw.name == engine.state.src_primary_name {
6307                    engine.state.src_anchor_cmd = span;
6308                }
6309            }
6310        }
6311    }
6312
6313    /// Convex hull (same source) of the invoking cs token span (`src_call_span`),
6314    /// the scanned argument token spans (`pstack[0..n]`, each a token list walked to
6315    /// its end), and the last consumed argument span (`src_call_argspan`, the final
6316    /// `}`). Returns the interned role-1 hull, or `0` if nothing was found. This is
6317    /// the `\frac{q}{2}` -> "\frac{q}{2}" recovery for a token-list-replayed macro,
6318    /// gated to the cs token's OWN source (not the replay level's `namefield`).
6319    unsafe fn src_arg_hull(self: &mut Self, n: integer) -> SrcId {
6320        let mut lo = u32::MAX;
6321        let mut hi = 0u32;
6322        let mut found = false;
6323        let mut name: strnumber = self.state.src_primary_name;
6324        if self.state.src_call_span != 0 {
6325            if let Some(raw) = self
6326                .state
6327                .src_spans
6328                .get((self.state.src_call_span - 1) as usize)
6329            {
6330                name = raw.name;
6331                lo = raw.start;
6332                hi = raw.end;
6333                found = true;
6334            }
6335        }
6336        // Forward-only gate: when seeded from a known command span, only union args
6337        // that fall AT OR AFTER the command start (the macro's own `cmd{..}` region).
6338        // This keeps `\def\foo{\frac{1}{2}}\foo` mapping its bar to "\foo": the inner
6339        // `\frac`'s args sit at the `\def` site, BEFORE the `\foo` invocation the bar
6340        // inherited, so they are excluded rather than merged into a span straddling
6341        // the definition and the call. When there is no command span (a library
6342        // helper), the gate is open (`0`) and the first arg seeds the hull.
6343        let cmd_start = if found { lo } else { 0 };
6344        let zmem = self.state.zmem;
6345        let lo_b = self.state.memmin;
6346        let hi_b = self.state.memmax;
6347        for i in 0..n.max(0) {
6348            let mut p = self.state.pstack[i as usize];
6349            let mut guard = 0i32;
6350            while p >= lo_b && p <= hi_b && p as i64 != -(268435455 as i64) {
6351                guard += 1;
6352                if guard > 100000 {
6353                    break;
6354                }
6355                let id = self.state.node_src.get_copy(p as usize);
6356                if id != 0 {
6357                    if let Some(raw) = self.state.src_spans.get((id - 1) as usize) {
6358                        if raw.name == name && raw.start >= cmd_start {
6359                            if found {
6360                                lo = lo.min(raw.start);
6361                                hi = hi.max(raw.end);
6362                            } else {
6363                                lo = raw.start;
6364                                hi = raw.end;
6365                                found = true;
6366                            }
6367                        }
6368                    }
6369                }
6370                p = (*zmem.offset(p as isize)).hh.v.RH;
6371            }
6372        }
6373        // Extend to the last consumed argument token (the closing `}` of the final
6374        // brace group) so the construct includes its trailing delimiter. Two tracked
6375        // sources: `src_call_argspan` (captured at the first `end_token_list` pop)
6376        // and the live `curinput.spanfield` (the last token read while matching the
6377        // args). The latter recovers the trailing `}` for a `\mathchoice`-replayed
6378        // robust `\frac␣` where the pop-order leaves `src_call_argspan` stale.
6379        for cand in [self.state.src_call_argspan, self.state.curinput.spanfield] {
6380            if cand == 0 {
6381                continue;
6382            }
6383            if let Some(raw) = self.state.src_spans.get((cand - 1) as usize) {
6384                if raw.name == name && raw.start >= cmd_start {
6385                    if found {
6386                        lo = lo.min(raw.start);
6387                        hi = hi.max(raw.end);
6388                    } else {
6389                        lo = raw.start;
6390                        hi = raw.end;
6391                        found = true;
6392                    }
6393                }
6394            }
6395        }
6396        if found && hi > lo {
6397            self.intern_span_raw(name, lo, hi, 1)
6398        } else {
6399            0
6400        }
6401    }
6402
6403    /// HOOK 9b (`macro_call`, right before the body `begin_token_list`): publish
6404    /// the call-site span the body level will inherit, LEVEL-TYPED by where the
6405    /// invoking control sequence was read from (captured at `src_macro_begin`,
6406    /// before the exhausted-list pop loop perturbs `curinput`):
6407    ///
6408    /// * Genuine macro ARGUMENT replay (entry token type `parameter` = 0, e.g. a
6409    ///   `\frac{q}{2}` typed inside another macro's `{...}` or replayed by
6410    ///   `\mathchoice`): the buffer `loc` has popped past this invocation, so its
6411    ///   `[cs,loc)` would overshoot into the enclosing macro. Recover the extent as
6412    ///   the same-source convex hull of the cs token and the scanned argument token
6413    ///   spans (`pstack[0..n]`) -> the user's own `\frac{q}{2}`, not `\frac`.
6414    /// * Otherwise CURRENT buffer (`statefield != 0`, the common direct call and the
6415    ///   backed-up math-probe whose args were scanned from the buffer): the whole
6416    ///   `[cs_start, loc)` invocation (loc past the args) -> `\frac{a}{b}`.
6417    /// * Otherwise a still-live token list (a macro body / every-list): the
6418    ///   inherited baseline, so body-internal calls collapse to the outer
6419    ///   invocation (`\def\foo{\frac..}\foo` -> `\foo`).
6420    pub(crate) unsafe fn src_macro_set_pending(engine: *mut Self, n: integer) {
6421        let Some(engine) = engine.as_mut() else {
6422            return;
6423        };
6424        if !engine.state.source_tracking {
6425            return;
6426        }
6427        if engine.state.src_call_index == 0 && engine.state.src_call_span == 0 {
6428            // A LIBRARY HELPER macro -- one with no in-fragment cs span, e.g. `\@sqrt`
6429            // invoked by `\sqrt`, or `\root`/`\mathchoice` machinery -- is NOT a user
6430            // construct. Its in-fragment ARGUMENTS (`[3]{x}`) are the OUTER command's
6431            // args, not a new construct boundary. So it must NOT establish a new
6432            // baseline from those args (which is why the surd was landing on `[3]{x}`);
6433            // inherit the parent baseline (the transitively-propagated user command,
6434            // e.g. `\sqrt`) by leaving the level to inherit on push. The arg CONTENT
6435            // (3, x) still keeps its own cell spans. Only a USER macro (in-fragment cs,
6436            // handled below) defines its own `[cs..args]` hull.
6437            engine.state.pending_call_span = 0;
6438        } else if engine.state.src_call_index == 0 {
6439            // ARGUMENT / backed-up replay (`\frac{q}{2}` typed inside another macro's
6440            // `{..}`): recover its own `[cs..args]` hull from the scanned args.
6441            let hull = engine.src_arg_hull(n);
6442            engine.state.pending_call_span = if hull != 0 {
6443                hull
6444            } else {
6445                engine.state.src_call_span
6446            };
6447        } else if engine.state.curinput.statefield as i32 != 0 {
6448            // BUFFER invocation: the whole `[cmd_start, loc)` extent (loc is past the
6449            // args). Anchor the START to the ENCLOSING USER COMMAND (captured at
6450            // `src_macro_begin`), not this macro's own `src_call_span`: a kernel
6451            // helper reached through a user command's expansion (e.g. `\@sqrt`,
6452            // `\root`, `\mathpalette` for `\sqrt[3]{x}`) is invoked from the buffer
6453            // with a borrowed cs span (the `\@ifnextchar` peeked `[`), which would
6454            // drop the `\sqrt` origin. The user command's start transitively anchors
6455            // every helper to the command the user typed. For a directly-typed user
6456            // macro the user command IS this macro, so the start is unchanged.
6457            let end = engine.src_buf_offset(engine.state.curinput.locfield as integer);
6458            let name = engine.state.curinput.namefield as strnumber;
6459            let mut start = engine.state.src_call_start.min(end);
6460            let user = engine.state.src_call_user_span;
6461            if user != 0 {
6462                if let Some(raw) = engine.state.src_spans.get((user - 1) as usize) {
6463                    if raw.name == name && raw.start <= start {
6464                        start = raw.start;
6465                    }
6466                }
6467            }
6468            let id = engine.intern_span_raw(name, start, end, 1);
6469            engine.state.pending_call_span = id;
6470        } else if n > 0 {
6471            // TOKEN-LIST macro BODY (`index >= 5`) that consumed args. The inherited
6472            // baseline is the user invocation that produced this body (`\frac` ->
6473            // `\protect\frac␣`: `\frac␣`'s baseline is the user's `\frac`). UNION it
6474            // with the macro's OWN trailing args so a `\frac{q}{2}` the user typed as
6475            // a `\mathchoice`-replayed radicand recovers "\frac{q}{2}". `src_arg_hull`
6476            // only unions args that fall AFTER the command start (the macro's own
6477            // `cmd{..}` region), so a `\def\foo{\frac{1}{2}}\foo` body -- whose inner
6478            // `\frac` args precede the `\foo` invocation -- excludes them and keeps
6479            // the bar -> "\foo". The join is the command and its OWN tracked args
6480            // (the macro-call chain), never an unrelated adjacent range.
6481            let hull = engine.src_arg_hull(n);
6482            engine.state.pending_call_span = if hull != 0 {
6483                hull
6484            } else {
6485                engine.state.src_call_span
6486            };
6487        } else {
6488            engine.state.pending_call_span = engine.state.src_call_span;
6489        }
6490    }
6491
6492    /// HOOK 11 (`main_control` main loop, the `is_hyph` seam): record the source
6493    /// span of the input char just appended to `nativetext`, one entry per UTF-16
6494    /// code unit (a surrogate-pair char fills both units with the same id). This
6495    /// runs once per collected char while `curinput.spanfield` still points at it.
6496    /// The run begins when `nativelen` was 0 before this char (`prev == 0`), so the
6497    /// table self-resets at the head of each run without a second anchor; a later
6498    /// re-measure of an unrelated run is caught by the length guard in
6499    /// [`Self::src_resolve_native_glyphs`]. No-op when tracking off.
6500    pub(crate) unsafe fn src_native_run_push(engine: *mut Self) {
6501        let Some(engine) = engine.as_mut() else {
6502            return;
6503        };
6504        if !engine.state.source_tracking {
6505            return;
6506        }
6507        let nativelen = engine.state.nativelen.max(0) as usize;
6508        // The engine appends 2 UTF-16 units for a supplementary scalar, else 1
6509        // (mirrors the `curchr > 65535` branch just above this seam).
6510        let units = if engine.state.curchr as i64 > 65535 { 2 } else { 1 };
6511        let prev = nativelen.saturating_sub(units);
6512        if prev == 0 {
6513            engine.state.src_native_offsets.clear();
6514        }
6515        // Trim any stale overshoot (defensive; cleared at `prev == 0`), then fill
6516        // this char's units with its tracked span id.
6517        if engine.state.src_native_offsets.len() > prev {
6518            engine.state.src_native_offsets.truncate(prev);
6519        }
6520        let id = engine.state.curinput.spanfield;
6521        while engine.state.src_native_offsets.len() < nativelen {
6522            engine.state.src_native_offsets.push(id);
6523        }
6524    }
6525
6526    /// Map each shaped glyph of a native run to the EXACT source span of the input
6527    /// char(s) under its shaper cluster, setting `src_start`/`src_end` (in the
6528    /// node's `primary_source.source` coordinates). The shaper reports each glyph's
6529    /// `cluster_start` as a UTF-8 BYTE offset into `String::from_utf16_lossy(text)`
6530    /// (the engine hands the shaper UTF-16 `nativetext`, the adapter converts to a
6531    /// Rust `&str`, and rustybuzz clusters are UTF-8 byte indices). This rebuilds
6532    /// that string, maps each UTF-8 byte to its char's tracked span via a UTF-16
6533    /// code-unit cursor aligned with `src_native_offsets`, then for each glyph
6534    /// unions the (same-source) spans across its cluster's byte extent — the extent
6535    /// being `[cluster_start, next distinct cluster_start)`, so a ligature glyph
6536    /// covers the contiguous union of its source chars. CONSUME-ONCE: the offsets
6537    /// table is taken here, so a re-measure (reconstituted/hyphenated run, or an
6538    /// unrelated node) finds it empty and leaves clusters unmapped rather than
6539    /// mis-mapping. Zero heuristics: no text==source linear assumption — every span
6540    /// comes from a per-char tracked id. No-op when tracking off.
6541    pub(crate) unsafe fn src_resolve_native_glyphs(
6542        engine: *mut Self,
6543        node: halfword,
6544        text: &[u16],
6545        glyphs: &mut [PortableNativeGlyph],
6546    ) {
6547        let Some(engine) = engine.as_mut() else {
6548            return;
6549        };
6550        if !engine.state.source_tracking {
6551            return;
6552        }
6553        let offsets = core::mem::take(&mut engine.state.src_native_offsets);
6554        // Only resolve our freshly-collected run: the per-code-unit table must
6555        // exactly cover this node's text. Any mismatch => leave glyphs unmapped.
6556        if text.is_empty() || offsets.len() != text.len() {
6557            return;
6558        }
6559        if node < 0 {
6560            return;
6561        }
6562        // The cluster span lives in the node's source coordinates, so only chars
6563        // from the node's own source file may contribute (no cross-source span).
6564        let node_id = engine.state.node_src.get_copy(node as usize);
6565        if node_id == 0 {
6566            return;
6567        }
6568        let node_name = match engine.state.src_spans.get((node_id - 1) as usize) {
6569            Some(raw) => raw.name,
6570            None => return,
6571        };
6572        // Rebuild the exact UTF-8 string the shaper saw and tag each byte with the
6573        // source id of the char it belongs to (UTF-16 cursor aligns to `offsets`).
6574        // The shaper reports `cluster_start` as a UTF-8 BYTE offset into this
6575        // string (verified empirically: a supplementary-plane run char lands its
6576        // following glyph at the UTF-8 byte offset, not the UTF-16 code-unit one).
6577        let s = String::from_utf16_lossy(text);
6578        let n = s.len();
6579        let mut byte_src = vec![0u32; n];
6580        let mut u16i = 0usize;
6581        for (b, ch) in s.char_indices() {
6582            let id = offsets.get(u16i).copied().unwrap_or(0);
6583            let upper = (b + ch.len_utf8()).min(n);
6584            for slot in byte_src.iter_mut().take(upper).skip(b) {
6585                *slot = id;
6586            }
6587            u16i += ch.len_utf16();
6588        }
6589        for gi in 0..glyphs.len() {
6590            let cs = (glyphs[gi].cluster_start as usize).min(n);
6591            // Monotone (LTR) clusters: this cluster ends at the next strictly
6592            // greater `cluster_start`, else at end-of-text. Glyphs sharing `cs`
6593            // (a decomposed char) resolve to the same char span.
6594            let mut ce = n;
6595            for g2 in glyphs.iter() {
6596                let c2 = (g2.cluster_start as usize).min(n);
6597                if c2 > cs && c2 < ce {
6598                    ce = c2;
6599                }
6600            }
6601            let mut lo = u32::MAX;
6602            let mut hi = 0u32;
6603            let mut found = false;
6604            for &id in byte_src.iter().take(ce).skip(cs) {
6605                if id == 0 {
6606                    continue;
6607                }
6608                let Some(raw) = engine.state.src_spans.get((id - 1) as usize) else {
6609                    continue;
6610                };
6611                if raw.name != node_name {
6612                    continue;
6613                }
6614                lo = lo.min(raw.start);
6615                hi = hi.max(raw.end);
6616                found = true;
6617            }
6618            if found && hi > lo {
6619                glyphs[gi].src_start = lo;
6620                glyphs[gi].src_end = hi;
6621            }
6622        }
6623    }
6624
6625    /// All interned spans, resolved to display form. Restored from the stubbed
6626    /// `&[]`: exposes the populated table for inspection / tests.
6627    pub fn input_source_spans(&self) -> Vec<PortableSourceSpan> {
6628        self.state
6629            .src_spans
6630            .iter()
6631            .filter_map(|raw| {
6632                let name = unsafe { self.pool_string(raw.name) }?;
6633                Some(PortableSourceSpan {
6634                    name,
6635                    start: raw.start,
6636                    end: raw.end,
6637                    role: raw.role,
6638                })
6639            })
6640            .collect()
6641    }
6642
6643    pub(crate) unsafe fn resolve_font_handle(
6644        engine: *mut PortableTexEngine<'_>,
6645        name: *mut ASCIIcode,
6646        size: integer,
6647    ) -> FontHandle {
6648        let Some(engine) = engine.as_mut() else {
6649            return 0;
6650        };
6651        let name_len = engine.state.namelength.max(0) as usize;
6652        let name = if name.is_null() || name_len == 0 {
6653            &[]
6654        } else {
6655            core::slice::from_raw_parts(name as *const i32, name_len)
6656        };
6657        engine.fonts.resolve_font_handle(name, size).unwrap_or(0)
6658    }
6659
6660    pub(crate) unsafe fn measure_font_metrics(
6661        engine: *mut PortableTexEngine<'_>,
6662        font: FontHandle,
6663        ascent: *mut integer,
6664        descent: *mut integer,
6665        xheight: *mut integer,
6666        capheight: *mut integer,
6667        slant: *mut integer,
6668    ) {
6669        let metrics = engine
6670            .as_mut()
6671            .map(|engine| engine.fonts.font_metrics(font))
6672            .unwrap_or_default();
6673        if !ascent.is_null() {
6674            *ascent = metrics.ascent;
6675        }
6676        if !descent.is_null() {
6677            *descent = metrics.descent;
6678        }
6679        if !xheight.is_null() {
6680            *xheight = metrics.xheight;
6681        }
6682        if !capheight.is_null() {
6683            *capheight = metrics.capheight;
6684        }
6685        if !slant.is_null() {
6686            *slant = metrics.slant;
6687        }
6688    }
6689
6690    pub(crate) unsafe fn get_native_mathsy_parameter(
6691        engine: *mut PortableTexEngine<'resources>,
6692        font: integer,
6693        param: integer,
6694    ) -> integer {
6695        let Some(engine) = engine.as_mut() else {
6696            return 0;
6697        };
6698        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
6699            return 0;
6700        };
6701        engine.fonts.math_symbol_parameter(font_handle, param)
6702    }
6703
6704    pub(crate) unsafe fn get_native_mathex_parameter(
6705        engine: *mut PortableTexEngine<'resources>,
6706        font: integer,
6707        param: integer,
6708    ) -> integer {
6709        let Some(engine) = engine.as_mut() else {
6710            return 0;
6711        };
6712        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
6713            return 0;
6714        };
6715        engine.fonts.math_extension_parameter(font_handle, param)
6716    }
6717
6718    /// Italic correction for a native OpenType-math glyph, in scaled points.
6719    ///
6720    /// Mirrors XeTeX's `get_ot_math_ital_corr` (`XeTeXOTMath.cpp`): it reads the
6721    /// glyph's `MathItalicsCorrectionInfo` value and scales it through the same
6722    /// `unitsToPoints` + `D2Fix` path as every other font metric. The math list
6723    /// builder (`mlist_to_hlist`) appends a `\kern` of this size after an ord
6724    /// glyph when there is no following subscript, so a correct nonzero value
6725    /// produces the exact italic-correction kern real XeTeX emits.
6726    pub(crate) unsafe fn get_ot_math_ital_corr(
6727        engine: *mut PortableTexEngine<'resources>,
6728        font: integer,
6729        glyph: integer,
6730    ) -> integer {
6731        let Some(engine) = engine.as_mut() else {
6732            return 0;
6733        };
6734        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
6735            return 0;
6736        };
6737        engine.fonts.math_glyph_italic_correction(font_handle, glyph)
6738    }
6739
6740    /// The `v`-th larger MATH glyph variant of `g` (horizontal or vertical),
6741    /// writing its scaled advance to `*adv`. Mirrors XeTeX's
6742    /// `get_ot_math_variant`: returns the glyph unchanged with `*adv = -1` when
6743    /// there is no such variant.
6744    pub(crate) unsafe fn get_ot_math_variant(
6745        engine: *mut PortableTexEngine<'resources>,
6746        f_0: integer,
6747        g_0: integer,
6748        v: integer,
6749        adv: *mut integer,
6750        horiz: integer,
6751    ) -> integer {
6752        if !adv.is_null() {
6753            *adv = -1;
6754        }
6755        let Some(engine) = engine.as_mut() else {
6756            return g_0;
6757        };
6758        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6759            return g_0;
6760        };
6761        let index = u16::try_from(v).unwrap_or(u16::MAX);
6762        match engine
6763            .fonts
6764            .math_glyph_variant(font_handle, g_0, index, horiz != 0)
6765        {
6766            Some(variant) => {
6767                if !adv.is_null() {
6768                    *adv = variant.advance;
6769                }
6770                variant.glyph
6771            }
6772            None => g_0,
6773        }
6774    }
6775
6776    /// Build a heap-owned [`GlyphAssembly`] for the stretchable glyph `g` and
6777    /// hand ownership to the engine as a `void*` (reclaimed by
6778    /// [`free_ot_assembly`]). Returns null when there is no assembly. Mirrors
6779    /// XeTeX's `get_ot_assembly_ptr`, but allocates a safe Rust struct with
6780    /// `Box::into_raw` instead of a libc C struct.
6781    pub(crate) unsafe fn get_ot_assembly_ptr(
6782        engine: *mut PortableTexEngine<'resources>,
6783        f_0: integer,
6784        g_0: integer,
6785        horiz: integer,
6786    ) -> voidpointer {
6787        let Some(engine) = engine.as_mut() else {
6788            return nullptr;
6789        };
6790        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6791            return nullptr;
6792        };
6793        let parts = engine
6794            .fonts
6795            .math_glyph_assembly(font_handle, g_0, horiz != 0);
6796        if parts.is_empty() {
6797            return nullptr;
6798        }
6799        let assembly = Box::new(GlyphAssembly { parts });
6800        Box::into_raw(assembly) as voidpointer
6801    }
6802
6803    /// Minimum connector overlap between assembly parts for font `f`, in scaled
6804    /// points (`ot_min_connector_overlap`).
6805    pub(crate) unsafe fn ot_min_connector_overlap(
6806        engine: *mut PortableTexEngine<'resources>,
6807        f_0: integer,
6808    ) -> integer {
6809        let Some(engine) = engine.as_mut() else {
6810            return 0;
6811        };
6812        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6813            return 0;
6814        };
6815        engine.fonts.math_min_connector_overlap(font_handle)
6816    }
6817
6818    /// MATH glyph height (scaled points) via the font platform.
6819    unsafe fn math_glyph_height(
6820        engine: &mut PortableTexEngine<'resources>,
6821        f_0: integer,
6822        g_0: integer,
6823    ) -> scaled {
6824        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6825            return 0;
6826        };
6827        let Ok(glyph) = u16::try_from(g_0) else {
6828            return 0;
6829        };
6830        engine.fonts.measure_native_glyph(font_handle, glyph, true).height
6831    }
6832
6833    /// MATH glyph depth (scaled points) via the font platform.
6834    unsafe fn math_glyph_depth(
6835        engine: &mut PortableTexEngine<'resources>,
6836        f_0: integer,
6837        g_0: integer,
6838    ) -> scaled {
6839        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6840            return 0;
6841        };
6842        let Ok(glyph) = u16::try_from(g_0) else {
6843            return 0;
6844        };
6845        engine.fonts.measure_native_glyph(font_handle, glyph, true).depth
6846    }
6847
6848    /// Evaluate one MATH kern corner of glyph `g` at `correction_height` (font
6849    /// design units), in raw font units, via the font platform.
6850    unsafe fn math_kern_at(
6851        engine: &mut PortableTexEngine<'resources>,
6852        f_0: integer,
6853        g_0: integer,
6854        height: integer,
6855        corner: PortableMathKernCorner,
6856    ) -> integer {
6857        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6858            return 0;
6859        };
6860        engine.fonts.math_kern_at(font_handle, g_0, corner, height)
6861    }
6862
6863    /// Superscript/subscript cut-in kerning between base glyph `g` in font `f`
6864    /// and script glyph `sg` in font `sf`. Faithful port of XeTeX's
6865    /// `get_ot_math_kern` (`XeTeXOTMath.cpp`): all intermediate arithmetic runs
6866    /// in base-glyph units with a `scale_factor = sf_size / f_size`, the "max not
6867    /// min" corner choice is preserved, and the result is scaled to scaled points
6868    /// through the same `unitsToPoints` + `D2Fix` path.
6869    pub(crate) unsafe fn get_ot_math_kern(
6870        engine: *mut PortableTexEngine<'resources>,
6871        f_0: integer,
6872        g_0: integer,
6873        sf: integer,
6874        sg: integer,
6875        cmd: integer,
6876        shift_scaled: integer,
6877    ) -> integer {
6878        const SUP_CMD: integer = 0;
6879        const SUB_CMD: integer = 1;
6880        let Some(engine) = engine.as_mut() else {
6881            return 0;
6882        };
6883        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
6884            return 0;
6885        };
6886        let Some(sfont_handle) = Self::font_handle_for_number(engine, sf) else {
6887            return 0;
6888        };
6889
6890        // Glyph height/depth in points (sp -> pt) for the base and script glyphs.
6891        let g_height_pt = Self::math_glyph_height(engine, f_0, g_0) as f32 / 65536.0;
6892        let g_depth_pt = Self::math_glyph_depth(engine, f_0, g_0) as f32 / 65536.0;
6893        let sg_height_pt = Self::math_glyph_height(engine, sf, sg) as f32 / 65536.0;
6894        let sg_depth_pt = Self::math_glyph_depth(engine, sf, sg) as f32 / 65536.0;
6895
6896        // Convert everything to base-glyph units.
6897        let g_height = engine.fonts.math_points_to_units(font_handle, g_height_pt) as integer;
6898        let g_depth = engine.fonts.math_points_to_units(font_handle, g_depth_pt) as integer;
6899        let sg_height = engine
6900            .fonts
6901            .math_points_to_units(sfont_handle, sg_height_pt) as integer;
6902        let sg_depth = engine
6903            .fonts
6904            .math_points_to_units(sfont_handle, sg_depth_pt) as integer;
6905        let shift_pt = shift_scaled as f32 / 65536.0;
6906        let shift = engine.fonts.math_points_to_units(font_handle, shift_pt) as integer;
6907
6908        let f_size = engine.fonts.math_point_size(font_handle);
6909        let sf_size = engine.fonts.math_point_size(sfont_handle);
6910        if f_size == 0.0 {
6911            return 0;
6912        }
6913        let scale_factor = sf_size / f_size;
6914
6915        let mut rval: integer;
6916        if cmd == SUP_CMD {
6917            let kern = Self::math_kern_at(
6918                engine,
6919                f_0,
6920                g_0,
6921                shift - (scale_factor * sg_depth as f32) as integer,
6922                PortableMathKernCorner::TopRight,
6923            );
6924            let skern =
6925                Self::math_kern_at(engine, sf, sg, -sg_depth, PortableMathKernCorner::BottomLeft);
6926            let top_kern = kern + (scale_factor * skern as f32) as integer;
6927
6928            let kern =
6929                Self::math_kern_at(engine, f_0, g_0, g_height, PortableMathKernCorner::TopRight);
6930            let skern = Self::math_kern_at(
6931                engine,
6932                sf,
6933                sg,
6934                ((g_height - shift) as f32 / scale_factor) as integer,
6935                PortableMathKernCorner::BottomLeft,
6936            );
6937            let bot_kern = kern + (scale_factor * skern as f32) as integer;
6938
6939            rval = if top_kern > bot_kern { top_kern } else { bot_kern };
6940        } else if cmd == SUB_CMD {
6941            let kern = Self::math_kern_at(
6942                engine,
6943                f_0,
6944                g_0,
6945                (scale_factor * sg_height as f32) as integer - shift,
6946                PortableMathKernCorner::BottomRight,
6947            );
6948            let skern =
6949                Self::math_kern_at(engine, sf, sg, sg_height, PortableMathKernCorner::TopLeft);
6950            let top_kern = kern + (scale_factor * skern as f32) as integer;
6951
6952            let kern =
6953                Self::math_kern_at(engine, f_0, g_0, -g_depth, PortableMathKernCorner::BottomRight);
6954            let skern = Self::math_kern_at(
6955                engine,
6956                sf,
6957                sg,
6958                ((shift - g_depth) as f32 / scale_factor) as integer,
6959                PortableMathKernCorner::TopLeft,
6960            );
6961            let bot_kern = kern + (scale_factor * skern as f32) as integer;
6962
6963            rval = if top_kern > bot_kern { top_kern } else { bot_kern };
6964        } else {
6965            return 0;
6966        }
6967
6968        rval = engine.fonts.math_units_to_scaled(font_handle, rval);
6969        rval
6970    }
6971
6972    pub(crate) unsafe fn get_native_word_cp(
6973        engine: *mut PortableTexEngine<'resources>,
6974        node: voidpointer,
6975        side: integer,
6976    ) -> integer {
6977        let Some(engine) = engine.as_mut() else {
6978            return 0;
6979        };
6980        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
6981            return 0;
6982        };
6983        let Some(info) = engine.native_glyph_infos.get(&node_index) else {
6984            return 0;
6985        };
6986        let glyph = if side == 0 {
6987            info.glyphs.first()
6988        } else {
6989            info.glyphs.last()
6990        };
6991        let Some(glyph) = glyph else {
6992            return 0;
6993        };
6994        let font = Self::native_node_font(engine.state.zmem, node_index);
6995        Self::character_protrusion(engine, font, u32::from(glyph.glyph_id), side)
6996    }
6997
6998    pub(crate) unsafe fn get_native_glyph(
6999        engine: *mut PortableTexEngine<'resources>,
7000        node: voidpointer,
7001        index: u32,
7002    ) -> uint16_t {
7003        let Some(engine) = engine.as_mut() else {
7004            return 0;
7005        };
7006        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7007            return 0;
7008        };
7009        engine
7010            .native_glyph_infos
7011            .get(&node_index)
7012            .and_then(|info| info.glyphs.get(index as usize))
7013            .map_or(0, |glyph| glyph.glyph_id)
7014    }
7015
7016    pub(crate) unsafe fn get_character_protrusion(
7017        engine: *mut PortableTexEngine<'_>,
7018        font: integer,
7019        code: u32,
7020        side: integer,
7021    ) -> integer {
7022        engine
7023            .as_mut()
7024            .map_or(0, |engine| Self::character_protrusion(engine, font, code, side))
7025    }
7026
7027    pub(crate) fn character_protrusion(
7028        engine: &PortableTexEngine<'_>,
7029        font: integer,
7030        code: u32,
7031        side: integer,
7032    ) -> integer {
7033        engine
7034            .character_protrusions
7035            .get(&(font, code, side))
7036            .copied()
7037            .unwrap_or(0)
7038    }
7039
7040    pub(crate) fn set_character_protrusion(
7041        engine: &mut PortableTexEngine<'_>,
7042        font: integer,
7043        code: u32,
7044        side: integer,
7045        value: integer,
7046    ) {
7047        let key = (font, code, side);
7048        if value == 0 {
7049            engine.character_protrusions.remove(&key);
7050        } else {
7051            engine.character_protrusions.insert(key, value);
7052        }
7053    }
7054
7055    pub(crate) unsafe fn get_opentype_math_constant(
7056        engine: *mut PortableTexEngine<'resources>,
7057        font: integer,
7058        constant: integer,
7059    ) -> integer {
7060        let Some(engine) = engine.as_mut() else {
7061            return 0;
7062        };
7063        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7064            return 0;
7065        };
7066        engine.fonts.opentype_math_constant(font_handle, constant)
7067    }
7068
7069    pub(crate) unsafe fn get_opentype_math_accent_position(
7070        engine: *mut PortableTexEngine<'resources>,
7071        font: integer,
7072        glyph: integer,
7073    ) -> integer {
7074        let Some(engine) = engine.as_mut() else {
7075            return 0;
7076        };
7077        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7078            return 0;
7079        };
7080        engine.fonts.opentype_math_accent_position(font_handle, glyph)
7081    }
7082
7083    pub(crate) unsafe fn map_char_to_glyph(
7084        engine: *mut PortableTexEngine<'resources>,
7085        font: integer,
7086        ch: integer,
7087    ) -> integer {
7088        let Some(engine) = engine.as_mut() else {
7089            return 0;
7090        };
7091        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7092            return 0;
7093        };
7094        engine.fonts.map_char_to_glyph(font_handle, ch)
7095    }
7096
7097    pub(crate) unsafe fn map_glyph_to_index(
7098        engine: *mut PortableTexEngine<'resources>,
7099        font: integer,
7100    ) -> integer {
7101        let Some(engine) = engine.as_mut() else {
7102            return 0;
7103        };
7104        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7105            return 0;
7106        };
7107        let Some(name) = engine.pool_string(engine.state.curname) else {
7108            return 0;
7109        };
7110        engine.fonts.map_glyph_to_index(font_handle, name.as_str())
7111    }
7112
7113    /// Boundary for the `\XeTeXOT*` / `\XeTeXcountglyphs` `last_item` primitives.
7114    /// Resolves the font number to a platform handle (mirroring
7115    /// `map_char_to_glyph`) and dispatches to the font platform's OpenType
7116    /// layout enumeration. Replaces the old `otfontget*` no-op stubs.
7117    pub(crate) unsafe fn ot_font_get(
7118        engine: *mut PortableTexEngine<'resources>,
7119        what: integer,
7120        font: integer,
7121        param1: integer,
7122        param2: integer,
7123        param3: integer,
7124    ) -> integer {
7125        let Some(engine) = engine.as_mut() else {
7126            return 0;
7127        };
7128        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7129            return 0;
7130        };
7131        engine
7132            .fonts
7133            .ot_font_get(font_handle, what, param1, param2, param3)
7134    }
7135
7136    pub(crate) unsafe fn is_opentype_math_font(
7137        engine: *mut PortableTexEngine<'_>,
7138        font: FontHandle,
7139    ) -> boolean {
7140        engine
7141            .as_mut()
7142            .map(|engine| engine.fonts.is_opentype_math_font(font) as boolean)
7143            .unwrap_or(false_0)
7144    }
7145
7146    pub(crate) unsafe fn using_opentype(
7147        engine: *mut PortableTexEngine<'_>,
7148        font: FontHandle,
7149    ) -> boolean {
7150        engine
7151            .as_mut()
7152            .map(|engine| engine.fonts.using_opentype(font) as boolean)
7153            .unwrap_or(false_0)
7154    }
7155
7156    pub(crate) unsafe fn release_font_engine(
7157        engine: *mut PortableTexEngine<'_>,
7158        font: FontHandle,
7159        type_flag: integer,
7160    ) {
7161        if let Some(engine) = engine.as_mut() {
7162            engine.fonts.release_font_handle(font, type_flag);
7163        }
7164    }
7165
7166    pub(crate) unsafe fn measure_opentype_font_metrics(
7167        engine: *mut PortableTexEngine<'_>,
7168        font: FontHandle,
7169        ascent: *mut integer,
7170        descent: *mut integer,
7171        xheight: *mut integer,
7172        capheight: *mut integer,
7173        slant: *mut integer,
7174    ) {
7175        let metrics = engine
7176            .as_mut()
7177            .map(|engine| engine.fonts.opentype_font_metrics(font))
7178            .unwrap_or_default();
7179        if !ascent.is_null() {
7180            *ascent = metrics.ascent;
7181        }
7182        if !descent.is_null() {
7183            *descent = metrics.descent;
7184        }
7185        if !xheight.is_null() {
7186            *xheight = metrics.xheight;
7187        }
7188        if !capheight.is_null() {
7189            *capheight = metrics.capheight;
7190        }
7191        if !slant.is_null() {
7192            *slant = metrics.slant;
7193        }
7194    }
7195
7196    pub(crate) unsafe fn measure_native_node(
7197        engine: *mut PortableTexEngine<'resources>,
7198        node: voidpointer,
7199        use_glyph_metrics: integer,
7200    ) {
7201        let Some(engine) = engine.as_mut() else {
7202            return;
7203        };
7204        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7205            return;
7206        };
7207        let mem = engine.state.zmem;
7208        let font_number = Self::native_node_font(mem, node_index);
7209        let Some(font_handle) = Self::font_handle_for_number(engine, font_number) else {
7210            return;
7211        };
7212        let text = Self::native_node_text(mem, node_index);
7213        let mut metrics =
7214            engine
7215                .fonts
7216                .shape_native_text(font_handle, text, use_glyph_metrics != 0);
7217        // Source tracking: map each shaped glyph back to the EXACT source span of
7218        // the input char(s) that produced its shaper cluster, via the per-code-unit
7219        // ids collected during the main-loop run (no-op when tracking off).
7220        Self::src_resolve_native_glyphs(
7221            engine as *mut PortableTexEngine<'resources>,
7222            node_index,
7223            text,
7224            metrics.glyphs.as_mut_slice(),
7225        );
7226        Self::write_native_node_metrics(
7227            mem,
7228            node_index,
7229            metrics.width,
7230            metrics.height,
7231            metrics.depth,
7232        );
7233        (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B3 =
7234            (metrics.glyphs.len().min(i32::MAX as usize) as quarterword) as u16;
7235        (*mem.offset((node_index + 5) as isize)).ptr = nullptr;
7236        engine.native_glyph_infos.insert(
7237            node_index,
7238            PortableNativeGlyphInfo {
7239                glyphs: metrics.glyphs,
7240            },
7241        );
7242    }
7243
7244    pub(crate) unsafe fn measure_native_glyph(
7245        engine: *mut PortableTexEngine<'resources>,
7246        node: voidpointer,
7247        use_glyph_metrics: integer,
7248    ) {
7249        let Some(engine) = engine.as_mut() else {
7250            return;
7251        };
7252        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7253            return;
7254        };
7255        let mem = engine.state.zmem;
7256        let font_number = Self::native_node_font(mem, node_index);
7257        let Some(font_handle) = Self::font_handle_for_number(engine, font_number) else {
7258            return;
7259        };
7260        let glyph = (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B2 as u16;
7261        let metrics = engine
7262            .fonts
7263            .measure_native_glyph(font_handle, glyph, use_glyph_metrics != 0);
7264        Self::write_native_node_metrics(
7265            mem,
7266            node_index,
7267            metrics.width,
7268            metrics.height,
7269            metrics.depth,
7270        );
7271        (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B3 = (1 as quarterword) as u16;
7272        // NOTE: a `glyph_node` is allocated with `glyph_node_size = 5` words
7273        // (indices 0..=4), but XeTeX's `native_glyph_info_ptr` macro lives at word
7274        // `node + 5` -- one past this node. In the original C engine that word
7275        // aliases adjacent `mem`, which is tolerated; here `mem` is a bounds-real
7276        // Rust array and writing `node + 5` corrupts the *next* node (it crashed
7277        // `var_delimiter`, which builds a single-glyph delimiter box this way).
7278        // The glyph info this field would point at is held authoritatively in the
7279        // engine-side `native_glyph_infos` map (keyed by node index) and the raw
7280        // `node + 5` word is never dereferenced anywhere, so the write is omitted.
7281        engine.native_glyph_infos.insert(
7282            node_index,
7283            PortableNativeGlyphInfo {
7284                glyphs: Vec::from([PortableNativeGlyph {
7285                    glyph_id: glyph,
7286                    x: 0,
7287                    y: 0,
7288                    advance: metrics.width,
7289                    cluster_start: 0,
7290                    cluster_end: 0,
7291                    src_start: 0,
7292                    src_end: 0,
7293                }]),
7294            },
7295        );
7296    }
7297
7298    pub(crate) unsafe fn znotaatfonterror(
7299        self: &mut Self,
7300        cmd: integer,
7301        c_0: integer,
7302        f_0: integer,
7303    ) -> EngineFlow<()> {
7304        self.znototfonterror(cmd, c_0, f_0)?;
7305        Ok(())
7306    }
7307
7308    pub(crate) unsafe fn znotaatgrfonterror(
7309        self: &mut Self,
7310        cmd: integer,
7311        c_0: integer,
7312        f_0: integer,
7313    ) -> EngineFlow<()> {
7314        self.znototfonterror(cmd, c_0, f_0)?;
7315        Ok(())
7316    }
7317
7318    /// Append a native glyph for `(f_0, g_0)` to the end of box `b`, growing the
7319    /// box height/depth (hlist) or width (vlist). Port of XeTeX's
7320    /// `stack_glyph_into_box` (`xetex.web`). The glyph node is `glyph_node_size`
7321    /// (5) words, measured through `measure_native_glyph`.
7322    unsafe fn stack_glyph_into_box(
7323        self: &mut Self,
7324        b: halfword,
7325        f_0: internalfontnumber,
7326        g_0: integer,
7327    ) -> EngineFlow<()> {
7328        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7329        const NULL: halfword = -(268435455 as i64) as halfword;
7330        let p = (&mut *(self as *mut PortableTexEngine<'_>)).zgetnode(5)?;
7331        (*mem.offset(p as isize)).hh.u.B0 = 8;
7332        (*mem.offset(p as isize)).hh.u.B1 = 42;
7333        (*mem.offset((p + 4) as isize)).v.QQQQ.u.B1 = (f_0 as quarterword) as u16;
7334        (*mem.offset((p + 4) as isize)).v.QQQQ.u.B2 = (g_0 as quarterword) as u16;
7335        Self::measure_native_glyph(
7336            self as *mut PortableTexEngine<'resources>,
7337            mem.offset(p as isize) as *mut memoryword as *mut (),
7338            1,
7339        );
7340        Ok(
7341            if (*mem.offset(b as isize)).hh.u.B0 as i32 == 0 {
7342                let mut q = (*mem.offset((b + 5) as isize)).hh.v.RH;
7343                if q == NULL {
7344                    (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7345                } else {
7346                    while (*mem.offset(q as isize)).hh.v.RH != NULL {
7347                        q = (*mem.offset(q as isize)).hh.v.RH;
7348                    }
7349                    (*mem.offset(q as isize)).hh.v.RH = p;
7350                    if (*mem.offset((b + 3) as isize)).u.CINT
7351                        < (*mem.offset((p + 3) as isize)).u.CINT
7352                    {
7353                        (*mem.offset((b + 3) as isize)).u.CINT = (*mem
7354                            .offset((p + 3) as isize))
7355                            .u
7356                            .CINT;
7357                    }
7358                    if (*mem.offset((b + 2) as isize)).u.CINT
7359                        < (*mem.offset((p + 2) as isize)).u.CINT
7360                    {
7361                        (*mem.offset((b + 2) as isize)).u.CINT = (*mem
7362                            .offset((p + 2) as isize))
7363                            .u
7364                            .CINT;
7365                    }
7366                }
7367            } else {
7368                (*mem.offset(p as isize)).hh.v.RH = (*mem.offset((b + 5) as isize))
7369                    .hh
7370                    .v
7371                    .RH;
7372                (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7373                (*mem.offset((b + 3) as isize)).u.CINT = (*mem.offset((p + 3) as isize))
7374                    .u
7375                    .CINT;
7376                if (*mem.offset((b + 1) as isize)).u.CINT
7377                    < (*mem.offset((p + 1) as isize)).u.CINT
7378                {
7379                    (*mem.offset((b + 1) as isize)).u.CINT = (*mem
7380                        .offset((p + 1) as isize))
7381                        .u
7382                        .CINT;
7383                }
7384            },
7385        )
7386    }
7387
7388    /// Append a glue node with natural width `min` and stretch `max - min` to box
7389    /// `b`. Port of XeTeX's `stack_glue_into_box` (`xetex.web`).
7390    unsafe fn stack_glue_into_box(
7391        self: &mut Self,
7392        b: halfword,
7393        min: scaled,
7394        max: scaled,
7395    ) -> EngineFlow<()> {
7396        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7397        const NULL: halfword = -(268435455 as i64) as halfword;
7398        const ZERO_GLUE: halfword = 0;
7399        let q = (&mut *(self as *mut PortableTexEngine<'_>)).znewspec(ZERO_GLUE)?;
7400        (*mem.offset((q + 1) as isize)).u.CINT = min;
7401        (*mem.offset((q + 2) as isize)).u.CINT = max - min;
7402        let p = (&mut *(self as *mut PortableTexEngine<'_>)).znewglue(q)?;
7403        Ok(
7404            if (*mem.offset(b as isize)).hh.u.B0 as i32 == 0 {
7405                let mut r = (*mem.offset((b + 5) as isize)).hh.v.RH;
7406                if r == NULL {
7407                    (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7408                } else {
7409                    while (*mem.offset(r as isize)).hh.v.RH != NULL {
7410                        r = (*mem.offset(r as isize)).hh.v.RH;
7411                    }
7412                    (*mem.offset(r as isize)).hh.v.RH = p;
7413                }
7414            } else {
7415                (*mem.offset(p as isize)).hh.v.RH = (*mem.offset((b + 5) as isize))
7416                    .hh
7417                    .v
7418                    .RH;
7419                (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7420                (*mem.offset((b + 3) as isize)).u.CINT = (*mem.offset((p + 3) as isize))
7421                    .u
7422                    .CINT;
7423                (*mem.offset((b + 1) as isize)).u.CINT = (*mem.offset((p + 1) as isize))
7424                    .u
7425                    .CINT;
7426            },
7427        )
7428    }
7429
7430    /// Build a box (height/width at least `s`) for the stretchable glyph assembly
7431    /// `assembly` in font `f_0`, stacking parts with overlap glue. Faithful port
7432    /// of XeTeX's `build_opentype_assembly` (`xetex.web`), reading parts from the
7433    /// heap-owned [`GlyphAssembly`] handed out by `get_ot_assembly_ptr`.
7434    pub(crate) unsafe fn zbuildopentypeassembly(
7435        self: &mut Self,
7436        f_0: internalfontnumber,
7437        assembly: voidpointer,
7438        s: scaled,
7439        horiz_flag: integer,
7440    ) -> EngineFlow<halfword> {
7441        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7442        let horiz = horiz_flag != 0;
7443        let b = (&mut *(self as *mut PortableTexEngine<'_>)).newnullbox()?;
7444        (*mem.offset(b as isize)).hh.u.B0 = if horiz { 0 } else { 1 };
7445        let parts: &[PortableMathAssemblyPart] = if assembly.is_null() {
7446            &[]
7447        } else {
7448            &(*(assembly as *const GlyphAssembly)).parts
7449        };
7450        let part_count = parts.len();
7451        let min_o = Self::ot_min_connector_overlap(
7452            self as *mut PortableTexEngine<'resources>,
7453            f_0 as i32,
7454        );
7455        let mut n: integer = -1;
7456        let mut no_extenders = true;
7457        loop {
7458            n += 1;
7459            let mut s_max: scaled = 0;
7460            let mut prev_o: scaled = 0;
7461            for part in parts.iter() {
7462                if part.extender {
7463                    no_extenders = false;
7464                    for _ in 0..n {
7465                        let mut o = part.start_connector;
7466                        if min_o < o {
7467                            o = min_o;
7468                        }
7469                        if prev_o < o {
7470                            o = prev_o;
7471                        }
7472                        s_max = s_max - o + part.full_advance;
7473                        prev_o = part.end_connector;
7474                    }
7475                } else {
7476                    let mut o = part.start_connector;
7477                    if min_o < o {
7478                        o = min_o;
7479                    }
7480                    if prev_o < o {
7481                        o = prev_o;
7482                    }
7483                    s_max = s_max - o + part.full_advance;
7484                    prev_o = part.end_connector;
7485                }
7486            }
7487            if s_max >= s || no_extenders {
7488                break;
7489            }
7490        }
7491        let mut prev_o: scaled = 0;
7492        for i in 0..part_count {
7493            let part = parts[i];
7494            let reps = if part.extender { n } else { 1 };
7495            for _ in 0..reps {
7496                let mut o = part.start_connector;
7497                if prev_o < o {
7498                    o = prev_o;
7499                }
7500                let oo = o;
7501                if min_o < o {
7502                    o = min_o;
7503                }
7504                if oo > 0 {
7505                    (&mut *(self as *mut PortableTexEngine<'_>))
7506                        .stack_glue_into_box(b, -oo, -o)?;
7507                }
7508                let g = part.glyph;
7509                (&mut *(self as *mut PortableTexEngine<'_>))
7510                    .stack_glyph_into_box(b, f_0, g)?;
7511                prev_o = part.end_connector;
7512            }
7513        }
7514        const NULL: halfword = -(268435455 as i64) as halfword;
7515        let mut p = (*mem.offset((b + 5) as isize)).hh.v.RH;
7516        let mut nat: scaled = 0;
7517        let mut str_: scaled = 0;
7518        while p != NULL {
7519            let ty = (*mem.offset(p as isize)).hh.u.B0 as i32;
7520            if ty == 8 {
7521                if horiz {
7522                    nat += (*mem.offset((p + 1) as isize)).u.CINT;
7523                } else {
7524                    nat
7525                        += (*mem.offset((p + 3) as isize)).u.CINT
7526                            + (*mem.offset((p + 2) as isize)).u.CINT;
7527                }
7528            } else if ty == 10 {
7529                let spec = (*mem.offset((p + 1) as isize)).hh.v.LH;
7530                nat += (*mem.offset((spec + 1) as isize)).u.CINT;
7531                str_ += (*mem.offset((spec + 2) as isize)).u.CINT;
7532            }
7533            p = (*mem.offset(p as isize)).hh.v.RH;
7534        }
7535        if s > nat && str_ > 0 {
7536            let mut o = s - nat;
7537            if o > str_ {
7538                o = str_;
7539            }
7540            (*mem.offset((b + 5) as isize)).hh.u.B1 = 0;
7541            (*mem.offset((b + 5) as isize)).hh.u.B0 = 1;
7542            (*mem.offset((b + 6) as isize)).gr = o as f64 / str_ as f64;
7543            let stretched = nat
7544                + (str_ as f64 * (*mem.offset((b + 6) as isize)).gr).round() as scaled;
7545            if horiz {
7546                (*mem.offset((b + 1) as isize)).u.CINT = stretched;
7547            } else {
7548                (*mem.offset((b + 3) as isize)).u.CINT = stretched;
7549            }
7550        } else if horiz {
7551            (*mem.offset((b + 1) as isize)).u.CINT = nat;
7552        } else {
7553            (*mem.offset((b + 3) as isize)).u.CINT = nat;
7554        }
7555        Ok(b)
7556    }
7557
7558}
7559
7560pub(crate) unsafe fn fputs(_text: const_string, _file: NativeFileHandle) -> i32 {
7561    0
7562}
7563
7564pub(crate) unsafe fn free(_ptr: voidpointer) {}
7565
7566pub(crate) unsafe fn xrealloc(old_address: address, _new_size: size_t) -> address {
7567    old_address
7568}
7569
7570pub(crate) unsafe fn getcreationdate() {}
7571
7572pub(crate) unsafe fn getfilemoddate(_s: integer) {}
7573
7574pub(crate) unsafe fn getfilesize(_s: integer) {}
7575
7576pub(crate) unsafe fn getfiledump(_s: integer, _offset: i32, _length: i32) {}
7577
7578pub(crate) unsafe fn getmd5sum(_s: integer, _file: i32) {}
7579
7580pub(crate) unsafe fn u_close_file_or_pipe(file: *mut unicodefile) {
7581    if !file.is_null() {
7582        PortableTexEngine::boundary_close_file(*file);
7583        *file = core::ptr::null_mut();
7584    }
7585}
7586
7587pub(crate) unsafe fn setinputfileencoding(
7588    file: unicodefile,
7589    mode: integer,
7590    _encoding_data: integer,
7591) {
7592    // XeTeX modes: AUTO=0, UTF8=1, UTF16BE=2, UTF16LE=3, RAW=4, ICUMAPPING=5.
7593    // We do not support ICU; unknown/ICU modes degrade to raw bytes. `AUTO`
7594    // here resolves to UTF-8 (the default after a failed sniff).
7595    if file.is_null() {
7596        return;
7597    }
7598    let handle = &mut *file;
7599    handle.encoding = match mode {
7600        1 => InputEncoding::Utf8,
7601        2 => InputEncoding::Utf16Be,
7602        3 => InputEncoding::Utf16Le,
7603        4 => InputEncoding::Bytes,
7604        0 => InputEncoding::Utf8, // AUTO resolves to UTF-8.
7605        _ => InputEncoding::Bytes,
7606    };
7607}
7608
7609pub(crate) unsafe fn usingGraphite(_engine: FontHandle) -> boolean {
7610    false_0
7611}
7612
7613pub(crate) unsafe fn aatprintfontname(
7614    _what: i32,
7615    _attrs: CFDictionaryRef,
7616    _param1: i32,
7617    _param2: i32,
7618) {
7619}
7620
7621pub(crate) unsafe fn grprintfontname(
7622    _what: integer,
7623    _engine: voidpointer,
7624    _param1: integer,
7625    _param2: integer,
7626) {
7627}
7628
7629pub(crate) unsafe fn printglyphname(_font: integer, _gid: integer) {}
7630
7631pub(crate) unsafe fn getnativecharheightdepth(
7632    _font: integer,
7633    _ch: integer,
7634    height: *mut integer,
7635    depth: *mut integer,
7636) {
7637    if !height.is_null() {
7638        *height = 0;
7639    }
7640    if !depth.is_null() {
7641        *depth = 0;
7642    }
7643}
7644
7645pub(crate) unsafe fn getnativecharsidebearings(
7646    _font: integer,
7647    _ch: integer,
7648    lsb: *mut integer,
7649    rsb: *mut integer,
7650) {
7651    if !lsb.is_null() {
7652        *lsb = 0;
7653    }
7654    if !rsb.is_null() {
7655        *rsb = 0;
7656    }
7657}
7658
7659pub(crate) unsafe fn getnativecharwd(_font: integer, _ch: integer) -> integer {
7660    0
7661}
7662
7663pub(crate) unsafe fn getnativecharht(_font: integer, _ch: integer) -> integer {
7664    0
7665}
7666
7667pub(crate) unsafe fn getnativechardp(_font: integer, _ch: integer) -> integer {
7668    0
7669}
7670
7671pub(crate) unsafe fn getnativecharic(_font: integer, _ch: integer) -> integer {
7672    0
7673}
7674
7675pub(crate) unsafe fn getglyphbounds(_font: integer, _edge: integer, _gid: integer) -> integer {
7676    0
7677}
7678
7679pub(crate) unsafe fn getfontcharrange(_font: integer, _first: i32) -> integer {
7680    0
7681}
7682
7683pub(crate) unsafe fn get_native_italic_correction(_node: voidpointer) -> Fixed {
7684    0
7685}
7686
7687pub(crate) unsafe fn get_native_glyph_italic_correction(_node: voidpointer) -> Fixed {
7688    0
7689}
7690
7691pub(crate) unsafe fn applymapping(
7692    _mapping: voidpointer,
7693    _text: *mut uint16_t,
7694    text_len: i32,
7695) -> i32 {
7696    text_len
7697}
7698
7699pub(crate) unsafe fn checkfortfmfontmapping() {}
7700
7701pub(crate) unsafe fn loadtfmfontmapping() -> voidpointer {
7702    nullptr
7703}
7704
7705pub(crate) unsafe fn applytfmfontmapping(_mapping: voidpointer, c_0: i32) -> i32 {
7706    c_0
7707}
7708
7709pub(crate) unsafe fn set_cp_code(
7710    _font_num: i32,
7711    _code: u32,
7712    _side: i32,
7713    _value: i32,
7714) -> i32 {
7715    0
7716}
7717
7718pub(crate) unsafe fn countpdffilepages() -> i32 {
7719    0
7720}
7721
7722pub(crate) unsafe fn aatfontget(_what: i32, _attrs: CFDictionaryRef) -> i32 {
7723    0
7724}
7725
7726pub(crate) unsafe fn aatfontget1(_what: i32, _attrs: CFDictionaryRef, _param: i32) -> i32 {
7727    0
7728}
7729
7730pub(crate) unsafe fn aatfontget2(
7731    _what: i32,
7732    _attrs: CFDictionaryRef,
7733    _param1: i32,
7734    _param2: i32,
7735) -> i32 {
7736    0
7737}
7738
7739pub(crate) unsafe fn aatfontgetnamed(_what: i32, _attrs: CFDictionaryRef) -> i32 {
7740    0
7741}
7742
7743pub(crate) unsafe fn aatfontgetnamed1(
7744    _what: i32,
7745    _attrs: CFDictionaryRef,
7746    _param: i32,
7747) -> i32 {
7748    0
7749}
7750
7751pub(crate) unsafe fn grfontgetnamed(_what: integer, _engine: voidpointer) -> integer {
7752    0
7753}
7754
7755pub(crate) unsafe fn grfontgetnamed1(
7756    _what: integer,
7757    _engine: voidpointer,
7758    _param: integer,
7759) -> integer {
7760    0
7761}
7762
7763pub(crate) unsafe fn otfontget(_what: integer, _engine: voidpointer) -> integer {
7764    0
7765}
7766
7767pub(crate) unsafe fn otfontget1(
7768    _what: integer,
7769    _engine: voidpointer,
7770    _param: integer,
7771) -> integer {
7772    0
7773}
7774
7775pub(crate) unsafe fn otfontget2(
7776    _what: integer,
7777    _engine: voidpointer,
7778    _param1: integer,
7779    _param2: integer,
7780) -> integer {
7781    0
7782}
7783
7784pub(crate) unsafe fn otfontget3(
7785    _what: integer,
7786    _engine: voidpointer,
7787    _param1: integer,
7788    _param2: integer,
7789    _param3: integer,
7790) -> integer {
7791    0
7792}
7793
7794/// Reclaim a [`GlyphAssembly`] previously handed out by
7795/// `get_ot_assembly_ptr`. Mirrors XeTeX's `free_ot_assembly`, but reclaims the
7796/// safe Rust `Box` allocation instead of calling `libc::free`.
7797///
7798/// # Safety
7799/// `assembly`, if non-null, must be a pointer returned by `get_ot_assembly_ptr`
7800/// and not previously freed.
7801pub(crate) unsafe fn free_ot_assembly(assembly: *mut GlyphAssembly) {
7802    if !assembly.is_null() {
7803        drop(Box::from_raw(assembly));
7804    }
7805}
7806
7807#[cfg(test)]
7808mod tests {
7809    use super::*;
7810
7811    /// Build a text [`PortableFileHandle`] over `bytes` with the given encoding.
7812    fn text_handle(bytes: Vec<u8>, encoding: InputEncoding) -> PortableFileHandle {
7813        let mut handle = PortableFileHandle::new(
7814            "test.tex".to_string(),
7815            ResourceKind::TexInput,
7816            None,
7817            resource_format_tex_input,
7818            bytes,
7819        );
7820        handle.encoding = encoding;
7821        handle
7822    }
7823
7824    /// Drain every Unicode scalar the decoder produces until EOF.
7825    fn decode_all(handle: &mut PortableFileHandle) -> Vec<u32> {
7826        let mut out = Vec::new();
7827        while let Some(scalar) = handle.next_input_scalar() {
7828            out.push(scalar);
7829        }
7830        out
7831    }
7832
7833    #[test]
7834    fn utf8_decoder_reads_multibyte_scalars() {
7835        // "αβγ" = CE B1 CE B2 CE B3 -> U+03B1, U+03B2, U+03B3.
7836        let mut h = text_handle(vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3], InputEncoding::Utf8);
7837        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
7838        // ASCII stays one-scalar-per-byte; a 3-byte (U+20AC €) and 4-byte
7839        // (U+1F600 😀) sequence round-trip.
7840        let mut h = text_handle(
7841            vec![b'A', 0xE2, 0x82, 0xAC, 0xF0, 0x9F, 0x98, 0x80],
7842            InputEncoding::Utf8,
7843        );
7844        assert_eq!(decode_all(&mut h), vec![0x41, 0x20AC, 0x1F600]);
7845    }
7846
7847    #[test]
7848    fn utf8_decoder_replaces_bad_sequences() {
7849        // A lead byte 0xCE followed by a non-continuation 'A' -> U+FFFD, and the
7850        // 'A' is UNGETC'd so it decodes next.
7851        let mut h = text_handle(vec![0xCE, b'A'], InputEncoding::Utf8);
7852        assert_eq!(decode_all(&mut h), vec![0xFFFD, 0x41]);
7853        // Lone continuation byte (0x80..0xBF as a lead) decodes to itself with
7854        // zero extra bytes (matches bytesFromUTF8 == 0), i.e. C8.. raw -> 0xFFFD
7855        // only when range-checked; a bare 0x80 has extra=0 so rval=0x80.
7856        let mut h = text_handle(vec![0x80], InputEncoding::Utf8);
7857        assert_eq!(decode_all(&mut h), vec![0x80]);
7858    }
7859
7860    #[test]
7861    fn utf16_decoders_read_units_and_surrogates() {
7862        // "αβγ" UTF-16LE: B1 03 B2 03 B3 03.
7863        let mut h = text_handle(
7864            vec![0xB1, 0x03, 0xB2, 0x03, 0xB3, 0x03],
7865            InputEncoding::Utf16Le,
7866        );
7867        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
7868        // Same in UTF-16BE: 03 B1 03 B2 03 B3.
7869        let mut h = text_handle(
7870            vec![0x03, 0xB1, 0x03, 0xB2, 0x03, 0xB3],
7871            InputEncoding::Utf16Be,
7872        );
7873        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
7874        // Surrogate pair U+1F600 in UTF-16LE: D83D DE00 -> 3D D8 00 DE.
7875        let mut h = text_handle(vec![0x3D, 0xD8, 0x00, 0xDE], InputEncoding::Utf16Le);
7876        assert_eq!(decode_all(&mut h), vec![0x1F600]);
7877        // High surrogate followed by a non-low unit -> U+FFFD, and the stray unit
7878        // (here U+0041) is stashed in saved_char and decoded next.
7879        let mut h = text_handle(vec![0x3D, 0xD8, 0x41, 0x00], InputEncoding::Utf16Le);
7880        assert_eq!(decode_all(&mut h), vec![0xFFFD, 0x41]);
7881        // Lone low surrogate -> U+FFFD.
7882        let mut h = text_handle(vec![0x00, 0xDC], InputEncoding::Utf16Le);
7883        assert_eq!(decode_all(&mut h), vec![0xFFFD]);
7884    }
7885
7886    #[test]
7887    fn bytes_mode_reads_each_byte_raw() {
7888        // RAW/Bytes: every byte becomes its own scalar, no multibyte decoding.
7889        let mut h = text_handle(vec![0xCE, 0xB1, 0x41], InputEncoding::Bytes);
7890        assert_eq!(decode_all(&mut h), vec![0xCE, 0xB1, 0x41]);
7891    }
7892
7893    #[test]
7894    fn bom_sniff_selects_encoding_and_consumes_bom() {
7895        // UTF-8 BOM EF BB BF + "A" -> UTF8, BOM consumed, 'A' next.
7896        let mut h = text_handle(vec![0xEF, 0xBB, 0xBF, b'A'], InputEncoding::Bytes);
7897        h.resolve_text_encoding_auto();
7898        assert_eq!(h.encoding, InputEncoding::Utf8);
7899        assert_eq!(h.cursor, 3);
7900        assert_eq!(decode_all(&mut h), vec![0x41]);
7901        // UTF-16BE BOM FE FF + U+03B1 -> UTF16BE, BOM consumed.
7902        let mut h = text_handle(vec![0xFE, 0xFF, 0x03, 0xB1], InputEncoding::Bytes);
7903        h.resolve_text_encoding_auto();
7904        assert_eq!(h.encoding, InputEncoding::Utf16Be);
7905        assert_eq!(h.cursor, 2);
7906        assert_eq!(decode_all(&mut h), vec![0x3B1]);
7907        // UTF-16LE BOM FF FE + U+03B1 -> UTF16LE, BOM consumed.
7908        let mut h = text_handle(vec![0xFF, 0xFE, 0xB1, 0x03], InputEncoding::Bytes);
7909        h.resolve_text_encoding_auto();
7910        assert_eq!(h.encoding, InputEncoding::Utf16Le);
7911        assert_eq!(h.cursor, 2);
7912        assert_eq!(decode_all(&mut h), vec![0x3B1]);
7913        // No BOM, ASCII text -> UTF8, nothing consumed.
7914        let mut h = text_handle(vec![b'h', b'i'], InputEncoding::Bytes);
7915        h.resolve_text_encoding_auto();
7916        assert_eq!(h.encoding, InputEncoding::Utf8);
7917        assert_eq!(h.cursor, 0);
7918        // 00 xx (BOM-less UTF-16BE heuristic) -> UTF16BE, NOT consumed (rewind).
7919        let mut h = text_handle(vec![0x00, 0x41], InputEncoding::Bytes);
7920        h.resolve_text_encoding_auto();
7921        assert_eq!(h.encoding, InputEncoding::Utf16Be);
7922        assert_eq!(h.cursor, 0);
7923        assert_eq!(decode_all(&mut h), vec![0x41]);
7924    }
7925
7926    #[test]
7927    fn input_line_decodes_into_buffer_per_profile() {
7928        // End-to-end through the real `boundary_input_line` reader: a text input
7929        // under the XeTeX profile is decoded (encoding resolved by the open-path
7930        // AUTO sniff), while the same bytes under the non-XeTeX (tex) profile are
7931        // read raw (Bytes mode). The engine's `buffer[first..last]` must hold the
7932        // expected Unicode scalars.
7933        fn buffer_after_input_line(profile: EngineProfile, bytes: Vec<u8>) -> Vec<u32> {
7934            let image = PortableFormatImage::empty();
7935            let mut engine = PortableTexEngine::from_format(profile, &image, EmptyResourceProvider);
7936            engine.initialize_format_state();
7937            // Build a text handle and resolve its encoding via the same open-path
7938            // helper the boundary open sites use.
7939            let mut handle = PortableFileHandle::new(
7940                "input.tex".to_string(),
7941                ResourceKind::TexInput,
7942                None,
7943                resource_format_tex_input,
7944                bytes,
7945            );
7946            PortableTexEngine::resolve_input_encoding(&engine, &mut handle);
7947            let raw = Box::into_raw(Box::new(handle));
7948            // Read one line into the buffer starting at `state.first`.
7949            engine.state.first = 0;
7950            let ok = unsafe {
7951                PortableTexEngine::boundary_input_line(
7952                    &mut engine as *mut PortableTexEngine<'_>,
7953                    raw as NativeFileHandle,
7954                )
7955            };
7956            assert_ne!(ok, 0, "boundary_input_line should succeed");
7957            let first = engine.state.first.max(0) as usize;
7958            let last = engine.state.last.max(0) as usize;
7959            let out = (first..last)
7960                .map(|i| unsafe { *engine.state.buffer.offset(i as isize) as u32 })
7961                .collect();
7962            unsafe { drop(Box::from_raw(raw)) };
7963            out
7964        }
7965        // "αβγ" = CE B1 CE B2 CE B3.
7966        let utf8 = vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3];
7967        assert_eq!(
7968            buffer_after_input_line(EngineProfile::xetex(), utf8.clone()),
7969            vec![0x3B1, 0x3B2, 0x3B3],
7970            "xetex must UTF-8 decode the input line"
7971        );
7972        assert_eq!(
7973            buffer_after_input_line(EngineProfile::tex(), utf8),
7974            vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3],
7975            "non-xetex must read raw bytes"
7976        );
7977        // UTF-16LE with BOM under xetex decodes to the same scalars.
7978        let utf16le_bom = vec![0xFF, 0xFE, 0xB1, 0x03, 0xB2, 0x03, 0xB3, 0x03];
7979        assert_eq!(
7980            buffer_after_input_line(EngineProfile::xetex(), utf16le_bom),
7981            vec![0x3B1, 0x3B2, 0x3B3],
7982            "xetex must UTF-16LE decode a BOM'd input line"
7983        );
7984        // UTF-16BE with BOM under xetex.
7985        let utf16be_bom = vec![0xFE, 0xFF, 0x03, 0xB1, 0x03, 0xB2, 0x03, 0xB3];
7986        assert_eq!(
7987            buffer_after_input_line(EngineProfile::xetex(), utf16be_bom),
7988            vec![0x3B1, 0x3B2, 0x3B3],
7989            "xetex must UTF-16BE decode a BOM'd input line"
7990        );
7991    }
7992
7993    #[test]
7994    fn engine_abort_is_captured_at_runtime_boundary() {
7995        let image = PortableFormatImage::empty();
7996        let mut engine =
7997            PortableTexEngine::from_format(EngineProfile::tex(), &image, EmptyResourceProvider);
7998
7999        let completed = engine.catch_engine_abort(|engine| unsafe {
8000            PortableTexEngine::abort_engine(engine as *mut PortableTexEngine<'_>, 7)?;
8001            Ok(())
8002        });
8003
8004        assert!(!completed);
8005        assert_eq!(engine.last_abort_status(), Some(7));
8006    }
8007
8008    #[test]
8009    fn successful_engine_abort_completes_runtime_boundary() {
8010        let image = PortableFormatImage::empty();
8011        let mut engine =
8012            PortableTexEngine::from_format(EngineProfile::tex(), &image, EmptyResourceProvider);
8013
8014        let completed = engine.catch_engine_abort(|engine| unsafe {
8015            PortableTexEngine::abort_engine(engine as *mut PortableTexEngine<'_>, 0)?;
8016            Ok(())
8017        });
8018
8019        assert!(completed);
8020        assert_eq!(engine.last_abort_status(), None);
8021    }
8022}