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