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    fn portable_bytes_header_len(bytes: &[u8]) -> Option<usize> {
2549        let mut cursor = 0usize;
2550        if bytes.get(cursor..cursor + PORTABLE_FORMAT_MAGIC.len())? != &PORTABLE_FORMAT_MAGIC[..] {
2551            return None;
2552        }
2553        cursor += PORTABLE_FORMAT_MAGIC.len();
2554        if *bytes.get(cursor)? as usize != core::mem::size_of::<usize>() {
2555            return None;
2556        }
2557        cursor += 1;
2558        let struct_size =
2559            u64::from_le_bytes(bytes.get(cursor..cursor + 8)?.try_into().ok()?) as usize;
2560        cursor += 8;
2561        if struct_size != core::mem::size_of::<Self>() {
2562            return None;
2563        }
2564        bytes.get(cursor..cursor + struct_size)?;
2565        Some(cursor)
2566    }
2567
2568    fn portable_validation_tag() -> [u8; 17] {
2569        let mut tag = [0u8; 17];
2570        tag[..8].copy_from_slice(&PORTABLE_FORMAT_MAGIC);
2571        tag[8] = core::mem::size_of::<usize>() as u8;
2572        tag[9..].copy_from_slice(&(core::mem::size_of::<Self>() as u64).to_le_bytes());
2573        tag
2574    }
2575
2576    /// Reconstruct an engine state from [`Self::to_portable_bytes`] output.
2577    ///
2578    /// Returns `None` if the magic/stamps don't match the loading target or the
2579    /// buffer is truncated. The input-file pointer table is nulled (those are
2580    /// process-local OS handles). `fontlayoutengine`/`fontmapping` hold *integer
2581    /// font handles*, not addresses, so they are preserved verbatim and the
2582    /// caller re-binds them to a fresh font platform via
2583    /// [`FontPlatform::restore_font_table`] before rendering.
2584    pub(crate) fn from_portable_bytes(bytes: &[u8]) -> Option<Box<Self>> {
2585        let mut cursor = Self::portable_bytes_header_len(bytes)?;
2586        let image = bytes.get(cursor..cursor + core::mem::size_of::<Self>())?;
2587        cursor += core::mem::size_of::<Self>();
2588
2589        let mut state = Box::<Self>::new_uninit();
2590        let state_ptr = state.as_mut_ptr();
2591        // SAFETY: mirrors `clone_boxed` — copy the POD struct image, then
2592        // overwrite every heap-owning field with a freshly-decoded `Vec`
2593        // (`ptr::write` does not drop the garbage header it overwrites), then
2594        // refresh all derived raw pointers from the new Vec bases.
2595        let state = unsafe {
2596            core::ptr::copy_nonoverlapping(
2597                image.as_ptr(),
2598                state_ptr as *mut u8,
2599                core::mem::size_of::<Self>(),
2600            );
2601            let decoded_mem: Vec<memoryword> = portable_read_vec_ranges(bytes, &mut cursor)?;
2602            core::ptr::addr_of_mut!((*state_ptr).mem).write(decoded_mem);
2603            macro_rules! read_field {
2604                ($name:ident, $ty:ty) => {
2605                    let decoded: Vec<$ty> = portable_read_vec_ranges(bytes, &mut cursor)?;
2606                    core::ptr::addr_of_mut!((*state_ptr).$name).write(decoded);
2607                };
2608            }
2609            portable_owning_vecs!(read_field);
2610            let eqtb_paged =
2611                portable_read_paged(bytes, &mut cursor, eqtb_codepoint_default, eqtb_sig_range)?;
2612            core::ptr::addr_of_mut!((*state_ptr).eqtb_paged).write(eqtb_paged);
2613            let hash_paged =
2614                portable_read_paged(bytes, &mut cursor, hash_codepoint_default, hash_sig_range)?;
2615            core::ptr::addr_of_mut!((*state_ptr).hash_paged).write(hash_paged);
2616            // Source-tracking tables are transient per-render parse state, never
2617            // serialized; overwrite the raw-image headers with fresh empties so
2618            // the reloaded state owns valid (empty) tables. `source_tracking`
2619            // itself round-trips as a POD scalar but is reset at each
2620            // `begin_primary_input`, so its dumped value is irrelevant.
2621            core::ptr::addr_of_mut!((*state_ptr).src_spans).write(Vec::new());
2622            core::ptr::addr_of_mut!((*state_ptr).src_dedup)
2623                .write(std::collections::HashMap::new());
2624            core::ptr::addr_of_mut!((*state_ptr).node_src)
2625                .write(PagedArray::new(0, 0, node_src_default, node_src_sig));
2626            core::ptr::addr_of_mut!((*state_ptr).src_native_offsets).write(Vec::new());
2627            core::ptr::addr_of_mut!((*state_ptr).src_stack_cells).write(Vec::new());
2628            core::ptr::addr_of_mut!((*state_ptr).cur_stack_head).write(0);
2629            core::ptr::addr_of_mut!((*state_ptr).node_stack)
2630                .write(PagedArray::new(0, 0, node_stack_default, node_stack_sig));
2631            // Input-file slots are process-local OS handles; null them. The font
2632            // handle tables (`fontlayoutengine`/`fontmapping`) are integer
2633            // handles, preserved for `restore_font_table` to rebind.
2634            for slot in (*state_ptr).inputfile_storage.iter_mut() {
2635                *slot = core::ptr::null_mut();
2636            }
2637            let mut state = state.assume_init();
2638            state.refresh_runtime_pointers();
2639            state
2640        };
2641        Some(state)
2642    }
2643
2644    /// Total bytes backing the engine's dynamic arrays (the dominant runtime
2645    /// footprint: `mem`, `eqtb`, `hash`, `fontinfo`, string pool, trie, …).
2646    /// Counts allocated capacity, so it reflects real resident memory.
2647    pub(crate) fn state_array_bytes(&self) -> usize {
2648        let mut total = self.mem.capacity() * core::mem::size_of::<memoryword>();
2649        macro_rules! accumulate {
2650            ($name:ident, $ty:ty) => {
2651                total += self.$name.capacity() * core::mem::size_of::<$ty>();
2652            };
2653        }
2654        portable_owning_vecs!(accumulate);
2655        total += self.eqtb_paged.resident_bytes() + self.hash_paged.resident_bytes();
2656        total
2657    }
2658
2659    fn allocate_initial_arrays(&mut self) {
2660        self.iniversion = true_0;
2661        self.membot = 0;
2662        self.memmin = self.membot;
2663        // Math-fragment workloads use a tiny fraction of TeX's worst-case main
2664        // memory. Sized down from the 5M default; the latex+amsmath+unicode-math
2665        // format build is the high-water mark and fits comfortably under 1M words.
2666        // Raise if a large document hits `! TeX capacity exceeded (main memory)`.
2667        self.memtop = 999_999;
2668        self.memmax = self.memtop;
2669        self.hashextra = 600_000;
2670        self.eqtbtop = xetex_eqtb_top + self.hashextra;
2671        self.bufsize = 200_000;
2672        self.nestsize = 1_000;
2673        self.maxinopen = 15;
2674        self.paramsize = 20_000;
2675        self.savesize = 200_000;
2676        self.stacksize = 10_000;
2677        self.dvibufsize = 16_384;
2678        self.poolsize = 6_250_000;
2679        self.maxstrings = 500_000;
2680        self.fontmemsize = 1_000_000;
2681        self.fontmax = 500;
2682        // Hyphenation trie construction peak — the production `\patterns` the real
2683        // `latex.ltx` loads need >700K nodes, so this stays at the worst case.
2684        // The six *construction-scratch* trie arrays (everything except the packed
2685        // trietr{c,l,o}) are freed in `seal_as_format_snapshot`, so this large
2686        // size only costs memory transiently during the one-time format build.
2687        self.triesize = 1_100_000;
2688        self.hyphsize = 8_191;
2689        self.primused = 2_100;
2690        self.errorline = 79;
2691        self.halferrorline = 50;
2692        self.maxprintline = 79;
2693        self.expanddepth = 10_000;
2694
2695        self.mem = zeroed_vec((self.memtop - self.memmin + 1) as usize);
2696        // These arrays are `array[0..N]` / `array[1..N]` in web2c, allocated via
2697        // `xmallocarray(T, N)` == `xmalloc((N+1)*sizeof(T))` (cpascal.h), i.e. N+1
2698        // elements so the top index N is valid. The c2rust output allocates each
2699        // as `(dim + 1)` accordingly. Allocating only `dim` here under-sizes every
2700        // one by a slot: in the original C arena the top index harmlessly aliased
2701        // adjacent memory, but this is a bounds-real Rust Vec, so e.g. show_context
2702        // reading `linestack[index+1]` at the top input level was an OOB read.
2703        self.buffer_storage = zeroed_vec((self.bufsize + 1) as usize);
2704        self.nest_storage = zeroed_vec((self.nestsize + 1) as usize);
2705        self.savestack_storage = zeroed_vec((self.savesize + 1) as usize);
2706        self.inputstack_storage = zeroed_vec((self.stacksize + 1) as usize);
2707        self.inputfile_storage = zeroed_vec((self.maxinopen + 1) as usize);
2708        self.eofseen_storage = zeroed_vec((self.maxinopen + 1) as usize);
2709        self.linestack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2710        self.grpstack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2711        self.ifstack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2712        self.sourcefilenamestack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2713        self.fullsourcefilenamestack_storage = zeroed_vec((self.maxinopen + 1) as usize);
2714        self.paramstack_storage = zeroed_vec((self.paramsize + 1) as usize);
2715        self.hyphword_storage = zeroed_vec((self.hyphsize + 1) as usize);
2716        self.hyphlist_storage = zeroed_vec((self.hyphsize + 1) as usize);
2717        self.hyphlink_storage = zeroed_vec((self.hyphsize + 1) as usize);
2718        // eqtb/hash: dense low region [..CODEPOINT_LO), then the per-codepoint
2719        // bands paged lazily. `hash` starts at absolute index `hashoffset`
2720        // (its element 0); both run through absolute index `eqtbtop`.
2721        self.eqtb_paged = PagedArray::new(
2722            0,
2723            (self.eqtbtop + 1) as usize,
2724            eqtb_codepoint_default,
2725            eqtb_sig_range,
2726        );
2727        self.hash_paged = PagedArray::new(
2728            hashoffset as usize,
2729            (self.eqtbtop + 1) as usize,
2730            hash_codepoint_default,
2731            hash_sig_range,
2732        );
2733        self.strstart_storage = zeroed_vec((self.maxstrings + 1) as usize);
2734        self.strpool_storage = zeroed_vec((self.poolsize + 1) as usize);
2735        self.fontinfo_storage = zeroed_vec((self.fontmemsize + 1) as usize);
2736        let font_slots = (self.fontmax + 1) as usize;
2737        self.bcharlabel_storage = zeroed_vec(font_slots);
2738        self.charbase_storage = zeroed_vec(font_slots);
2739        self.widthbase_storage = zeroed_vec(font_slots);
2740        self.heightbase_storage = zeroed_vec(font_slots);
2741        self.depthbase_storage = zeroed_vec(font_slots);
2742        self.italicbase_storage = zeroed_vec(font_slots);
2743        self.ligkernbase_storage = zeroed_vec(font_slots);
2744        self.kernbase_storage = zeroed_vec(font_slots);
2745        self.extenbase_storage = zeroed_vec(font_slots);
2746        self.parambase_storage = zeroed_vec(font_slots);
2747        self.fontarea_storage = zeroed_vec(font_slots);
2748        self.fontname_storage = zeroed_vec(font_slots);
2749        self.fontbc_storage = zeroed_vec(font_slots);
2750        self.fontec_storage = zeroed_vec(font_slots);
2751        self.fontbchar_storage = zeroed_vec(font_slots);
2752        self.fontfalsebchar_storage = zeroed_vec(font_slots);
2753        self.fontcheck_storage = zeroed_vec(font_slots);
2754        self.fontdsize_storage = zeroed_vec(font_slots);
2755        self.fontsize_storage = zeroed_vec(font_slots);
2756        self.fontflags_storage = zeroed_vec(font_slots);
2757        self.fontglue_storage = zeroed_vec(font_slots);
2758        self.fontlayoutengine_storage = zeroed_vec(font_slots);
2759        self.fontletterspace_storage = zeroed_vec(font_slots);
2760        self.fontmapping_storage = zeroed_vec(font_slots);
2761        self.fontparams_storage = zeroed_vec(font_slots);
2762        self.fontused_storage = zeroed_vec(font_slots);
2763        self.nativetext_storage = Vec::new();
2764        self.hyphenchar_storage = zeroed_vec(font_slots);
2765        self.skewchar_storage = zeroed_vec(font_slots);
2766        let trie_slots = (self.triesize + 1) as usize;
2767        self.triec_storage = zeroed_vec(trie_slots);
2768        self.triehash_storage = zeroed_vec(trie_slots);
2769        self.triel_storage = zeroed_vec(trie_slots);
2770        self.trieo_storage = zeroed_vec(trie_slots);
2771        self.trier_storage = zeroed_vec(trie_slots);
2772        self.trietaken_storage = zeroed_vec(trie_slots);
2773        self.trietrc_storage = zeroed_vec(trie_slots);
2774        self.trietrl_storage = zeroed_vec(trie_slots);
2775        self.trietro_storage = zeroed_vec(trie_slots);
2776        self.refresh_runtime_pointers();
2777    }
2778}
2779
2780fn zeroed_vec<T>(len: usize) -> Vec<T>
2781where
2782    T: Clone + Default,
2783{
2784    vec![T::default(); len]
2785}
2786
2787fn pointer_or_null<T>(storage: &mut Vec<T>) -> *mut T {
2788    if storage.is_empty() {
2789        core::ptr::null_mut()
2790    } else {
2791        storage.as_mut_ptr()
2792    }
2793}
2794
2795#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2796pub enum EngineProfileKind {
2797    Tex,
2798    Etex,
2799    Xetex,
2800}
2801
2802#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2803pub struct EngineProfile {
2804    pub id: &'static str,
2805    pub kind: EngineProfileKind,
2806    pub etex: bool,
2807    pub xetex: bool,
2808    pub unicode_scalars: bool,
2809    pub unicode_math: bool,
2810    pub native_fonts: bool,
2811}
2812
2813#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2814struct WriteTokenConstants {
2815    open_group_token: halfword,
2816    end_write_token: halfword,
2817    close_group_token: halfword,
2818}
2819
2820impl EngineProfile {
2821    pub const fn tex() -> Self {
2822        Self {
2823            id: "tex",
2824            kind: EngineProfileKind::Tex,
2825            etex: false,
2826            xetex: false,
2827            unicode_scalars: false,
2828            unicode_math: false,
2829            native_fonts: false,
2830        }
2831    }
2832
2833    pub const fn etex() -> Self {
2834        Self {
2835            id: "etex",
2836            kind: EngineProfileKind::Etex,
2837            etex: true,
2838            xetex: false,
2839            unicode_scalars: false,
2840            unicode_math: false,
2841            native_fonts: false,
2842        }
2843    }
2844
2845    pub const fn xetex() -> Self {
2846        Self {
2847            id: "xetex",
2848            kind: EngineProfileKind::Xetex,
2849            etex: true,
2850            xetex: true,
2851            unicode_scalars: true,
2852            unicode_math: true,
2853            native_fonts: false,
2854        }
2855    }
2856
2857    const fn write_token_constants(self) -> WriteTokenConstants {
2858        match self.kind {
2859            EngineProfileKind::Tex | EngineProfileKind::Etex => WriteTokenConstants {
2860                open_group_token: 637,
2861                end_write_token: 19617,
2862                close_group_token: 379,
2863            },
2864            EngineProfileKind::Xetex => WriteTokenConstants {
2865                open_group_token: 4_194_429,
2866                end_write_token: 34_749_089,
2867                close_group_token: 2_097_275,
2868            },
2869        }
2870    }
2871}
2872
2873pub struct PortableFormatImage {
2874    source: PortableFormatSource,
2875}
2876
2877enum PortableFormatSource {
2878    Owned(Box<PortableTexState>),
2879    Static(&'static [u8]),
2880}
2881
2882#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2883pub struct PortableNodeHandle(pub i32);
2884
2885#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2886pub enum PortableNodeKind {
2887    HorizontalBox,
2888    VerticalBox,
2889    Rule,
2890    Insertion,
2891    Mark,
2892    Adjustment,
2893    Ligature,
2894    Discretionary,
2895    OutputWhatsit,
2896    Whatsit,
2897    Math,
2898    Glue,
2899    Kern,
2900    Penalty,
2901    UnsetBox,
2902    Noad,
2903    Style,
2904    Choice,
2905    Character,
2906    NativeWord,
2907    NativeGlyph,
2908    HostBoxRef,
2909    Unknown(i32),
2910}
2911
2912#[derive(Clone, Debug, PartialEq)]
2913pub struct PortableNodeSnapshot {
2914    pub handle: PortableNodeHandle,
2915    pub kind: PortableNodeKind,
2916    pub subtype: i32,
2917    pub source: Option<PortableSourceSpan>,
2918    pub link: Option<PortableNodeHandle>,
2919    pub font: i32,
2920    pub character: i32,
2921    pub width: i32,
2922    pub height: i32,
2923    pub depth: i32,
2924    pub shift: i32,
2925    pub list: Option<PortableNodeHandle>,
2926    pub native_glyphs: Vec<PortableNativeGlyph>,
2927    /// Box glue-set ratio (from `hpack`/`vpack`); meaningful for hlist/vlist.
2928    pub glue_set: f64,
2929    /// Box glue sign: 0 normal, 1 stretching, 2 shrinking.
2930    pub glue_sign: i32,
2931    /// Box glue order (0..3) that participates in stretching/shrinking.
2932    pub glue_order: i32,
2933    /// Glue node's spec stretch amount (raw glue order in `glue_stretch_order`).
2934    pub glue_stretch: i32,
2935    /// Glue node's spec shrink amount.
2936    pub glue_shrink: i32,
2937    /// Order (0..3) of the glue node's stretch component.
2938    pub glue_stretch_order: i32,
2939    /// Order (0..3) of the glue node's shrink component.
2940    pub glue_shrink_order: i32,
2941}
2942
2943impl Clone for PortableFormatImage {
2944    fn clone(&self) -> Self {
2945        match &self.source {
2946            PortableFormatSource::Owned(state) => Self {
2947                source: PortableFormatSource::Owned(state.clone_boxed()),
2948            },
2949            PortableFormatSource::Static(bytes) => Self {
2950                source: PortableFormatSource::Static(bytes),
2951            },
2952        }
2953    }
2954}
2955
2956impl PortableFormatImage {
2957    pub fn empty() -> Self {
2958        Self {
2959            source: PortableFormatSource::Owned(PortableTexState::new_boxed_default()),
2960        }
2961    }
2962
2963    fn from_engine_state(state: &PortableTexState) -> Self {
2964        let mut state = state.clone_boxed();
2965        state.seal_as_format_snapshot();
2966        Self {
2967            source: PortableFormatSource::Owned(state),
2968        }
2969    }
2970
2971    /// Wrap an already-sealed engine state, taking ownership without cloning.
2972    /// Used by [`PortableTexEngine::into_format`].
2973    fn from_sealed_state(state: Box<PortableTexState>) -> Self {
2974        Self {
2975            source: PortableFormatSource::Owned(state),
2976        }
2977    }
2978
2979    fn instantiate_state(&self) -> Box<PortableTexState> {
2980        match &self.source {
2981            PortableFormatSource::Owned(state) => state.clone_boxed(),
2982            PortableFormatSource::Static(bytes) => PortableTexState::from_portable_bytes(bytes)
2983                .expect("validated static format image became unreadable"),
2984        }
2985    }
2986
2987    /// Returns the same-target validation tag stored at the start of every image.
2988    #[must_use]
2989    pub fn validation_tag() -> [u8; 17] {
2990        PortableTexState::portable_validation_tag()
2991    }
2992
2993    /// Reports whether a serialized image carries this build target's validation tag.
2994    #[must_use]
2995    pub fn bytes_match_target(bytes: &[u8]) -> bool {
2996        PortableTexState::portable_bytes_header_len(bytes).is_some()
2997    }
2998
2999    /// Borrows a static serialized image without allocating or copying its contents.
3000    #[must_use]
3001    pub fn from_static_bytes(bytes: &'static [u8]) -> Option<Self> {
3002        Self::bytes_match_target(bytes).then_some(())?;
3003        Some(Self {
3004            source: PortableFormatSource::Static(bytes),
3005        })
3006    }
3007
3008    /// Serialize this format image to a portable byte buffer (a dumped `.fmt`)
3009    /// that [`Self::from_bytes`] can reload. Same-target only — see
3010    /// [`PortableTexState::to_portable_bytes`].
3011    #[must_use]
3012    pub fn to_bytes(&self) -> Vec<u8> {
3013        match &self.source {
3014            PortableFormatSource::Owned(state) => state.to_portable_bytes(),
3015            PortableFormatSource::Static(bytes) => bytes.to_vec(),
3016        }
3017    }
3018
3019    /// Reload a format image previously produced by [`Self::to_bytes`]. Returns
3020    /// `None` if the buffer is not a format image for this build target.
3021    #[must_use]
3022    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
3023        Some(Self {
3024            source: PortableFormatSource::Owned(PortableTexState::from_portable_bytes(bytes)?),
3025        })
3026    }
3027
3028    /// Resident bytes of the engine's dynamic arrays once instantiated from this
3029    /// image — the dominant runtime memory footprint.
3030    #[must_use]
3031    pub fn state_array_bytes(&self) -> usize {
3032        match &self.source {
3033            PortableFormatSource::Owned(state) => state.state_array_bytes(),
3034            PortableFormatSource::Static(bytes) => PortableTexState::from_portable_bytes(bytes)
3035                .expect("validated static format image became unreadable")
3036                .state_array_bytes(),
3037        }
3038    }
3039}
3040
3041/// Size in bytes of one TeX `memory_word` in this build. Real XeTeX packs it to
3042/// 8; this engine uses a 16-byte word (the `four_quarters` view is `u16`, but
3043/// `two_halves`/`cint` keep the c2rust 32-bit alignment).
3044#[must_use]
3045pub fn memory_word_bytes() -> usize {
3046    core::mem::size_of::<memoryword>()
3047}
3048
3049pub struct PortableTexEngine<'resources> {
3050    pub(crate) state: Box<PortableTexState>,
3051    pub(crate) profile: EngineProfile,
3052    pub(crate) resources: Box<dyn ResourceProvider + 'resources>,
3053    pub(crate) fonts: Box<dyn FontPlatform + 'resources>,
3054    pub(crate) platform: Box<dyn PortablePlatform + 'resources>,
3055    pub(crate) nameoffile_storage: Vec<UTF8code>,
3056    native_glyph_infos: std::collections::BTreeMap<i32, PortableNativeGlyphInfo>,
3057    // Host box render payloads by record index, markers in node memory carry only the index.
3058    hostbox_records: Vec<PortableHostBox>,
3059    character_protrusions: std::collections::BTreeMap<(integer, u32, integer), integer>,
3060    pub(crate) resource_requests: usize,
3061    pub(crate) resource_request_records: Vec<PortableResourceRequestRecord>,
3062    pub(crate) virtual_files: std::collections::BTreeMap<String, Vec<u8>>,
3063    pub(crate) transcript_bytes: Vec<u8>,
3064    pub(crate) current_input_package_owner: Option<String>,
3065    pub(crate) stripped_page_builds: usize,
3066    pub(crate) stripped_shipouts: usize,
3067    pub(crate) stripped_special_outputs: usize,
3068    pub(crate) stripped_picture_loads: usize,
3069    pub(crate) stripped_source_specials: usize,
3070    pub(crate) stripped_write_whatsit_diagnostics: usize,
3071    pub(crate) stripped_pdf_extensions: usize,
3072    pub(crate) stripped_page_top_prunes: usize,
3073    pub(crate) last_stripped_shipout_box: Option<PortableNodeHandle>,
3074    pub(crate) fragment_capture_enabled: bool,
3075    pub(crate) format_initialization: bool,
3076    pub(crate) captured_fragment_root: Option<PortableNodeHandle>,
3077    pub(crate) last_abort_status: Option<integer>,
3078    /// Captured message from the most recent surfaced [`EngineError`] (mirrors
3079    /// `last_abort_status` for the error channel). The driver boundary stores it
3080    /// here so the host can read *what* went wrong after a failed run.
3081    pub(crate) last_error_message: Option<String>,
3082    /// When set, the engine enforces the "render one math expression" sandbox:
3083    /// breakout (`$`), job-control (`\end`), and IO (`\input`, `\write`, ...)
3084    /// tokens are rejected as [`EngineError`]s, and a work budget bounds runtime.
3085    /// Off during format construction (which legitimately uses those); the host
3086    /// sets it for fragment renders. See [`PortableTexEngine::sandbox_reject`].
3087    pub(crate) sandbox: bool,
3088    /// Sandbox bookkeeping: the live MATH nesting depth (`init_math` `+1`,
3089    /// `after_math` `-1`). The wrapper `$` opens depth 1 and the user content stays at
3090    /// depth >= 1; nested math inside a text block (`\hbox{$x$}`, `\text{$y$}`) opens
3091    /// depth 2+. Depth only returns to 0 when the wrapper math is CLOSED -- so a `$` that
3092    /// re-opens math at depth 0 (after `sandbox_math_opened`) is a breakout, while a `$`
3093    /// at depth >= 1 is legitimate nested math.
3094    pub(crate) sandbox_math_depth: i32,
3095    /// Sandbox: set once the wrapper math has opened, so the first depth-0 `init_math`
3096    /// (the wrapper) is allowed but any later depth-0 re-open (a user `$` breakout) is
3097    /// rejected.
3098    pub(crate) sandbox_math_opened: bool,
3099    /// Sandbox work budget: main-control iterations consumed this run, to bound
3100    /// runaway expansion / infinite loops (`\def\x{\x}\x`).
3101    pub(crate) sandbox_ops: u64,
3102}
3103
3104pub(crate) fn zround(value: real) -> integer {
3105    value.round() as integer
3106}
3107
3108impl<'resources> PortableTexEngine<'resources> {
3109    pub fn from_format<R>(
3110        profile: EngineProfile,
3111        format: &PortableFormatImage,
3112        resources: R,
3113    ) -> Self
3114    where
3115        R: ResourceProvider + 'resources,
3116    {
3117        Self {
3118            state: format.instantiate_state(),
3119            profile,
3120            resources: Box::new(resources),
3121            fonts: Box::<EmptyFontPlatform>::default(),
3122            platform: Box::<EmptyPlatform>::default(),
3123            nameoffile_storage: Vec::new(),
3124            native_glyph_infos: std::collections::BTreeMap::new(),
3125            hostbox_records: Vec::new(),
3126            character_protrusions: std::collections::BTreeMap::new(),
3127            resource_requests: 0,
3128            resource_request_records: Vec::new(),
3129            virtual_files: std::collections::BTreeMap::new(),
3130            transcript_bytes: Vec::new(),
3131            current_input_package_owner: None,
3132            stripped_page_builds: 0,
3133            stripped_shipouts: 0,
3134            stripped_special_outputs: 0,
3135            stripped_picture_loads: 0,
3136            stripped_source_specials: 0,
3137            stripped_write_whatsit_diagnostics: 0,
3138            stripped_pdf_extensions: 0,
3139            stripped_page_top_prunes: 0,
3140            last_stripped_shipout_box: None,
3141            fragment_capture_enabled: false,
3142            format_initialization: false,
3143            captured_fragment_root: None,
3144            last_abort_status: None,
3145            last_error_message: None,
3146            sandbox: false,
3147            sandbox_math_depth: 0,
3148            sandbox_math_opened: false,
3149            sandbox_ops: 0,
3150        }
3151    }
3152
3153    pub fn with_font_platform<F>(mut self, fonts: F) -> Self
3154    where
3155        F: FontPlatform + 'resources,
3156    {
3157        self.fonts = Box::new(fonts);
3158        self
3159    }
3160
3161    pub fn with_platform<P>(mut self, platform: P) -> Self
3162    where
3163        P: PortablePlatform + 'resources,
3164    {
3165        self.platform = Box::new(platform);
3166        self
3167    }
3168
3169    pub fn profile(&self) -> EngineProfile {
3170        self.profile
3171    }
3172
3173    pub fn initialize_format_state(self: &mut Self) -> bool {
3174        self.catch_engine_abort(|engine| unsafe {
3175            let this = engine as *mut PortableTexEngine<'_>;
3176            engine.state.allocate_initial_arrays();
3177            engine.initialize();
3178            if engine.supports_etex() {
3179                engine.state.eTeXmode = 1 as eightbits;
3180            }
3181            // `getstringsstarted`, `initprim`, and the two startup-primitive
3182            // methods are abort-reachable (`EngineFlow<..>`), so thread `?`. The
3183            // `abort_engine(this, 1)` path here is the "could not start strings"
3184            // error abort (status 1).
3185            if engine.getstringsstarted()? == 0 {
3186                Self::abort_engine(this, 1 as integer)?;
3187            }
3188            engine.initprim()?;
3189            engine.init_etex_startup_primitives()?;
3190            engine.init_xetex_startup_primitives()?;
3191            engine.register_host_box_primitive()?;
3192            engine.state.initstrptr = engine.state.strptr;
3193            engine.state.initpoolptr = engine.state.poolptr;
3194            // Inter-element math spacing offset. The original computes this in
3195            // `mainbody` (`magicoffset = strstart[math_spacing] - 9*ord_noad`),
3196            // which the importer replaces with this init path, so the assignment
3197            // was lost and `magicoffset` stayed 0 -- making the spacing lookup in
3198            // mlist_to_hlist index arbitrary pool data and trip confusion("mlist4")
3199            // on the first binary operator. The engine loads xetex.pool for every
3200            // profile, so math_spacing is xetex string 784 (66320 - 65536) and
3201            // ord_noad is 16.
3202            engine.state.magicoffset =
3203                (*engine.state.strstart.offset(784) as integer - 9 * 16) as integer;
3204            engine.state.alignstate = 1000000 as integer;
3205            Ok(())
3206        })
3207    }
3208
3209    pub(crate) fn ensure_nativetext_capacity(
3210        engine: &mut PortableTexEngine<'_>,
3211        required: integer,
3212    ) {
3213        let required = required.max(0) as usize;
3214        if engine.state.nativetext_storage.len() < required {
3215            engine.state.nativetext_storage.resize(required, UTF16code::default());
3216        }
3217        engine.state.nativetext = pointer_or_null(&mut engine.state.nativetext_storage);
3218    }
3219
3220    unsafe fn init_etex_startup_primitives(&mut self) -> EngineFlow<()> {
3221        if !self.supports_etex() || self.is_xetex() {
3222            return Ok(());
3223        }
3224        self.state.nonewcontrolsequence = false_0 as boolean;
3225        (&mut *(self as *mut PortableTexEngine<'_>))
3226            .zprimitive(1360 as i32, 70 as i32 as quarterword, 3 as i32)?;
3227        (&mut *(self as *mut PortableTexEngine<'_>))
3228            .zprimitive(1361 as i32, 70 as i32 as quarterword, 6 as i32)?;
3229        (&mut *(self as *mut PortableTexEngine<'_>))
3230            .zprimitive(765 as i32, 108 as i32 as quarterword, 5 as i32)?;
3231        (&mut *(self as *mut PortableTexEngine<'_>))
3232            .zprimitive(1363 as i32, 72 as i32 as quarterword, 25067 as i32)?;
3233        (&mut *(self as *mut PortableTexEngine<'_>))
3234            .zprimitive(1364 as i32, 73 as i32 as quarterword, 27234 as i32)?;
3235        (&mut *(self as *mut PortableTexEngine<'_>))
3236            .zprimitive(1365 as i32, 73 as i32 as quarterword, 27235 as i32)?;
3237        (&mut *(self as *mut PortableTexEngine<'_>))
3238            .zprimitive(1366 as i32, 73 as i32 as quarterword, 27236 as i32)?;
3239        (&mut *(self as *mut PortableTexEngine<'_>))
3240            .zprimitive(1367 as i32, 73 as i32 as quarterword, 27237 as i32)?;
3241        (&mut *(self as *mut PortableTexEngine<'_>))
3242            .zprimitive(1368 as i32, 73 as i32 as quarterword, 27238 as i32)?;
3243        (&mut *(self as *mut PortableTexEngine<'_>))
3244            .zprimitive(1369 as i32, 73 as i32 as quarterword, 27239 as i32)?;
3245        (&mut *(self as *mut PortableTexEngine<'_>))
3246            .zprimitive(1370 as i32, 73 as i32 as quarterword, 27240 as i32)?;
3247        (&mut *(self as *mut PortableTexEngine<'_>))
3248            .zprimitive(1371 as i32, 73 as i32 as quarterword, 27241 as i32)?;
3249        (&mut *(self as *mut PortableTexEngine<'_>))
3250            .zprimitive(1372 as i32, 73 as i32 as quarterword, 27242 as i32)?;
3251        (&mut *(self as *mut PortableTexEngine<'_>))
3252            .zprimitive(1387 as i32, 70 as i32 as quarterword, 7 as i32)?;
3253        (&mut *(self as *mut PortableTexEngine<'_>))
3254            .zprimitive(1388 as i32, 70 as i32 as quarterword, 8 as i32)?;
3255        (&mut *(self as *mut PortableTexEngine<'_>))
3256            .zprimitive(1389 as i32, 70 as i32 as quarterword, 9 as i32)?;
3257        (&mut *(self as *mut PortableTexEngine<'_>))
3258            .zprimitive(1390 as i32, 70 as i32 as quarterword, 10 as i32)?;
3259        (&mut *(self as *mut PortableTexEngine<'_>))
3260            .zprimitive(1391 as i32, 70 as i32 as quarterword, 11 as i32)?;
3261        (&mut *(self as *mut PortableTexEngine<'_>))
3262            .zprimitive(1392 as i32, 70 as i32 as quarterword, 14 as i32)?;
3263        (&mut *(self as *mut PortableTexEngine<'_>))
3264            .zprimitive(1393 as i32, 70 as i32 as quarterword, 15 as i32)?;
3265        (&mut *(self as *mut PortableTexEngine<'_>))
3266            .zprimitive(1394 as i32, 70 as i32 as quarterword, 16 as i32)?;
3267        (&mut *(self as *mut PortableTexEngine<'_>))
3268            .zprimitive(1395 as i32, 70 as i32 as quarterword, 17 as i32)?;
3269        (&mut *(self as *mut PortableTexEngine<'_>))
3270            .zprimitive(1396 as i32, 70 as i32 as quarterword, 18 as i32)?;
3271        (&mut *(self as *mut PortableTexEngine<'_>))
3272            .zprimitive(1397 as i32, 70 as i32 as quarterword, 19 as i32)?;
3273        (&mut *(self as *mut PortableTexEngine<'_>))
3274            .zprimitive(1398 as i32, 70 as i32 as quarterword, 20 as i32)?;
3275        (&mut *(self as *mut PortableTexEngine<'_>))
3276            .zprimitive(1399 as i32, 19 as i32 as quarterword, 4 as i32)?;
3277        (&mut *(self as *mut PortableTexEngine<'_>))
3278            .zprimitive(1401 as i32, 19 as i32 as quarterword, 5 as i32)?;
3279        (&mut *(self as *mut PortableTexEngine<'_>))
3280            .zprimitive(1402 as i32, 109 as i32 as quarterword, 1 as i32)?;
3281        (&mut *(self as *mut PortableTexEngine<'_>))
3282            .zprimitive(1403 as i32, 109 as i32 as quarterword, 5 as i32)?;
3283        (&mut *(self as *mut PortableTexEngine<'_>))
3284            .zprimitive(1404 as i32, 19 as i32 as quarterword, 6 as i32)?;
3285        (&mut *(self as *mut PortableTexEngine<'_>))
3286            .zprimitive(1408 as i32, 82 as i32 as quarterword, 2 as i32)?;
3287        (&mut *(self as *mut PortableTexEngine<'_>))
3288            .zprimitive(908 as i32, 49 as i32 as quarterword, 1 as i32)?;
3289        (&mut *(self as *mut PortableTexEngine<'_>))
3290            .zprimitive(1412 as i32, 73 as i32 as quarterword, 27243 as i32)?;
3291        (&mut *(self as *mut PortableTexEngine<'_>))
3292            .zprimitive(1413 as i32, 33 as i32 as quarterword, 6 as i32)?;
3293        (&mut *(self as *mut PortableTexEngine<'_>))
3294            .zprimitive(1414 as i32, 33 as i32 as quarterword, 7 as i32)?;
3295        (&mut *(self as *mut PortableTexEngine<'_>))
3296            .zprimitive(1415 as i32, 33 as i32 as quarterword, 10 as i32)?;
3297        (&mut *(self as *mut PortableTexEngine<'_>))
3298            .zprimitive(1416 as i32, 33 as i32 as quarterword, 11 as i32)?;
3299        (&mut *(self as *mut PortableTexEngine<'_>))
3300            .zprimitive(1425 as i32, 104 as i32 as quarterword, 2 as i32)?;
3301        (&mut *(self as *mut PortableTexEngine<'_>))
3302            .zprimitive(1427 as i32, 96 as i32 as quarterword, 1 as i32)?;
3303        (&mut *(self as *mut PortableTexEngine<'_>))
3304            .zprimitive(799 as i32, 102 as i32 as quarterword, 1 as i32)?;
3305        (&mut *(self as *mut PortableTexEngine<'_>))
3306            .zprimitive(1428 as i32, 105 as i32 as quarterword, 17 as i32)?;
3307        (&mut *(self as *mut PortableTexEngine<'_>))
3308            .zprimitive(1429 as i32, 105 as i32 as quarterword, 18 as i32)?;
3309        (&mut *(self as *mut PortableTexEngine<'_>))
3310            .zprimitive(1430 as i32, 105 as i32 as quarterword, 19 as i32)?;
3311        (&mut *(self as *mut PortableTexEngine<'_>))
3312            .zprimitive(1217 as i32, 93 as i32 as quarterword, 8 as i32)?;
3313        (&mut *(self as *mut PortableTexEngine<'_>))
3314            .zprimitive(1436 as i32, 70 as i32 as quarterword, 25 as i32)?;
3315        (&mut *(self as *mut PortableTexEngine<'_>))
3316            .zprimitive(1437 as i32, 70 as i32 as quarterword, 26 as i32)?;
3317        (&mut *(self as *mut PortableTexEngine<'_>))
3318            .zprimitive(1438 as i32, 70 as i32 as quarterword, 27 as i32)?;
3319        (&mut *(self as *mut PortableTexEngine<'_>))
3320            .zprimitive(1439 as i32, 70 as i32 as quarterword, 28 as i32)?;
3321        (&mut *(self as *mut PortableTexEngine<'_>))
3322            .zprimitive(1443 as i32, 70 as i32 as quarterword, 12 as i32)?;
3323        (&mut *(self as *mut PortableTexEngine<'_>))
3324            .zprimitive(1444 as i32, 70 as i32 as quarterword, 13 as i32)?;
3325        (&mut *(self as *mut PortableTexEngine<'_>))
3326            .zprimitive(1445 as i32, 70 as i32 as quarterword, 21 as i32)?;
3327        (&mut *(self as *mut PortableTexEngine<'_>))
3328            .zprimitive(1446 as i32, 70 as i32 as quarterword, 22 as i32)?;
3329        (&mut *(self as *mut PortableTexEngine<'_>))
3330            .zprimitive(1447 as i32, 70 as i32 as quarterword, 23 as i32)?;
3331        (&mut *(self as *mut PortableTexEngine<'_>))
3332            .zprimitive(1448 as i32, 70 as i32 as quarterword, 24 as i32)?;
3333        (&mut *(self as *mut PortableTexEngine<'_>))
3334            .zprimitive(1449 as i32, 18 as i32 as quarterword, 5 as i32)?;
3335        (&mut *(self as *mut PortableTexEngine<'_>))
3336            .zprimitive(1450 as i32, 110 as i32 as quarterword, 5 as i32)?;
3337        (&mut *(self as *mut PortableTexEngine<'_>))
3338            .zprimitive(1451 as i32, 110 as i32 as quarterword, 6 as i32)?;
3339        (&mut *(self as *mut PortableTexEngine<'_>))
3340            .zprimitive(1452 as i32, 110 as i32 as quarterword, 7 as i32)?;
3341        (&mut *(self as *mut PortableTexEngine<'_>))
3342            .zprimitive(1453 as i32, 110 as i32 as quarterword, 8 as i32)?;
3343        (&mut *(self as *mut PortableTexEngine<'_>))
3344            .zprimitive(1454 as i32, 110 as i32 as quarterword, 9 as i32)?;
3345        (&mut *(self as *mut PortableTexEngine<'_>))
3346            .zprimitive(1458 as i32, 24 as i32 as quarterword, 2 as i32)?;
3347        (&mut *(self as *mut PortableTexEngine<'_>))
3348            .zprimitive(1459 as i32, 24 as i32 as quarterword, 3 as i32)?;
3349        (&mut *(self as *mut PortableTexEngine<'_>))
3350            .zprimitive(1460 as i32, 84 as i32 as quarterword, 25324 as i32)?;
3351        (&mut *(self as *mut PortableTexEngine<'_>))
3352            .zprimitive(1461 as i32, 84 as i32 as quarterword, 25325 as i32)?;
3353        (&mut *(self as *mut PortableTexEngine<'_>))
3354            .zprimitive(1462 as i32, 84 as i32 as quarterword, 25326 as i32)?;
3355        (&mut *(self as *mut PortableTexEngine<'_>))
3356            .zprimitive(1463 as i32, 84 as i32 as quarterword, 25327 as i32)?;
3357        self.state.eTeXmode = 1 as eightbits;
3358        Ok(())
3359    }
3360    unsafe fn init_xetex_startup_primitives(&mut self) -> EngineFlow<()> {
3361        if !self.is_xetex() {
3362            return Ok(());
3363        }
3364        self.state.nonewcontrolsequence = false_0 as boolean;
3365        (&mut *(self as *mut PortableTexEngine<'_>))
3366            .zprimitive(66755 as i64 as strnumber, 59 as i32 as quarterword, 41 as i32)?;
3367        (&mut *(self as *mut PortableTexEngine<'_>))
3368            .zprimitive(66756 as i64 as strnumber, 59 as i32 as quarterword, 42 as i32)?;
3369        (&mut *(self as *mut PortableTexEngine<'_>))
3370            .zprimitive(66757 as i64 as strnumber, 59 as i32 as quarterword, 43 as i32)?;
3371        (&mut *(self as *mut PortableTexEngine<'_>))
3372            .zprimitive(66758 as i64 as strnumber, 59 as i32 as quarterword, 46 as i32)?;
3373        (&mut *(self as *mut PortableTexEngine<'_>))
3374            .zprimitive(
3375                66759 as i64 as strnumber,
3376                73 as i32 as quarterword,
3377                1206306 as i64 as halfword,
3378            )?;
3379        (&mut *(self as *mut PortableTexEngine<'_>))
3380            .zprimitive(66760 as i64 as strnumber, 59 as i32 as quarterword, 23 as i32)?;
3381        (&mut *(self as *mut PortableTexEngine<'_>))
3382            .zprimitive(66816 as i64 as strnumber, 71 as i32 as quarterword, 3 as i32)?;
3383        (&mut *(self as *mut PortableTexEngine<'_>))
3384            .zprimitive(66817 as i64 as strnumber, 71 as i32 as quarterword, 19 as i32)?;
3385        (&mut *(self as *mut PortableTexEngine<'_>))
3386            .zprimitive(66115 as i64 as strnumber, 111 as i32 as quarterword, 5 as i32)?;
3387        (&mut *(self as *mut PortableTexEngine<'_>))
3388            .zprimitive(66818 as i64 as strnumber, 71 as i32 as quarterword, 27 as i32)?;
3389        (&mut *(self as *mut PortableTexEngine<'_>))
3390            .zprimitive(
3391                66819 as i64 as strnumber,
3392                111 as i32 as quarterword,
3393                33 as i32,
3394            )?;
3395        (&mut *(self as *mut PortableTexEngine<'_>))
3396            .zprimitive(66820 as i64 as strnumber, 71 as i32 as quarterword, 28 as i32)?;
3397        (&mut *(self as *mut PortableTexEngine<'_>))
3398            .zprimitive(66821 as i64 as strnumber, 71 as i32 as quarterword, 29 as i32)?;
3399        (&mut *(self as *mut PortableTexEngine<'_>))
3400            .zprimitive(66822 as i64 as strnumber, 71 as i32 as quarterword, 30 as i32)?;
3401        (&mut *(self as *mut PortableTexEngine<'_>))
3402            .zprimitive(66823 as i64 as strnumber, 71 as i32 as quarterword, 31 as i32)?;
3403        (&mut *(self as *mut PortableTexEngine<'_>))
3404            .zprimitive(66824 as i64 as strnumber, 71 as i32 as quarterword, 32 as i32)?;
3405        (&mut *(self as *mut PortableTexEngine<'_>))
3406            .zprimitive(66825 as i64 as strnumber, 71 as i32 as quarterword, 33 as i32)?;
3407        (&mut *(self as *mut PortableTexEngine<'_>))
3408            .zprimitive(66826 as i64 as strnumber, 71 as i32 as quarterword, 34 as i32)?;
3409        (&mut *(self as *mut PortableTexEngine<'_>))
3410            .zprimitive(66827 as i64 as strnumber, 71 as i32 as quarterword, 35 as i32)?;
3411        (&mut *(self as *mut PortableTexEngine<'_>))
3412            .zprimitive(66828 as i64 as strnumber, 71 as i32 as quarterword, 36 as i32)?;
3413        (&mut *(self as *mut PortableTexEngine<'_>))
3414            .zprimitive(66829 as i64 as strnumber, 71 as i32 as quarterword, 37 as i32)?;
3415        (&mut *(self as *mut PortableTexEngine<'_>))
3416            .zprimitive(66830 as i64 as strnumber, 71 as i32 as quarterword, 38 as i32)?;
3417        (&mut *(self as *mut PortableTexEngine<'_>))
3418            .zprimitive(66831 as i64 as strnumber, 71 as i32 as quarterword, 39 as i32)?;
3419        (&mut *(self as *mut PortableTexEngine<'_>))
3420            .zprimitive(66832 as i64 as strnumber, 71 as i32 as quarterword, 40 as i32)?;
3421        (&mut *(self as *mut PortableTexEngine<'_>))
3422            .zprimitive(66833 as i64 as strnumber, 71 as i32 as quarterword, 41 as i32)?;
3423        (&mut *(self as *mut PortableTexEngine<'_>))
3424            .zprimitive(66834 as i64 as strnumber, 71 as i32 as quarterword, 42 as i32)?;
3425        (&mut *(self as *mut PortableTexEngine<'_>))
3426            .zprimitive(
3427                66835 as i64 as strnumber,
3428                111 as i32 as quarterword,
3429                34 as i32,
3430            )?;
3431        (&mut *(self as *mut PortableTexEngine<'_>))
3432            .zprimitive(
3433                66836 as i64 as strnumber,
3434                111 as i32 as quarterword,
3435                35 as i32,
3436            )?;
3437        (&mut *(self as *mut PortableTexEngine<'_>))
3438            .zprimitive(
3439                66837 as i64 as strnumber,
3440                111 as i32 as quarterword,
3441                36 as i32,
3442            )?;
3443        (&mut *(self as *mut PortableTexEngine<'_>))
3444            .zprimitive(66838 as i64 as strnumber, 71 as i32 as quarterword, 43 as i32)?;
3445        (&mut *(self as *mut PortableTexEngine<'_>))
3446            .zprimitive(66839 as i64 as strnumber, 71 as i32 as quarterword, 44 as i32)?;
3447        (&mut *(self as *mut PortableTexEngine<'_>))
3448            .zprimitive(66840 as i64 as strnumber, 71 as i32 as quarterword, 45 as i32)?;
3449        (&mut *(self as *mut PortableTexEngine<'_>))
3450            .zprimitive(66841 as i64 as strnumber, 71 as i32 as quarterword, 46 as i32)?;
3451        (&mut *(self as *mut PortableTexEngine<'_>))
3452            .zprimitive(66842 as i64 as strnumber, 71 as i32 as quarterword, 47 as i32)?;
3453        (&mut *(self as *mut PortableTexEngine<'_>))
3454            .zprimitive(66843 as i64 as strnumber, 71 as i32 as quarterword, 48 as i32)?;
3455        (&mut *(self as *mut PortableTexEngine<'_>))
3456            .zprimitive(66844 as i64 as strnumber, 71 as i32 as quarterword, 49 as i32)?;
3457        (&mut *(self as *mut PortableTexEngine<'_>))
3458            .zprimitive(66845 as i64 as strnumber, 71 as i32 as quarterword, 50 as i32)?;
3459        (&mut *(self as *mut PortableTexEngine<'_>))
3460            .zprimitive(66846 as i64 as strnumber, 71 as i32 as quarterword, 55 as i32)?;
3461        (&mut *(self as *mut PortableTexEngine<'_>))
3462            .zprimitive(
3463                66847 as i64 as strnumber,
3464                111 as i32 as quarterword,
3465                37 as i32,
3466            )?;
3467        (&mut *(self as *mut PortableTexEngine<'_>))
3468            .zprimitive(66848 as i64 as strnumber, 71 as i32 as quarterword, 51 as i32)?;
3469        (&mut *(self as *mut PortableTexEngine<'_>))
3470            .zprimitive(66849 as i64 as strnumber, 71 as i32 as quarterword, 52 as i32)?;
3471        (&mut *(self as *mut PortableTexEngine<'_>))
3472            .zprimitive(66850 as i64 as strnumber, 71 as i32 as quarterword, 53 as i32)?;
3473        (&mut *(self as *mut PortableTexEngine<'_>))
3474            .zprimitive(66851 as i64 as strnumber, 71 as i32 as quarterword, 54 as i32)?;
3475        (&mut *(self as *mut PortableTexEngine<'_>))
3476            .zprimitive(
3477                66861 as i64 as strnumber,
3478                73 as i32 as quarterword,
3479                1206305 as i64 as halfword,
3480            )?;
3481        (&mut *(self as *mut PortableTexEngine<'_>))
3482            .zprimitive(
3483                66862 as i64 as strnumber,
3484                74 as i32 as quarterword,
3485                7892325 as i64 as halfword,
3486            )?;
3487        (&mut *(self as *mut PortableTexEngine<'_>))
3488            .zprimitive(
3489                66863 as i64 as strnumber,
3490                74 as i32 as quarterword,
3491                7892326 as i64 as halfword,
3492            )?;
3493        (&mut *(self as *mut PortableTexEngine<'_>))
3494            .zprimitive(
3495                66864 as i64 as strnumber,
3496                74 as i32 as quarterword,
3497                7892327 as i64 as halfword,
3498            )?;
3499        (&mut *(self as *mut PortableTexEngine<'_>))
3500            .zprimitive(
3501                66865 as i64 as strnumber,
3502                74 as i32 as quarterword,
3503                7892328 as i64 as halfword,
3504            )?;
3505        (&mut *(self as *mut PortableTexEngine<'_>))
3506            .zprimitive(
3507                66866 as i64 as strnumber,
3508                74 as i32 as quarterword,
3509                7892329 as i64 as halfword,
3510            )?;
3511        (&mut *(self as *mut PortableTexEngine<'_>))
3512            .zprimitive(
3513                66867 as i64 as strnumber,
3514                74 as i32 as quarterword,
3515                7892330 as i64 as halfword,
3516            )?;
3517        (&mut *(self as *mut PortableTexEngine<'_>))
3518            .zprimitive(
3519                66868 as i64 as strnumber,
3520                74 as i32 as quarterword,
3521                7892331 as i64 as halfword,
3522            )?;
3523        (&mut *(self as *mut PortableTexEngine<'_>))
3524            .zprimitive(
3525                66869 as i64 as strnumber,
3526                74 as i32 as quarterword,
3527                7892332 as i64 as halfword,
3528            )?;
3529        (&mut *(self as *mut PortableTexEngine<'_>))
3530            .zprimitive(
3531                66870 as i64 as strnumber,
3532                74 as i32 as quarterword,
3533                7892333 as i64 as halfword,
3534            )?;
3535        (&mut *(self as *mut PortableTexEngine<'_>))
3536            .zprimitive(
3537                66871 as i64 as strnumber,
3538                74 as i32 as quarterword,
3539                7892335 as i64 as halfword,
3540            )?;
3541        (&mut *(self as *mut PortableTexEngine<'_>))
3542            .zprimitive(66885 as i64 as strnumber, 71 as i32 as quarterword, 20 as i32)?;
3543        (&mut *(self as *mut PortableTexEngine<'_>))
3544            .zprimitive(66886 as i64 as strnumber, 71 as i32 as quarterword, 21 as i32)?;
3545        (&mut *(self as *mut PortableTexEngine<'_>))
3546            .zprimitive(66887 as i64 as strnumber, 71 as i32 as quarterword, 22 as i32)?;
3547        (&mut *(self as *mut PortableTexEngine<'_>))
3548            .zprimitive(66888 as i64 as strnumber, 71 as i32 as quarterword, 23 as i32)?;
3549        (&mut *(self as *mut PortableTexEngine<'_>))
3550            .zprimitive(66889 as i64 as strnumber, 71 as i32 as quarterword, 24 as i32)?;
3551        (&mut *(self as *mut PortableTexEngine<'_>))
3552            .zprimitive(66890 as i64 as strnumber, 71 as i32 as quarterword, 56 as i32)?;
3553        (&mut *(self as *mut PortableTexEngine<'_>))
3554            .zprimitive(66891 as i64 as strnumber, 71 as i32 as quarterword, 57 as i32)?;
3555        (&mut *(self as *mut PortableTexEngine<'_>))
3556            .zprimitive(66892 as i64 as strnumber, 71 as i32 as quarterword, 58 as i32)?;
3557        (&mut *(self as *mut PortableTexEngine<'_>))
3558            .zprimitive(66893 as i64 as strnumber, 71 as i32 as quarterword, 59 as i32)?;
3559        (&mut *(self as *mut PortableTexEngine<'_>))
3560            .zprimitive(66894 as i64 as strnumber, 71 as i32 as quarterword, 60 as i32)?;
3561        (&mut *(self as *mut PortableTexEngine<'_>))
3562            .zprimitive(66895 as i64 as strnumber, 71 as i32 as quarterword, 61 as i32)?;
3563        (&mut *(self as *mut PortableTexEngine<'_>))
3564            .zprimitive(66896 as i64 as strnumber, 71 as i32 as quarterword, 62 as i32)?;
3565        (&mut *(self as *mut PortableTexEngine<'_>))
3566            .zprimitive(66897 as i64 as strnumber, 19 as i32 as quarterword, 4 as i32)?;
3567        (&mut *(self as *mut PortableTexEngine<'_>))
3568            .zprimitive(66899 as i64 as strnumber, 19 as i32 as quarterword, 5 as i32)?;
3569        (&mut *(self as *mut PortableTexEngine<'_>))
3570            .zprimitive(66900 as i64 as strnumber, 112 as i32 as quarterword, 1 as i32)?;
3571        (&mut *(self as *mut PortableTexEngine<'_>))
3572            .zprimitive(66901 as i64 as strnumber, 112 as i32 as quarterword, 5 as i32)?;
3573        (&mut *(self as *mut PortableTexEngine<'_>))
3574            .zprimitive(66902 as i64 as strnumber, 19 as i32 as quarterword, 6 as i32)?;
3575        (&mut *(self as *mut PortableTexEngine<'_>))
3576            .zprimitive(66906 as i64 as strnumber, 83 as i32 as quarterword, 2 as i32)?;
3577        (&mut *(self as *mut PortableTexEngine<'_>))
3578            .zprimitive(66288 as i64 as strnumber, 49 as i32 as quarterword, 1 as i32)?;
3579        (&mut *(self as *mut PortableTexEngine<'_>))
3580            .zprimitive(
3581                66910 as i64 as strnumber,
3582                74 as i32 as quarterword,
3583                7892334 as i64 as halfword,
3584            )?;
3585        (&mut *(self as *mut PortableTexEngine<'_>))
3586            .zprimitive(
3587                66911 as i64 as strnumber,
3588                74 as i32 as quarterword,
3589                7892339 as i64 as halfword,
3590            )?;
3591        (&mut *(self as *mut PortableTexEngine<'_>))
3592            .zprimitive(
3593                66912 as i64 as strnumber,
3594                74 as i32 as quarterword,
3595                7892341 as i64 as halfword,
3596            )?;
3597        (&mut *(self as *mut PortableTexEngine<'_>))
3598            .zprimitive(
3599                66913 as i64 as strnumber,
3600                74 as i32 as quarterword,
3601                7892342 as i64 as halfword,
3602            )?;
3603        (&mut *(self as *mut PortableTexEngine<'_>))
3604            .zprimitive(
3605                66914 as i64 as strnumber,
3606                74 as i32 as quarterword,
3607                7892343 as i64 as halfword,
3608            )?;
3609        (&mut *(self as *mut PortableTexEngine<'_>))
3610            .zprimitive(
3611                66915 as i64 as strnumber,
3612                74 as i32 as quarterword,
3613                7892340 as i64 as halfword,
3614            )?;
3615        (&mut *(self as *mut PortableTexEngine<'_>))
3616            .zprimitive(
3617                66916 as i64 as strnumber,
3618                74 as i32 as quarterword,
3619                7892344 as i64 as halfword,
3620            )?;
3621        (&mut *(self as *mut PortableTexEngine<'_>))
3622            .zprimitive(
3623                66917 as i64 as strnumber,
3624                74 as i32 as quarterword,
3625                7892347 as i64 as halfword,
3626            )?;
3627        (&mut *(self as *mut PortableTexEngine<'_>))
3628            .zprimitive(
3629                66918 as i64 as strnumber,
3630                74 as i32 as quarterword,
3631                7892348 as i64 as halfword,
3632            )?;
3633        (&mut *(self as *mut PortableTexEngine<'_>))
3634            .zprimitive(
3635                66919 as i64 as strnumber,
3636                74 as i32 as quarterword,
3637                7892349 as i64 as halfword,
3638            )?;
3639        (&mut *(self as *mut PortableTexEngine<'_>))
3640            .zprimitive(
3641                66920 as i64 as strnumber,
3642                74 as i32 as quarterword,
3643                7892350 as i64 as halfword,
3644            )?;
3645        (&mut *(self as *mut PortableTexEngine<'_>))
3646            .zprimitive(66761 as i64 as strnumber, 59 as i32 as quarterword, 44 as i32)?;
3647        (&mut *(self as *mut PortableTexEngine<'_>))
3648            .zprimitive(66762 as i64 as strnumber, 59 as i32 as quarterword, 45 as i32)?;
3649        (&mut *(self as *mut PortableTexEngine<'_>))
3650            .zprimitive(66921 as i64 as strnumber, 33 as i32 as quarterword, 6 as i32)?;
3651        (&mut *(self as *mut PortableTexEngine<'_>))
3652            .zprimitive(66922 as i64 as strnumber, 33 as i32 as quarterword, 7 as i32)?;
3653        (&mut *(self as *mut PortableTexEngine<'_>))
3654            .zprimitive(66923 as i64 as strnumber, 33 as i32 as quarterword, 10 as i32)?;
3655        (&mut *(self as *mut PortableTexEngine<'_>))
3656            .zprimitive(66924 as i64 as strnumber, 33 as i32 as quarterword, 11 as i32)?;
3657        (&mut *(self as *mut PortableTexEngine<'_>))
3658            .zprimitive(66933 as i64 as strnumber, 107 as i32 as quarterword, 2 as i32)?;
3659        (&mut *(self as *mut PortableTexEngine<'_>))
3660            .zprimitive(66935 as i64 as strnumber, 98 as i32 as quarterword, 1 as i32)?;
3661        (&mut *(self as *mut PortableTexEngine<'_>))
3662            .zprimitive(66164 as i64 as strnumber, 105 as i32 as quarterword, 1 as i32)?;
3663        (&mut *(self as *mut PortableTexEngine<'_>))
3664            .zprimitive(
3665                66936 as i64 as strnumber,
3666                108 as i32 as quarterword,
3667                17 as i32,
3668            )?;
3669        (&mut *(self as *mut PortableTexEngine<'_>))
3670            .zprimitive(
3671                66937 as i64 as strnumber,
3672                108 as i32 as quarterword,
3673                18 as i32,
3674            )?;
3675        (&mut *(self as *mut PortableTexEngine<'_>))
3676            .zprimitive(
3677                66938 as i64 as strnumber,
3678                108 as i32 as quarterword,
3679                19 as i32,
3680            )?;
3681        (&mut *(self as *mut PortableTexEngine<'_>))
3682            .zprimitive(
3683                66939 as i64 as strnumber,
3684                108 as i32 as quarterword,
3685                20 as i32,
3686            )?;
3687        (&mut *(self as *mut PortableTexEngine<'_>))
3688            .zprimitive(66623 as i64 as strnumber, 95 as i32 as quarterword, 8 as i32)?;
3689        (&mut *(self as *mut PortableTexEngine<'_>))
3690            .zprimitive(66945 as i64 as strnumber, 71 as i32 as quarterword, 67 as i32)?;
3691        (&mut *(self as *mut PortableTexEngine<'_>))
3692            .zprimitive(66946 as i64 as strnumber, 71 as i32 as quarterword, 68 as i32)?;
3693        (&mut *(self as *mut PortableTexEngine<'_>))
3694            .zprimitive(66947 as i64 as strnumber, 71 as i32 as quarterword, 69 as i32)?;
3695        (&mut *(self as *mut PortableTexEngine<'_>))
3696            .zprimitive(66948 as i64 as strnumber, 71 as i32 as quarterword, 70 as i32)?;
3697        (&mut *(self as *mut PortableTexEngine<'_>))
3698            .zprimitive(66952 as i64 as strnumber, 71 as i32 as quarterword, 25 as i32)?;
3699        (&mut *(self as *mut PortableTexEngine<'_>))
3700            .zprimitive(66953 as i64 as strnumber, 71 as i32 as quarterword, 26 as i32)?;
3701        (&mut *(self as *mut PortableTexEngine<'_>))
3702            .zprimitive(66954 as i64 as strnumber, 71 as i32 as quarterword, 63 as i32)?;
3703        (&mut *(self as *mut PortableTexEngine<'_>))
3704            .zprimitive(66955 as i64 as strnumber, 71 as i32 as quarterword, 64 as i32)?;
3705        (&mut *(self as *mut PortableTexEngine<'_>))
3706            .zprimitive(66956 as i64 as strnumber, 71 as i32 as quarterword, 65 as i32)?;
3707        (&mut *(self as *mut PortableTexEngine<'_>))
3708            .zprimitive(66957 as i64 as strnumber, 71 as i32 as quarterword, 66 as i32)?;
3709        (&mut *(self as *mut PortableTexEngine<'_>))
3710            .zprimitive(66958 as i64 as strnumber, 18 as i32 as quarterword, 5 as i32)?;
3711        (&mut *(self as *mut PortableTexEngine<'_>))
3712            .zprimitive(66959 as i64 as strnumber, 113 as i32 as quarterword, 5 as i32)?;
3713        (&mut *(self as *mut PortableTexEngine<'_>))
3714            .zprimitive(66960 as i64 as strnumber, 113 as i32 as quarterword, 6 as i32)?;
3715        (&mut *(self as *mut PortableTexEngine<'_>))
3716            .zprimitive(66961 as i64 as strnumber, 113 as i32 as quarterword, 7 as i32)?;
3717        (&mut *(self as *mut PortableTexEngine<'_>))
3718            .zprimitive(66962 as i64 as strnumber, 113 as i32 as quarterword, 8 as i32)?;
3719        (&mut *(self as *mut PortableTexEngine<'_>))
3720            .zprimitive(66963 as i64 as strnumber, 113 as i32 as quarterword, 9 as i32)?;
3721        (&mut *(self as *mut PortableTexEngine<'_>))
3722            .zprimitive(66968 as i64 as strnumber, 24 as i32 as quarterword, 2 as i32)?;
3723        (&mut *(self as *mut PortableTexEngine<'_>))
3724            .zprimitive(66969 as i64 as strnumber, 24 as i32 as quarterword, 3 as i32)?;
3725        (&mut *(self as *mut PortableTexEngine<'_>))
3726            .zprimitive(
3727                66970 as i64 as strnumber,
3728                85 as i32 as quarterword,
3729                1206563 as i64 as halfword,
3730            )?;
3731        (&mut *(self as *mut PortableTexEngine<'_>))
3732            .zprimitive(
3733                66971 as i64 as strnumber,
3734                85 as i32 as quarterword,
3735                1206564 as i64 as halfword,
3736            )?;
3737        (&mut *(self as *mut PortableTexEngine<'_>))
3738            .zprimitive(
3739                66972 as i64 as strnumber,
3740                85 as i32 as quarterword,
3741                1206565 as i64 as halfword,
3742            )?;
3743        (&mut *(self as *mut PortableTexEngine<'_>))
3744            .zprimitive(
3745                66973 as i64 as strnumber,
3746                85 as i32 as quarterword,
3747                1206566 as i64 as halfword,
3748            )?;
3749        if *self.state.buffer.offset(self.state.curinput.locfield as isize) == 42 as i32
3750        {
3751            self.state.curinput.locfield += 1;
3752        }
3753        self.state.eTeXmode = 1 as eightbits;
3754        self.state.maxregnum = 32767 as i32 as halfword;
3755        self.state.maxreghelpline = 66965 as i64 as strnumber;
3756        Ok(())
3757    }
3758
3759    pub fn begin_primary_input(self: &mut Self, name: &str, bytes: Vec<u8>) -> bool {
3760        // Catch point: `begin_primary_input_raw` now propagates aborts as
3761        // `Err(EngineAbort)` (it threads `beginfilereading`/`firmuptheline`).
3762        // The public API stays a plain `bool`, so consume the `Result` here: an
3763        // abort during input setup means the input could not be started.
3764        self.last_abort_status = None;
3765        self.last_error_message = None;
3766        match unsafe { self.begin_primary_input_raw(name, bytes) } {
3767            Ok(started) => started != 0,
3768            Err(EngineBreak::Abort(EngineAbort { status })) => {
3769                self.last_abort_status = Some(status);
3770                false
3771            }
3772            Err(EngineBreak::Error(error)) => {
3773                self.last_error_message = Some(error.message);
3774                false
3775            }
3776        }
3777    }
3778
3779    pub fn run_main_control(self: &mut Self) -> bool {
3780        self.catch_engine_abort(|engine| unsafe { engine.maincontrol() })
3781    }
3782
3783    pub fn run_format_initialization(self: &mut Self) -> bool {
3784        self.format_initialization = true;
3785        let completed = self.catch_engine_abort(|engine| unsafe { engine.maincontrol() });
3786        self.format_initialization = false;
3787        completed
3788    }
3789
3790    pub fn begin_fragment_capture(self: &mut Self) {
3791        self.fragment_capture_enabled = true;
3792        self.captured_fragment_root = None;
3793        // Record indices in marker nodes are per fragment, a reused engine must not accumulate.
3794        self.hostbox_records.clear();
3795    }
3796
3797    pub fn end_fragment_capture(self: &mut Self) {
3798        self.fragment_capture_enabled = false;
3799    }
3800
3801    fn catch_engine_abort<F>(self: &mut Self, run: F) -> bool
3802    where
3803        F: FnOnce(&mut Self) -> EngineFlow<()>,
3804    {
3805        // Non-unwinding abort boundary. The driver closure threads any fatal
3806        // `jump_out`/`fatal_error`/`overflow` back as `Err(EngineAbort)` via the
3807        // `?` operator instead of `panic_any`, so the engine runs under
3808        // `panic=abort`. Status mapping is identical to the old `catch_unwind`
3809        // path: status 0 (normal `\end`/dump) => "completed"; nonzero => abort.
3810        self.last_abort_status = None;
3811        self.last_error_message = None;
3812        match run(self) {
3813            Ok(()) => self.last_abort_status.is_none(),
3814            Err(EngineBreak::Abort(EngineAbort { status: 0 })) => {
3815                self.last_abort_status = None;
3816                true
3817            }
3818            Err(EngineBreak::Abort(EngineAbort { status })) => {
3819                self.last_abort_status = Some(status);
3820                false
3821            }
3822            // A surfaced TeX error (or sandbox violation): record its message so
3823            // the host can report it, and treat the run as not completed.
3824            Err(EngineBreak::Error(error)) => {
3825                self.last_error_message = Some(error.message);
3826                false
3827            }
3828        }
3829    }
3830
3831    pub fn snapshot_format(&self) -> PortableFormatImage {
3832        PortableFormatImage::from_engine_state(self.state.as_ref())
3833    }
3834
3835    /// Consume this engine and seal its state *in place* as a format snapshot,
3836    /// moving the `Box<PortableTexState>` instead of deep-cloning it the way
3837    /// [`Self::snapshot_format`] does. Building a format cache normally holds the
3838    /// freshly-initialized engine (~hundreds of MB of `mem`/`eqtb`/`hash`) and a
3839    /// full clone of it at the same time — a transient ~2x spike. When the caller
3840    /// owns the engine and discards it right after snapshotting (the
3841    /// `GeneratedFormatCache::initialized` / preload paths), moving the state
3842    /// avoids the clone and halves that peak.
3843    #[must_use]
3844    pub fn into_format(self) -> PortableFormatImage {
3845        // NOTE: do NOT `finalize_trie()` here. `into_format` is also used for the
3846        // base format that further packages (`\patterns`) are loaded on top of;
3847        // packing the trie is a one-way door ("! Too late for \patterns"). Callers
3848        // that have produced the *final* format call `finalize_trie()` explicitly
3849        // first (see `generated_format_for`, wasm `build_format`).
3850        let mut state = self.state;
3851        state.seal_as_format_snapshot();
3852        PortableFormatImage::from_sealed_state(state)
3853    }
3854
3855    /// Pack the hyphenation trie now, so its construction scratch can be freed
3856    /// when the format is sealed. This engine packs the trie lazily on the first
3857    /// runtime hyphenation (`inittrie` is a no-op while `format_initialization`);
3858    /// doing it here — once the *final* format is built, exactly like TeX's
3859    /// `\dump` — yields an identical packed trie and lets `seal_as_format_snapshot`
3860    /// drop the ~24 MB of builder scratch arrays. Only call this when no further
3861    /// `\patterns` will be loaded.
3862    pub fn finalize_trie(self: &mut Self) {
3863        // Only when the trie is unpacked AND its scratch is still present.
3864        if self.state.trienotready != 0 && !self.state.triehash.is_null() {
3865            let saved = self.format_initialization;
3866            self.format_initialization = false;
3867            let _ = unsafe { self.inittrie() };
3868            self.format_initialization = saved;
3869        }
3870    }
3871
3872    pub fn resource_request_count(&self) -> usize {
3873        self.resource_requests
3874    }
3875
3876    pub fn resource_request_records(&self) -> &[PortableResourceRequestRecord] {
3877        self.resource_request_records.as_slice()
3878    }
3879
3880    pub fn transcript_bytes(&self) -> &[u8] {
3881        self.transcript_bytes.as_slice()
3882    }
3883
3884    /// The interned span keyed to the primary input's source name, if any —
3885    /// the first recorded span whose name is the primary input file.
3886    pub fn primary_input_source_span(&self) -> Option<PortableSourceSpan> {
3887        if !self.state.source_tracking {
3888            return None;
3889        }
3890        let primary = self.state.src_primary_name;
3891        let raw = self
3892            .state
3893            .src_spans
3894            .iter()
3895            .find(|raw| raw.name == primary)?;
3896        let name = unsafe { self.pool_string(raw.name) }?;
3897        Some(PortableSourceSpan {
3898            name,
3899            start: raw.start,
3900            end: raw.end,
3901            role: raw.role,
3902        })
3903    }
3904
3905    /// Stamped node→span pairs. The `node_src` shadow is paged-sparse and not
3906    /// cheaply enumerable by node address, so callers that need per-node spans
3907    /// use [`Self::resolve_node_src`] / `snapshot_node` on the live node graph;
3908    /// this restored accessor returns an empty slice rather than scanning `mem`.
3909    pub fn node_source_spans(&self) -> &[PortableNodeSourceSpan] {
3910        &[]
3911    }
3912
3913    pub fn stripped_page_build_count(&self) -> usize {
3914        self.stripped_page_builds
3915    }
3916
3917    pub fn stripped_shipout_count(&self) -> usize {
3918        self.stripped_shipouts
3919    }
3920
3921    pub fn stripped_special_output_count(&self) -> usize {
3922        self.stripped_special_outputs
3923    }
3924
3925    pub fn stripped_picture_load_count(&self) -> usize {
3926        self.stripped_picture_loads
3927    }
3928
3929    pub fn stripped_source_special_count(&self) -> usize {
3930        self.stripped_source_specials
3931    }
3932
3933    pub fn stripped_write_whatsit_diagnostic_count(&self) -> usize {
3934        self.stripped_write_whatsit_diagnostics
3935    }
3936
3937    pub fn stripped_pdf_extension_count(&self) -> usize {
3938        self.stripped_pdf_extensions
3939    }
3940
3941    pub fn stripped_page_top_prune_count(&self) -> usize {
3942        self.stripped_page_top_prunes
3943    }
3944
3945    pub fn last_stripped_shipout_box(&self) -> Option<PortableNodeHandle> {
3946        self.last_stripped_shipout_box
3947    }
3948
3949    pub fn captured_fragment_root(&self) -> Option<PortableNodeHandle> {
3950        self.captured_fragment_root
3951    }
3952
3953    /// Host box render payload by record index, as referenced by `HostBoxRef` marker nodes.
3954    pub fn host_box_record(&self, index: usize) -> Option<&PortableHostBox> {
3955        self.hostbox_records.get(index)
3956    }
3957
3958    /// At-size (scaled points) a font was loaded at, by internal font number.
3959    /// Used by the IR builder to carry the real glyph-run font size.
3960    pub fn font_at_size(&self, font: integer) -> integer {
3961        if font < 0 {
3962            return 0;
3963        }
3964        self.state
3965            .fontsize_storage
3966            .get(font as usize)
3967            .copied()
3968            .unwrap_or(0)
3969    }
3970
3971    /// The interned `\font` name for a font number. For native fonts this is the
3972    /// XeTeX spec the font was loaded with (e.g.
3973    /// `[latinmodern-math.otf]:script=math;ssty=1`); for TFM fonts it is the
3974    /// `.tfm` name. Lets the IR carry a real font identity so a renderer can
3975    /// resolve per-run glyph outlines to the originating font file.
3976    pub fn font_name(&self, font: integer) -> Option<String> {
3977        if font < 0 {
3978            return None;
3979        }
3980        let name = self.state.fontname_storage.get(font as usize).copied()?;
3981        // SAFETY: decodes the engine string pool, identical to every other
3982        // `pool_string` read elsewhere in the boundary layer.
3983        unsafe { self.pool_string(name) }
3984    }
3985
3986    /// The `\font` spec a native font number was loaded with, recovered from the
3987    /// font platform (`[file]:features`). For native fonts this is the reliable
3988    /// identity (TFM `\fontname` is empty for them). Read-only.
3989    pub fn native_font_spec(&self, font: integer) -> Option<String> {
3990        let handle = Self::font_handle_for_number(self, font)?;
3991        self.fonts.font_spec(handle)
3992    }
3993
3994    /// Snapshot the native-font table `(handle, spec, size)` from the attached
3995    /// font platform, for packaging alongside a serialized format image.
3996    pub fn native_font_table(&self) -> Vec<(PortableFontHandle, String, i32)> {
3997        self.fonts.font_table()
3998    }
3999
4000    /// Re-bind native fonts on a cold-loaded format image: rebuilds the attached
4001    /// platform's handle→font map from a [`Self::native_font_table`] snapshot so
4002    /// the `fontlayoutengine` handles preserved in the image resolve again.
4003    /// Returns `false` if any font failed to reload.
4004    pub fn restore_native_font_table(
4005        self: &mut Self,
4006        table: &[(PortableFontHandle, String, i32)],
4007    ) -> bool {
4008        self.fonts.restore_font_table(table)
4009    }
4010
4011    pub fn last_abort_status(&self) -> Option<integer> {
4012        self.last_abort_status
4013    }
4014
4015    /// The message from the most recent surfaced [`EngineError`], if the last
4016    /// run failed with a TeX error (rather than a fatal abort). `None` after a
4017    /// clean run or a bare abort.
4018    pub fn last_error_message(&self) -> Option<&str> {
4019        self.last_error_message.as_deref()
4020    }
4021
4022    /// Enable/disable the fragment sandbox (see [`PortableTexEngine::sandbox`]),
4023    /// resetting the per-run bookkeeping so each render starts clean. Uses the
4024    /// `self: &mut Self` receiver form the patcher's passes expect for prelude
4025    /// methods (the `&mut self` shorthand gets its receiver stripped).
4026    pub fn set_sandbox(self: &mut Self, on: bool) {
4027        self.sandbox = on;
4028        self.sandbox_math_depth = 0;
4029        self.sandbox_math_opened = false;
4030        self.sandbox_ops = 0;
4031    }
4032
4033    /// Extract the message from the most recent `! ...` line in the transcript --
4034    /// what `error()` printed for the diagnostic now being surfaced -- trimming
4035    /// the trailing period `error()` appends. Diagnostics are printed with
4036    /// `selector = term_and_log`, so each byte reaches the transcript twice (the
4037    /// headless terminal and the log both feed it); [`collapse_doubled_line`]
4038    /// undoes that. Returns a generic label if no well-formed error line exists.
4039    pub(crate) fn capture_last_error_message(&self) -> String {
4040        let transcript = core::str::from_utf8(&self.transcript_bytes).unwrap_or("");
4041        for raw in transcript.lines().rev() {
4042            let collapsed = collapse_doubled_line(raw.trim());
4043            let line = collapsed.as_deref().unwrap_or(raw).trim();
4044            if let Some(rest) = line.strip_prefix("! ") {
4045                return rest.trim_end_matches('.').trim().into();
4046            }
4047        }
4048        "TeX error".into()
4049    }
4050
4051    pub fn snapshot_node(&self, handle: PortableNodeHandle) -> Option<PortableNodeSnapshot> {
4052        let node = handle.0 as halfword;
4053        let word = self.node_word(node, 0)?;
4054        let raw_kind = unsafe { word.hh.u.B0 as i32 };
4055        let subtype = unsafe { word.hh.u.B1 as i32 };
4056        let kind = self.node_kind(raw_kind, node, subtype);
4057        let link = self.node_link(node);
4058        let is_character = node >= self.state.himemmin;
4059        let native_word4 = if matches!(kind, PortableNodeKind::NativeWord | PortableNodeKind::NativeGlyph) {
4060            self.node_word(node, 4)
4061        } else {
4062            None
4063        };
4064        let font = if is_character {
4065            raw_kind
4066        } else {
4067            native_word4.map_or(0, |word| unsafe { word.v.QQQQ.u.B1 as i32 })
4068        };
4069        let character = if is_character {
4070            subtype
4071        } else if matches!(kind, PortableNodeKind::HostBoxRef) {
4072            // Host box markers carry their record index in the payload word after the metrics.
4073            self.node_word(node, 4).map_or(0, |word| unsafe { word.u.CINT })
4074        } else {
4075            native_word4.map_or(0, |word| unsafe { word.v.QQQQ.u.B2 as i32 })
4076        };
4077        let (width, height, depth, shift, list) = match raw_kind {
4078            _ if is_character => (
4079                self.character_width(font, character).unwrap_or_default(),
4080                0,
4081                0,
4082                0,
4083                None,
4084            ),
4085            0 | 1 | 13 => (
4086                self.node_scaled(node, 1).unwrap_or_default(),
4087                self.node_scaled(node, 3).unwrap_or_default(),
4088                self.node_scaled(node, 2).unwrap_or_default(),
4089                self.node_scaled(node, 4).unwrap_or_default(),
4090                self.node_field_link(node, 5),
4091            ),
4092            2 => (
4093                self.node_scaled(node, 1).unwrap_or_default(),
4094                self.node_scaled(node, 3).unwrap_or_default(),
4095                self.node_scaled(node, 2).unwrap_or_default(),
4096                0,
4097                None,
4098            ),
4099            10 => (
4100                self.glue_amount(node).unwrap_or_default(),
4101                0,
4102                0,
4103                0,
4104                None,
4105            ),
4106            11 => (
4107                self.node_scaled(node, 1).unwrap_or_default(),
4108                0,
4109                0,
4110                0,
4111                None,
4112            ),
4113            8 if matches!(
4114                kind,
4115                PortableNodeKind::NativeWord
4116                    | PortableNodeKind::NativeGlyph
4117                    | PortableNodeKind::HostBoxRef
4118            ) => (
4119                self.node_scaled(node, 1).unwrap_or_default(),
4120                self.node_scaled(node, 3).unwrap_or_default(),
4121                self.node_scaled(node, 2).unwrap_or_default(),
4122                0,
4123                None,
4124            ),
4125            _ => (0, 0, 0, 0, None),
4126        };
4127        let native_glyphs = if matches!(kind, PortableNodeKind::NativeWord | PortableNodeKind::NativeGlyph) {
4128            self.native_glyph_infos
4129                .get(&node)
4130                .map(|info| info.glyphs.clone())
4131                .unwrap_or_default()
4132        } else {
4133            Vec::new()
4134        };
4135
4136        // Box glue-set state (`hlist_out`/`vlist_out` read these to turn each
4137        // glue node's natural width into its SET width): `glue_set` is the float
4138        // ratio from `hpack`/`vpack`, `glue_sign` (0 normal / 1 stretching /
4139        // 2 shrinking) and `glue_order` (0..3) select which order participates.
4140        let (glue_set, glue_sign, glue_order) = match raw_kind {
4141            0 | 1 | 13 => (
4142                self.node_word(node, 6).map_or(0.0, |word| unsafe { word.gr }),
4143                self.node_word(node, 5)
4144                    .map_or(0, |word| unsafe { word.hh.u.B0 as i32 }),
4145                self.node_word(node, 5)
4146                    .map_or(0, |word| unsafe { word.hh.u.B1 as i32 }),
4147            ),
4148            _ => (0.0, 0, 0),
4149        };
4150        // Glue node's spec stretch/shrink and their orders (raw_kind 10).
4151        let (glue_stretch, glue_shrink, glue_stretch_order, glue_shrink_order) = if raw_kind == 10 {
4152            match self.node_word(node, 1).map(|word| unsafe { word.hh.v.LH }) {
4153                Some(spec) => (
4154                    self.node_scaled(spec, 2).unwrap_or_default(),
4155                    self.node_scaled(spec, 3).unwrap_or_default(),
4156                    self.node_word(spec, 0)
4157                        .map_or(0, |word| unsafe { word.hh.u.B0 as i32 }),
4158                    self.node_word(spec, 0)
4159                        .map_or(0, |word| unsafe { word.hh.u.B1 as i32 }),
4160                ),
4161                None => (0, 0, 0, 0),
4162            }
4163        } else {
4164            (0, 0, 0, 0)
4165        };
4166
4167        Some(PortableNodeSnapshot {
4168            handle,
4169            kind,
4170            subtype,
4171            source: self.resolve_node_src(node),
4172            link,
4173            font,
4174            character,
4175            width,
4176            height,
4177            depth,
4178            shift,
4179            list,
4180            native_glyphs,
4181            glue_set,
4182            glue_sign,
4183            glue_order,
4184            glue_stretch,
4185            glue_shrink,
4186            glue_stretch_order,
4187            glue_shrink_order,
4188        })
4189    }
4190
4191    fn node_kind(&self, raw_kind: i32, node: halfword, subtype: i32) -> PortableNodeKind {
4192        if node >= self.state.himemmin {
4193            return PortableNodeKind::Character;
4194        }
4195
4196        match raw_kind {
4197            0 => PortableNodeKind::HorizontalBox,
4198            1 => PortableNodeKind::VerticalBox,
4199            2 => PortableNodeKind::Rule,
4200            3 => PortableNodeKind::Insertion,
4201            4 => PortableNodeKind::Mark,
4202            5 => PortableNodeKind::Adjustment,
4203            6 => PortableNodeKind::Ligature,
4204            7 => PortableNodeKind::Discretionary,
4205            8 if matches!(subtype, 40 | 41) => PortableNodeKind::NativeWord,
4206            8 if subtype == 42 => PortableNodeKind::NativeGlyph,
4207            8 if subtype == HOST_BOX_RESOLVED_SUBTYPE as i32 => PortableNodeKind::HostBoxRef,
4208            8 if matches!(subtype, 0 | 1 | 2 | 3) => PortableNodeKind::OutputWhatsit,
4209            8 => PortableNodeKind::Whatsit,
4210            9 => PortableNodeKind::Math,
4211            10 => PortableNodeKind::Glue,
4212            11 => PortableNodeKind::Kern,
4213            12 => PortableNodeKind::Penalty,
4214            13 => PortableNodeKind::UnsetBox,
4215            16 => PortableNodeKind::Noad,
4216            14 => PortableNodeKind::Style,
4217            15 => PortableNodeKind::Choice,
4218            other => PortableNodeKind::Unknown(other),
4219        }
4220    }
4221
4222    pub(crate) fn copy_native_glyph_info(
4223        this: &mut Self,
4224        src: halfword,
4225        dest: halfword,
4226    ) -> quarterword {
4227        if let Some(info) = this.native_glyph_infos.get(&src).cloned() {
4228            let glyph_count = info.glyphs.len().min(i32::MAX as usize) as quarterword;
4229            this.native_glyph_infos.insert(dest, info);
4230            glyph_count
4231        } else {
4232            this.native_glyph_infos.remove(&dest);
4233            0
4234        }
4235    }
4236
4237    fn font_handle_for_number(this: &Self, font: integer) -> Option<FontHandle> {
4238        if font < 0 || this.state.fontlayoutengine.is_null() {
4239            return None;
4240        }
4241        let handle = unsafe { *this.state.fontlayoutengine.offset(font as isize) as FontHandle };
4242        (handle != 0).then_some(handle)
4243    }
4244
4245    unsafe fn node_index_for_pointer(
4246        engine: &PortableTexEngine<'_>,
4247        node: voidpointer,
4248    ) -> Option<halfword> {
4249        if node.is_null() || engine.state.zmem.is_null() {
4250            return None;
4251        }
4252        let base = engine.state.zmem as isize;
4253        let address = node as isize;
4254        let word_size = core::mem::size_of::<memoryword>() as isize;
4255        if word_size == 0 || address < base {
4256            return None;
4257        }
4258        let bytes = address - base;
4259        if bytes % word_size != 0 {
4260            return None;
4261        }
4262        let index = (bytes / word_size) as halfword;
4263        if index < engine.state.memmin || index > engine.state.memmax {
4264            None
4265        } else {
4266            Some(index)
4267        }
4268    }
4269
4270    unsafe fn native_node_font(mem: *mut memoryword, node: halfword) -> integer {
4271        (*mem.offset((node + 4) as isize)).v.QQQQ.u.B1 as integer
4272    }
4273
4274    unsafe fn native_node_text<'a>(mem: *mut memoryword, node: halfword) -> &'a [u16] {
4275        let len = (*mem.offset((node + 4) as isize)).v.QQQQ.u.B2.max(0) as usize;
4276        if len == 0 {
4277            return &[];
4278        }
4279        core::slice::from_raw_parts(
4280            mem.offset((node + native_node_size) as isize) as *const memoryword as *const u16,
4281            len,
4282        )
4283    }
4284
4285    unsafe fn write_native_node_metrics(
4286        mem: *mut memoryword,
4287        node: halfword,
4288        width: i32,
4289        height: i32,
4290        depth: i32,
4291    ) {
4292        (*mem.offset((node + 1) as isize)).u.CINT = width;
4293        (*mem.offset((node + 2) as isize)).u.CINT = depth;
4294        (*mem.offset((node + 3) as isize)).u.CINT = height;
4295    }
4296
4297    fn node_word(&self, node: halfword, offset: halfword) -> Option<memoryword> {
4298        let index = node.checked_add(offset)?;
4299        if node as i64 == -(268435455 as i64) {
4300            return None;
4301        }
4302        if self.state.zmem.is_null() || index < self.state.memmin || index > self.state.memmax {
4303            return None;
4304        }
4305        unsafe { Some(*self.state.zmem.offset(index as isize)) }
4306    }
4307
4308    fn node_link(&self, node: halfword) -> Option<PortableNodeHandle> {
4309        let word = self.node_word(node, 0)?;
4310        let link = unsafe { word.hh.v.RH };
4311        Self::node_handle_from_raw(link)
4312    }
4313
4314    fn node_field_link(&self, node: halfword, offset: halfword) -> Option<PortableNodeHandle> {
4315        let word = self.node_word(node, offset)?;
4316        let link = unsafe { word.hh.v.RH };
4317        Self::node_handle_from_raw(link)
4318    }
4319
4320    fn node_scaled(&self, node: halfword, offset: halfword) -> Option<i32> {
4321        let word = self.node_word(node, offset)?;
4322        Some(unsafe { word.u.CINT })
4323    }
4324
4325    fn glue_amount(&self, node: halfword) -> Option<i32> {
4326        let word = self.node_word(node, 1)?;
4327        let glue_spec = unsafe { word.hh.v.LH };
4328        self.node_scaled(glue_spec, 1)
4329    }
4330
4331    fn character_width(&self, font: i32, character: i32) -> Option<i32> {
4332        if font < 0
4333            || character < 0
4334            || self.state.fontinfo.is_null()
4335            || self.state.charbase.is_null()
4336            || self.state.widthbase.is_null()
4337        {
4338            return None;
4339        }
4340        let char_info_index = unsafe {
4341            *self.state.charbase.offset(font as isize) + character
4342        };
4343        let char_info = unsafe {
4344            (*self.state.fontinfo.offset(char_info_index as isize)).v.QQQQ
4345        };
4346        let width_index = unsafe {
4347            *self.state.widthbase.offset(font as isize) as i32 + char_info.u.B0 as i32
4348        };
4349        Some(unsafe { (*self.state.fontinfo.offset(width_index as isize)).u.CINT })
4350    }
4351
4352    fn node_handle_from_raw(raw: halfword) -> Option<PortableNodeHandle> {
4353        if raw as i64 == -(268435455 as i64) {
4354            None
4355        } else {
4356            Some(PortableNodeHandle(raw))
4357        }
4358    }
4359
4360
4361    pub(crate) fn is_xetex(&self) -> bool {
4362        self.profile.xetex
4363    }
4364
4365    pub(crate) fn supports_etex(&self) -> bool {
4366        self.profile.etex
4367    }
4368
4369    pub(crate) fn supports_unicode_scalars(&self) -> bool {
4370        self.profile.unicode_scalars
4371    }
4372
4373    pub(crate) fn supports_unicode_math(&self) -> bool {
4374        self.profile.unicode_math
4375    }
4376
4377    pub(crate) fn supports_native_fonts(&self) -> bool {
4378        self.profile.native_fonts
4379    }
4380
4381    pub(crate) unsafe fn getinputnormalizationstate(&self) -> integer {
4382        if !self.is_xetex() {
4383            return 0;
4384        }
4385        let eqtb = self.state.zeqtb.as_mut_ptr();
4386        if eqtb.is_null() {
4387            return 0;
4388        }
4389        (*eqtb.offset(7892344 as i64 as isize)).u.CINT
4390    }
4391
4392    pub(crate) unsafe fn gettracingfontsstate(&self) -> integer {
4393        if !self.is_xetex() {
4394            return 0;
4395        }
4396        let eqtb = self.state.zeqtb.as_mut_ptr();
4397        if eqtb.is_null() {
4398            return 0;
4399        }
4400        (*eqtb.offset(7892347 as i64 as isize)).u.CINT
4401    }
4402
4403    unsafe fn current_resource_name(engine: *mut PortableTexEngine<'_>) -> Option<String> {
4404        let engine = engine.as_ref()?;
4405        if engine.state.nameoffile.is_null() || engine.state.namelength <= 0 {
4406            return None;
4407        }
4408
4409        let mut bytes = Vec::with_capacity(engine.state.namelength as usize);
4410        for index in 1..=engine.state.namelength {
4411            let value = *engine.state.nameoffile.offset(index as isize);
4412            if value <= 0 {
4413                continue;
4414            }
4415            bytes.push(value as u8);
4416        }
4417        Some(String::from_utf8_lossy(bytes.as_slice()).into_owned())
4418    }
4419
4420    /// Resolve the `\XeTeXinputencoding "<name>"` encoding just scanned into
4421    /// `nameoffile`, returning the XeTeX mode (AUTO=0, UTF8=1, UTF16BE=2,
4422    /// UTF16LE=3, RAW=4) and zeroing `*info`. Faithful port of XeTeX's
4423    /// `getencodingmodeandinfo` (`XeTeX_ext.c`), minus ICU: unknown names degrade
4424    /// to RAW (read as raw bytes) rather than opening an ICU converter.
4425    pub(crate) unsafe fn get_encoding_mode_and_info(
4426        engine: *mut PortableTexEngine<'_>,
4427        info: *mut integer,
4428    ) -> integer {
4429        if !info.is_null() {
4430            *info = 0;
4431        }
4432        let name = Self::current_resource_name(engine).unwrap_or_default();
4433        let lowered = name.trim().to_ascii_lowercase();
4434        match lowered.as_str() {
4435            "auto" => 0,                       // AUTO
4436            "utf8" | "utf-8" => 1,             // UTF8
4437            // `utf16` is host-endian; treat as big-endian (xetex default name).
4438            "utf16" | "utf-16" | "utf16be" | "utf-16be" => 2, // UTF16BE
4439            "utf16le" | "utf-16le" => 3,       // UTF16LE
4440            "bytes" => 4,                      // RAW
4441            // Unknown / ICU encoding names: read as raw bytes (no ICU support).
4442            _ => 4,
4443        }
4444    }
4445
4446    unsafe fn mode_string(mode: const_string) -> String {
4447        if mode.is_null() {
4448            return String::new();
4449        }
4450
4451        let mut bytes = Vec::new();
4452        let mut cursor = mode;
4453        while *cursor != 0 {
4454            bytes.push(*cursor as u8);
4455            cursor = cursor.add(1);
4456        }
4457        String::from_utf8_lossy(bytes.as_slice()).into_owned()
4458    }
4459
4460    unsafe fn pool_string(&self, string: strnumber) -> Option<String> {
4461        if string < 0 || self.state.strstart.is_null() || self.state.strpool.is_null() {
4462            return None;
4463        }
4464        let index = Self::pool_string_index(string)?;
4465        let start = *self.state.strstart.offset(index);
4466        let end = *self.state.strstart.offset(index + 1);
4467        if start < 0 || end < start {
4468            return None;
4469        }
4470
4471        let len = usize::try_from(end - start).ok()?;
4472        let mut units = Vec::with_capacity(len);
4473        for offset in 0..len {
4474            units.push(*self.state.strpool.offset((start as usize + offset) as isize));
4475        }
4476        Some(
4477            char::decode_utf16(units)
4478                .map(|codepoint| codepoint.unwrap_or(char::REPLACEMENT_CHARACTER))
4479                .collect(),
4480        )
4481    }
4482
4483    pub(crate) fn pool_string_index(string: strnumber) -> Option<isize> {
4484        if string < 0 {
4485            return None;
4486        }
4487        let index = if string >= 65536 { string - 65536 } else { string };
4488        isize::try_from(index).ok()
4489    }
4490
4491    fn resource_kind(name: &str, format: integer) -> ResourceKind {
4492        match format {
4493            resource_format_tex_input | 0 => Self::tex_resource_kind(name),
4494            resource_format_tfm | resource_format_font => ResourceKind::Font,
4495            resource_format_encoding => ResourceKind::Encoding,
4496            resource_format_font_map => ResourceKind::Map,
4497            resource_format_config => ResourceKind::Config,
4498            resource_format_format_image => ResourceKind::FormatImage,
4499            other => ResourceKind::Other(other),
4500        }
4501    }
4502
4503    fn tex_resource_kind(name: &str) -> ResourceKind {
4504        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4505        let name = name.to_ascii_lowercase();
4506        if name.ends_with(".sty") {
4507            return ResourceKind::Package;
4508        }
4509        if name.ends_with(".cls") {
4510            return ResourceKind::Class;
4511        }
4512        if name.ends_with(".fd") {
4513            return ResourceKind::FontDefinition;
4514        }
4515        if name.ends_with(".clo")
4516            || name.ends_with(".def")
4517            || name.ends_with(".ldf")
4518            || name.ends_with(".cfg")
4519        {
4520            return ResourceKind::PackageSupport;
4521        }
4522        ResourceKind::TexInput
4523    }
4524
4525    fn resource_kind_for_open(
4526        engine: &PortableTexEngine<'_>,
4527        name: &str,
4528        format: integer,
4529    ) -> ResourceKind {
4530        let kind = Self::resource_kind(name, format);
4531        if kind == ResourceKind::TexInput
4532            && Self::active_package_owner(engine).is_some()
4533            && Self::looks_like_package_asset(name)
4534        {
4535            ResourceKind::Asset
4536        } else {
4537            kind
4538        }
4539    }
4540
4541    fn resource_package_owner(
4542        engine: &PortableTexEngine<'_>,
4543        name: &str,
4544        kind: ResourceKind,
4545    ) -> Option<String> {
4546        match kind {
4547            ResourceKind::Package | ResourceKind::Class => Self::resource_stem(name),
4548            ResourceKind::PackageSupport | ResourceKind::FontDefinition | ResourceKind::Asset => {
4549                Self::active_package_owner(engine)
4550            }
4551            _ => None,
4552        }
4553    }
4554
4555    fn active_package_owner(engine: &PortableTexEngine<'_>) -> Option<String> {
4556        engine.current_input_package_owner.clone()
4557    }
4558
4559    fn looks_like_package_asset(name: &str) -> bool {
4560        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4561        let name = name.to_ascii_lowercase();
4562        !(name.ends_with(".tex")
4563            || name.ends_with(".ltx")
4564            || name.ends_with(".sty")
4565            || name.ends_with(".cls")
4566            || name.ends_with(".fd")
4567            || name.ends_with(".clo")
4568            || name.ends_with(".def")
4569            || name.ends_with(".ldf")
4570            || name.ends_with(".cfg"))
4571    }
4572
4573    fn resource_stem(name: &str) -> Option<String> {
4574        let name = name.rsplit(['/', '\\']).next().unwrap_or(name);
4575        let stem = name.rsplit_once('.').map_or(name, |(stem, _)| stem);
4576        if stem.is_empty() {
4577            None
4578        } else {
4579            Some(stem.to_string())
4580        }
4581    }
4582
4583    fn source_index(value: usize) -> u32 {
4584        value.min(u32::MAX as usize) as u32
4585    }
4586
4587    fn virtual_file_key(name: &str) -> String {
4588        let mut name = name;
4589        while let Some(stripped) = name.strip_prefix("./") {
4590            name = stripped;
4591        }
4592        name.to_string()
4593    }
4594
4595    fn normalized_runtime_resource_name(mut name: &str) -> &str {
4596        while let Some(stripped) = name.strip_prefix("./") {
4597            name = stripped;
4598        }
4599        name
4600    }
4601
4602    // Hand-edited bridge: returns `EngineFlow<Option<strnumber>>` so the final
4603    // `makestring` (abort-reachable on pool overflow) propagates via `?`, while
4604    // the internal `Option` early-returns (a `None` means "did not intern", not
4605    // an abort) stay explicit `Ok(None)`. The `?`-on-`Option` shorthand used
4606    // before would not survive the return-type change, so this one is NOT
4607    // auto-rewritten by the flow pass.
4608    unsafe fn intern_static_pool_string(
4609        engine: &mut PortableTexEngine<'_>,
4610        text: &str,
4611    ) -> EngineFlow<Option<strnumber>> {
4612        if engine.state.strpool.is_null() || engine.state.strstart.is_null() {
4613            return Ok(None);
4614        }
4615
4616        let Ok(needed) = integer::try_from(text.encode_utf16().count()) else {
4617            return Ok(None);
4618        };
4619        let Some(next_pool) = engine.state.poolptr.checked_add(needed) else {
4620            return Ok(None);
4621        };
4622        if next_pool > engine.state.poolsize {
4623            return Ok(None);
4624        }
4625
4626        for unit in text.encode_utf16() {
4627            *engine.state.strpool.offset(engine.state.poolptr as isize) = unit as packedUTF16code;
4628            engine.state.poolptr += 1;
4629        }
4630
4631        Ok(Some(engine.makestring()?))
4632    }
4633
4634    unsafe fn append_text_to_pool(engine: &mut PortableTexEngine<'_>, text: &str) -> boolean {
4635        if engine.state.strpool.is_null() {
4636            return false_0;
4637        }
4638        let needed = match integer::try_from(text.encode_utf16().count()) {
4639            Ok(needed) => needed,
4640            Err(_) => return false_0,
4641        };
4642        let Some(next_pool) = engine.state.poolptr.checked_add(needed) else {
4643            return false_0;
4644        };
4645        if next_pool > engine.state.poolsize {
4646            return false_0;
4647        }
4648        for unit in text.encode_utf16() {
4649            *engine.state.strpool.offset(engine.state.poolptr as isize) = unit as packedUTF16code;
4650            engine.state.poolptr += 1;
4651        }
4652        true_0
4653    }
4654
4655    unsafe fn resource_bytes_for_name(
4656        engine: &mut PortableTexEngine<'_>,
4657        name: &str,
4658    ) -> Option<Vec<u8>> {
4659        let name = Self::normalized_runtime_resource_name(name);
4660        let kind = Self::resource_kind_for_open(engine, name, resource_format_tex_input);
4661        let package = Self::resource_package_owner(engine, name, kind);
4662        let request = ResourceRequest {
4663            name,
4664            kind,
4665            package: package.as_deref(),
4666            format: resource_format_tex_input,
4667            mode: "rb",
4668            source: None,
4669        };
4670        let virtual_key = Self::virtual_file_key(name);
4671        if let Some(bytes) = engine.virtual_files.get(&virtual_key) {
4672            Some(bytes.clone())
4673        } else {
4674            engine.resources.read(request)
4675        }
4676    }
4677
4678    pub(crate) unsafe fn boundary_get_file_size(
4679        engine: *mut PortableTexEngine<'_>,
4680        string: integer,
4681    ) {
4682        let Some(engine) = engine.as_mut() else {
4683            return;
4684        };
4685        let Some(name) = engine.pool_string(string as strnumber) else {
4686            return;
4687        };
4688        // expl3's file layer (`\file_full_name:n`) decides a file EXISTS solely
4689        // by whether `\filesize` expands to a non-empty value. We have no host
4690        // filesystem (wasm target): answer from the ResourceProvider. When the
4691        // provider serves the resource, report its real byte length; otherwise
4692        // (the in-memory job fragment we feed, which has no backing file, or a
4693        // probe for an asset we don't carry) report a nonzero placeholder so the
4694        // existence check still passes. Returning nothing here makes expl3
4695        // conclude the file is missing, which silently aborts data-file loads
4696        // like unicode-math's `\file_get {unicode-math-table.tex}`.
4697        const PLACEHOLDER_FILE_SIZE: usize = 4096;
4698        let size = Self::resource_bytes_for_name(engine, name.as_str())
4699            .map_or(PLACEHOLDER_FILE_SIZE, |bytes| bytes.len());
4700        Self::append_text_to_pool(engine, size.to_string().as_str());
4701    }
4702
4703    pub(crate) unsafe fn load_pool_strings(
4704        engine: &mut PortableTexEngine<'_>,
4705        spare_size: integer,
4706    ) -> EngineFlow<integer> {
4707        if engine.state.strpool.is_null() || engine.state.strstart.is_null()
4708            || spare_size <= 0
4709        {
4710            return Ok(0);
4711        }
4712        let mut used = 0_i32;
4713        let mut last = 0_i32;
4714        for line in include_str!("../pool/xetex.pool").lines() {
4715            if line.starts_with('*') {
4716                break;
4717            }
4718            let bytes = line.as_bytes();
4719            let text = if bytes.len() >= 2 && bytes[0].is_ascii_digit()
4720                && bytes[1].is_ascii_digit()
4721            {
4722                &line[2..]
4723            } else {
4724                line
4725            };
4726            let units = text.encode_utf16().count().min(i32::MAX as usize) as integer;
4727            used = used.saturating_add(units);
4728            if used >= spare_size
4729                || engine.state.poolptr.saturating_add(units) > engine.state.poolsize
4730            {
4731                return Ok(0);
4732            }
4733            for unit in text.encode_utf16() {
4734                *engine.state.strpool.offset(engine.state.poolptr as isize) = unit
4735                    as packedUTF16code;
4736                engine.state.poolptr += 1;
4737            }
4738            last = engine.makestring()?;
4739        }
4740        Ok(last)
4741    }
4742
4743    pub(crate) unsafe fn boundary_open_log_file(
4744        engine: *mut PortableTexEngine<'_>,
4745    ) -> EngineFlow<()> {
4746        let Some(engine) = engine.as_mut() else {
4747            return Ok(());
4748        };
4749        let old_setting = engine.state.selector;
4750        if engine.state.jobname == 0 {
4751            if let Some(jobname) = Self::intern_static_pool_string(engine, "texput")? {
4752                engine.state.jobname = jobname;
4753            }
4754        }
4755        engine.state.logopened = true_0 as boolean;
4756        engine.state.selector = (old_setting as i32 + 2).clamp(0, 21) as eightbits;
4757        Ok(())
4758    }
4759
4760    pub(crate) unsafe fn boundary_jump_out(
4761        engine: *mut PortableTexEngine<'_>,
4762    ) -> EngineFlow<core::convert::Infallible> {
4763        // Status arithmetic is LOAD-BEARING and unchanged: history<=1 is the
4764        // normal `\end`/dump termination (status 0 => "completed"); anything
4765        // else is an error abort (status 1). Every passing conformance test
4766        // funnels its normal end through here, so this must stay byte-exact.
4767        let status = if let Some(engine) = engine.as_ref() {
4768            if engine.state.history as i32 <= 1 {
4769                0 as integer
4770            } else {
4771                1 as integer
4772            }
4773        } else {
4774            1 as integer
4775        };
4776        Self::abort_engine(engine, status)
4777    }
4778
4779    pub(crate) unsafe fn boundary_shipout(
4780        engine: *mut PortableTexEngine<'_>,
4781        box_node: halfword,
4782    ) {
4783        if let Some(engine) = engine.as_mut() {
4784            engine.stripped_shipouts = engine.stripped_shipouts.saturating_add(1);
4785            engine.last_stripped_shipout_box = Some(PortableNodeHandle(box_node));
4786        }
4787    }
4788
4789    pub(crate) unsafe fn boundary_capture_fragment_box(
4790        engine: *mut PortableTexEngine<'_>,
4791        box_node: halfword,
4792        mode: integer,
4793        boxcontext: integer,
4794    ) -> boolean {
4795        if let Some(engine) = engine.as_mut() {
4796            if engine.fragment_capture_enabled {
4797                engine.captured_fragment_root = Some(PortableNodeHandle(box_node));
4798                let absolute_mode = if mode >= 0 { mode } else { -mode };
4799                if absolute_mode == 1 && boxcontext < 1_073_741_824 {
4800                    return true_0;
4801                }
4802            }
4803        }
4804        false_0
4805    }
4806
4807    pub(crate) unsafe fn boundary_build_page(
4808        engine: *mut PortableTexEngine<'_>,
4809    ) -> EngineFlow<()> {
4810        if let Some(engine) = engine.as_mut() {
4811            if engine.format_initialization
4812                && engine.state.curcmd as i32 == 15
4813                && engine.state.curchr == 1
4814            {
4815                // Dump during format build: status-0 abort (normal end of the
4816                // format-initialization run). Propagate via `?` so the engine
4817                // does not unwind.
4818                Self::abort_engine(engine as *mut PortableTexEngine<'_>, 0 as integer)?;
4819            }
4820            engine.stripped_page_builds = engine.stripped_page_builds.saturating_add(1);
4821        }
4822        Ok(())
4823    }
4824
4825    pub(crate) unsafe fn boundary_prune_page_top(
4826        engine: *mut PortableTexEngine<'_>,
4827        node: halfword,
4828        _saving: boolean,
4829    ) -> halfword {
4830        if let Some(engine) = engine.as_mut() {
4831            engine.stripped_page_top_prunes =
4832                engine.stripped_page_top_prunes.saturating_add(1);
4833        }
4834        node
4835    }
4836
4837    pub(crate) unsafe fn boundary_special_out(
4838        engine: *mut PortableTexEngine<'_>,
4839        node: halfword,
4840    ) -> EngineFlow<()> {
4841        let Some(engine) = engine.as_mut() else {
4842            return Ok(());
4843        };
4844        if node < engine.state.memmin || node > engine.state.memend {
4845            engine.stripped_special_outputs = engine
4846                .stripped_special_outputs
4847                .saturating_add(1);
4848            return Ok(());
4849        }
4850        let mem = engine.state.zmem.as_mut_ptr();
4851        if (*mem.offset(node as isize)).hh.u.B0 as i32 == 8 {
4852            match (*mem.offset(node as isize)).hh.u.B1 as i32 {
4853                0 => {
4854                    Self::boundary_open_write_whatsit(engine, node);
4855                    return Ok(());
4856                }
4857                1 => {
4858                    Self::boundary_write_whatsit(engine, node)?;
4859                    return Ok(());
4860                }
4861                2 => {
4862                    Self::boundary_close_write_whatsit(engine, node);
4863                    return Ok(());
4864                }
4865                _ => {}
4866            }
4867        }
4868        engine.stripped_special_outputs = engine
4869            .stripped_special_outputs
4870            .saturating_add(1);
4871        Ok(())
4872    }
4873
4874    unsafe fn boundary_open_write_whatsit(engine: &mut PortableTexEngine<'_>, node: halfword) {
4875        let mem = engine.state.zmem.as_mut_ptr();
4876        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4877        if stream >= 16 {
4878            return;
4879        }
4880
4881        if engine.state.writeopen[stream] != 0 {
4882            Self::boundary_close_write_stream(engine, stream);
4883        }
4884
4885        engine.state.curname = (*mem.offset((node + 1) as isize)).hh.v.RH as strnumber;
4886        engine.state.curarea = (*mem.offset((node + 2) as isize)).hh.v.LH as strnumber;
4887        engine.state.curext = (*mem.offset((node + 2) as isize)).hh.v.RH as strnumber;
4888        if engine.state.curext == 335 {
4889            engine.state.curext = 799;
4890        }
4891        engine.zpackfilename(engine.state.curname, engine.state.curarea, engine.state.curext);
4892        let Some(name) = Self::current_resource_name(engine as *mut PortableTexEngine<'_>) else {
4893            return;
4894        };
4895        let handle = Box::new(PortableFileHandle::new(
4896            name,
4897            ResourceKind::TexInput,
4898            None,
4899            resource_format_tex_input,
4900            Vec::new(),
4901        ));
4902        engine.state.writefile[stream] = Box::into_raw(handle);
4903        engine.state.writeopen[stream] = true_0;
4904    }
4905
4906    unsafe fn boundary_write_whatsit(
4907        engine: &mut PortableTexEngine<'_>,
4908        node: halfword,
4909    ) -> EngineFlow<()> {
4910        let mem = engine.state.zmem.as_mut_ptr();
4911        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4912        if stream >= 16 || engine.state.writeopen[stream] == 0 {
4913            return Ok(());
4914        }
4915        let write_tokens = engine.profile.write_token_constants();
4916        let q = engine.getavail()?;
4917        (*mem.offset(q as isize)).hh.v.LH = write_tokens.open_group_token;
4918        let r = engine.getavail()?;
4919        (*mem.offset(q as isize)).hh.v.RH = r;
4920        (*mem.offset(r as isize)).hh.v.LH = write_tokens.end_write_token;
4921        engine.zbegintokenlist(q, 4)?;
4922        engine.zbegintokenlist((*mem.offset((node + 1) as isize)).hh.v.RH, 15)?;
4923        let q = engine.getavail()?;
4924        (*mem.offset(q as isize)).hh.v.LH = write_tokens.close_group_token;
4925        engine.zbegintokenlist(q, 4)?;
4926        let old_mode = engine.state.curlist.modefield;
4927        engine.state.curlist.modefield = 0;
4928        engine.state.curcs = engine.state.writeloc;
4929        engine.zscantoks(false_0, true_0)?;
4930        engine.state.curlist.modefield = old_mode;
4931        engine.gettoken()?;
4932        if engine.state.curtok != write_tokens.end_write_token {
4933            while engine.state.curtok != write_tokens.end_write_token {
4934                engine.gettoken()?;
4935            }
4936        }
4937        engine.endtokenlist()?;
4938        let old_setting = engine.state.selector;
4939        engine.state.selector = stream as eightbits;
4940        engine.ztokenshow(engine.state.defref);
4941        engine.println();
4942        engine.state.selector = old_setting;
4943        engine.zflushlist(engine.state.defref);
4944        Ok(())
4945    }
4946
4947    unsafe fn boundary_close_write_whatsit(engine: &mut PortableTexEngine<'_>, node: halfword) {
4948        let mem = engine.state.zmem.as_mut_ptr();
4949        let stream = (*mem.offset((node + 1) as isize)).hh.v.LH as usize;
4950        if stream < 16 {
4951            Self::boundary_close_write_stream(engine, stream);
4952        }
4953    }
4954
4955    unsafe fn boundary_close_write_stream(engine: &mut PortableTexEngine<'_>, stream: usize) {
4956        if stream >= 16 || engine.state.writeopen[stream] == 0 {
4957            return;
4958        }
4959        let file = engine.state.writefile[stream];
4960        engine.state.writefile[stream] = core::ptr::null_mut();
4961        engine.state.writeopen[stream] = false_0;
4962        if file.is_null() {
4963            return;
4964        }
4965        let handle = Box::from_raw(file);
4966        let key = Self::virtual_file_key(handle.name.as_str());
4967        engine.virtual_files.insert(key, handle.bytes);
4968    }
4969
4970    pub(crate) unsafe fn boundary_load_picture(
4971        engine: *mut PortableTexEngine<'_>,
4972        _is_pdf: boolean,
4973    ) {
4974        if let Some(engine) = engine.as_mut() {
4975            engine.stripped_picture_loads = engine.stripped_picture_loads.saturating_add(1);
4976        }
4977    }
4978
4979    pub(crate) fn record_stripped_source_special(engine: *mut PortableTexEngine<'_>) {
4980        if let Some(engine) = unsafe { engine.as_mut() } {
4981            engine.stripped_source_specials = engine.stripped_source_specials.saturating_add(1);
4982        }
4983    }
4984
4985    pub(crate) fn record_stripped_write_whatsit_diagnostic(engine: *mut PortableTexEngine<'_>) {
4986        if let Some(engine) = unsafe { engine.as_mut() } {
4987            engine.stripped_write_whatsit_diagnostics =
4988                engine.stripped_write_whatsit_diagnostics.saturating_add(1);
4989        }
4990    }
4991
4992    pub(crate) fn record_stripped_pdf_extension(engine: *mut PortableTexEngine<'_>) {
4993        if let Some(engine) = unsafe { engine.as_mut() } {
4994            engine.stripped_pdf_extensions = engine.stripped_pdf_extensions.saturating_add(1);
4995        }
4996    }
4997
4998    /// Resolve a freshly opened file's input encoding.
4999    ///
5000    /// Mirrors XeTeX's `u_open_in`, which only AUTO-sniffs UNICODE text inputs:
5001    /// the default `\XeTeXinputencoding` is `auto`, applied to `read`/`\input`
5002    /// text streams. Binary inputs (`tfm`/`font`/`fmt`/…) are opened as raw byte
5003    /// files in the original engine and must NOT be sniffed (a stray `FE FF` at
5004    /// the start of a TFM must not skip bytes). The UTF-8/UTF-16 decoders are
5005    /// also XeTeX-only; under the non-XeTeX (`tex`/`etex`) profiles every input
5006    /// is read raw, byte == scalar, matching 8-bit TeX.
5007    fn resolve_input_encoding(engine: &PortableTexEngine<'_>, handle: &mut PortableFileHandle) {
5008        let is_text = handle.format == resource_format_tex_input || handle.format == 0;
5009        if engine.is_xetex() && is_text {
5010            handle.resolve_text_encoding_auto();
5011        } else {
5012            handle.encoding = InputEncoding::Bytes;
5013        }
5014    }
5015
5016    pub(crate) unsafe fn boundary_open_input(
5017        engine: *mut PortableTexEngine<'_>,
5018        file: *mut NativeFileHandle,
5019        format: integer,
5020        mode: const_string,
5021    ) -> boolean {
5022        let Some(engine) = engine.as_mut() else {
5023            if !file.is_null() {
5024                *file = core::ptr::null_mut();
5025            }
5026            return false_0;
5027        };
5028        let Some(name) = Self::current_resource_name(engine as *mut PortableTexEngine<'_>) else {
5029            if !file.is_null() {
5030                *file = core::ptr::null_mut();
5031            }
5032            return false_0;
5033        };
5034        let mode = Self::mode_string(mode);
5035        let request_kind = Self::resource_kind_for_open(engine, name.as_str(), format);
5036        let request_package = Self::resource_package_owner(engine, name.as_str(), request_kind);
5037        let source: Option<PortableSourceSpan> = None;
5038        let request = ResourceRequest {
5039            name: name.as_str(),
5040            kind: request_kind,
5041            package: request_package.as_deref(),
5042            format,
5043            mode: mode.as_str(),
5044            source: source.clone(),
5045        };
5046        engine.resource_requests = engine.resource_requests.saturating_add(1);
5047        let virtual_key = Self::virtual_file_key(name.as_str());
5048        let bytes = if let Some(bytes) = engine.virtual_files.get(&virtual_key) {
5049            bytes.clone()
5050        } else if let Some(bytes) = engine.resources.read(request) {
5051            bytes
5052        } else {
5053            engine.resource_request_records.push(PortableResourceRequestRecord {
5054                name,
5055                kind: request_kind,
5056                package: request_package,
5057                format,
5058                mode,
5059                source,
5060                byte_len: None,
5061            });
5062            if !file.is_null() {
5063                *file = core::ptr::null_mut();
5064            }
5065            return false_0;
5066        };
5067        let byte_len = Self::source_index(bytes.len());
5068        engine.resource_request_records.push(PortableResourceRequestRecord {
5069            name: name.clone(),
5070            kind: request_kind,
5071            package: request_package.clone(),
5072            format,
5073            mode: mode.clone(),
5074            source,
5075            byte_len: Some(byte_len),
5076        });
5077        let mut handle = Box::new(PortableFileHandle::new(
5078            name,
5079            request_kind,
5080            request_package,
5081            format,
5082            bytes,
5083        ));
5084        // Resolve the input encoding (XeTeX `u_open_in` AUTO sniff) for text
5085        // inputs under the XeTeX profile; binary inputs and non-XeTeX profiles
5086        // stay raw `Bytes` with no BOM consumption.
5087        Self::resolve_input_encoding(engine, &mut handle);
5088        if format == resource_format_tfm {
5089            engine.state.tfmtemp = handle.read_byte().map_or(-1, |byte| byte as integer);
5090        }
5091        if !file.is_null() {
5092            *file = Box::into_raw(handle);
5093            return true_0;
5094        }
5095        drop(handle);
5096        false_0
5097    }
5098
5099    unsafe fn begin_primary_input_raw(
5100        self: &mut Self,
5101        name: &str,
5102        bytes: Vec<u8>,
5103    ) -> EngineFlow<boolean> {
5104        if self.state.inputfile.is_null() || self.state.sourcefilenamestack.is_null()
5105            || self.state.fullsourcefilenamestack.is_null()
5106            || self.state.buffer.is_null()
5107        {
5108            return Ok(false_0);
5109        }
5110        let Some(source_name) = Self::intern_static_pool_string(self, name)? else {
5111            return Ok(false_0);
5112        };
5113        self.beginfilereading()?;
5114        let slot = self.state.curinput.indexfield as isize;
5115        let mut handle = Box::new(
5116            PortableFileHandle::new(
5117                name.to_string(),
5118                ResourceKind::TexInput,
5119                None,
5120                resource_format_tex_input,
5121                bytes,
5122            ),
5123        );
5124        Self::resolve_input_encoding(self, &mut handle);
5125        *self.state.inputfile.offset(slot) = Box::into_raw(handle);
5126        self.state.curinput.namefield = source_name as halfword;
5127        *self.state.sourcefilenamestack.offset(slot) = source_name;
5128        *self.state.fullsourcefilenamestack.offset(slot) = source_name;
5129        self.state.curinput.statefield = 33 as quarterword;
5130        self.state.line = 1 as integer;
5131        self.state.src_primary_name = source_name;
5132        self.state.cmd_span = 0;
5133        self.state.pending_call_span = 0;
5134        self.state.src_user_cmd_span = 0;
5135        self.state.src_tok_span = 0;
5136        self.state.src_anchor_cmd = 0;
5137        self.state.src_grp_stack.clear();
5138        self.state.src_grp_closing = 0;
5139        self.state.src_call_user_span = 0;
5140        self.state.src_line_base = 0;
5141        self.state.src_line_buf_start = 0;
5142        self.state.src_prev_line_len = 0;
5143        self.state.src_line_initialized = false;
5144        self.state.curinput.spanfield = 0;
5145        self.state.src_native_offsets.clear();
5146        self.state.src_stack_cells.clear();
5147        self.state.cur_stack_head = 0;
5148        if Self::boundary_input_line(
5149            self as *mut PortableTexEngine<'_>,
5150            *self.state.inputfile.offset(slot) as NativeFileHandle,
5151        ) == 0
5152        {
5153            self.endfilereading();
5154            return Ok(false_0);
5155        }
5156        self.firmuptheline()?;
5157        let eqtb = self.state.zeqtb.as_mut_ptr();
5158        let endline_char_index = if self.is_xetex() && self.state.eqtbtop >= 7_892_312 {
5159            7_892_312_i64
5160        } else {
5161            27_212_i64
5162        };
5163        let endline_char = if eqtb.is_null() {
5164            -1
5165        } else {
5166            (*eqtb.offset(endline_char_index as isize)).u.CINT
5167        };
5168        if !(0..=255).contains(&endline_char) {
5169            self.state.curinput.limitfield -= 1;
5170        } else {
5171            *self.state.buffer.offset(self.state.curinput.limitfield as isize) = endline_char
5172                as UnicodeScalar;
5173        }
5174        self.state.first = (self.state.curinput.limitfield as i32 + 1) as integer;
5175        self.state.curinput.locfield = self.state.curinput.startfield;
5176        Ok(true_0)
5177    }
5178
5179    pub(crate) unsafe fn boundary_input_line(
5180        engine: *mut PortableTexEngine<'_>,
5181        file: NativeFileHandle,
5182    ) -> boolean {
5183        let Some(engine) = engine.as_mut() else {
5184            return false_0;
5185        };
5186        if file.is_null() || engine.state.buffer.is_null() {
5187            return false_0;
5188        }
5189
5190        let handle = &mut *file;
5191        if !handle.has_remaining() {
5192            return false_0;
5193        }
5194
5195        let first = engine.state.first.max(0) as usize;
5196        let limit = engine.state.bufsize.max(0) as usize;
5197        let mut last = first;
5198        // XeTeX `input_line`: decode Unicode scalars via `get_uni_c`, terminating
5199        // on EOF / LF (0x0A) / CR (0x0D). A CR coalesces a following LF (the
5200        // `skipNextLF` logic), so CRLF reads as a single line break.
5201        while last < limit {
5202            let Some(scalar) = handle.next_input_scalar() else {
5203                break;
5204            };
5205            match scalar {
5206                0x0A => break,
5207                0x0D => {
5208                    // Peek the next scalar; consume it only if it is LF. The peek
5209                    // must be restorable (the decoder may have advanced the cursor
5210                    // and/or set saved_char), so snapshot both before decoding.
5211                    let saved_cursor = handle.cursor;
5212                    let saved_lookahead = handle.saved_char;
5213                    let was_eof = handle.eof_after_failed_read;
5214                    match handle.next_input_scalar() {
5215                        Some(0x0A) => {} // CRLF: consume the LF.
5216                        _ => {
5217                            // Not LF (or EOF): restore the peeked state.
5218                            handle.cursor = saved_cursor;
5219                            handle.saved_char = saved_lookahead;
5220                            handle.eof_after_failed_read = was_eof;
5221                        }
5222                    }
5223                    break;
5224                }
5225                scalar => {
5226                    *engine.state.buffer.offset(last as isize) = scalar as UnicodeScalar;
5227                    last += 1;
5228                }
5229            }
5230        }
5231
5232        while last > first && *engine.state.buffer.offset((last - 1) as isize) == b' ' as UnicodeScalar {
5233            last -= 1;
5234        }
5235        if handle.format == resource_format_tex_input || handle.format == 0 {
5236            engine.current_input_package_owner = handle.package.clone();
5237        }
5238        // Source tracking multi-line accumulator (HOOK 7): each line of the
5239        // primary input reloads at the same buffer base, so `loc - first` is a
5240        // within-line column. Track the absolute char offset of the current
5241        // line's first slot (`src_line_base`), advancing it by the previous
5242        // line's length + 1 (the line break) at each refill. The buffer holds
5243        // `[first, last)` file characters for this line.
5244        if engine.state.source_tracking
5245            && engine.state.curinput.namefield as strnumber == engine.state.src_primary_name
5246        {
5247            if engine.state.src_line_initialized {
5248                engine.state.src_line_base += engine.state.src_prev_line_len + 1;
5249            } else {
5250                engine.state.src_line_base = 0;
5251                engine.state.src_line_initialized = true;
5252            }
5253            engine.state.src_line_buf_start = first as integer;
5254            engine.state.src_prev_line_len = last.saturating_sub(first) as u32;
5255            // The token that straddles the line break is read in the same
5256            // `get_next` call as this refill, so the top-of-loop token-start
5257            // snapshot is stale (it points at the previous line). Re-anchor it to
5258            // the new line's start so its span is measured in the new line's
5259            // coordinates.
5260            engine.state.src_token_start = first as integer;
5261        }
5262        engine.state.last = last as integer;
5263        true_0
5264    }
5265
5266    pub(crate) unsafe fn boundary_read_byte(_file: NativeFileHandle) -> integer {
5267        if _file.is_null() {
5268            return -1;
5269        }
5270        (&mut *_file).read_byte().map_or(-1, |byte| byte as integer)
5271    }
5272
5273    pub(crate) unsafe fn boundary_end_of_file(_file: NativeFileHandle) -> integer {
5274        if _file.is_null() || (&*_file).is_eof() {
5275            1
5276        } else {
5277            0
5278        }
5279    }
5280
5281    pub(crate) unsafe fn boundary_flush_file(_file: NativeFileHandle) -> integer {
5282        0
5283    }
5284
5285    pub(crate) unsafe fn boundary_write_byte(
5286        engine: *mut PortableTexEngine<'_>,
5287        character: integer,
5288        file: NativeFileHandle,
5289    ) -> integer {
5290        if !file.is_null() {
5291            if let Ok(byte) = u8::try_from(character) {
5292                (*file).bytes.push(byte);
5293            }
5294            return character;
5295        }
5296        if let Some(engine) = engine.as_mut() {
5297            if let Ok(byte) = u8::try_from(character) {
5298                engine.transcript_bytes.push(byte);
5299            }
5300        }
5301        character
5302    }
5303
5304    pub(crate) unsafe fn boundary_close_file(_file: NativeFileHandle) {
5305        if !_file.is_null() {
5306            drop(Box::from_raw(_file));
5307        }
5308    }
5309
5310    pub(crate) unsafe fn get_seconds_and_micros(
5311        engine: *mut PortableTexEngine<'_>,
5312        seconds: *mut integer,
5313        micros: *mut integer,
5314    ) {
5315        let clock = engine
5316            .as_mut()
5317            .map(|engine| engine.platform.clock())
5318            .unwrap_or_default();
5319        if !seconds.is_null() {
5320            *seconds = clock.seconds;
5321        }
5322        if !micros.is_null() {
5323            *micros = clock.micros;
5324        }
5325    }
5326
5327    pub(crate) unsafe fn linebreak_start(
5328        engine: *mut PortableTexEngine<'_>,
5329        font: integer,
5330        locale: integer,
5331        text: *mut uint16_t,
5332        text_length: integer,
5333    ) {
5334        let Some(engine) = engine.as_mut() else {
5335            return;
5336        };
5337        let text = if text.is_null() || text_length <= 0 {
5338            &[]
5339        } else {
5340            core::slice::from_raw_parts(text as *const uint16_t, text_length as usize)
5341        };
5342        engine
5343            .platform
5344            .linebreak_start(PortableLinebreakRequest { font, locale, text });
5345    }
5346
5347    pub(crate) unsafe fn linebreak_next(engine: *mut PortableTexEngine<'_>) -> integer {
5348        engine
5349            .as_mut()
5350            .and_then(|engine| engine.platform.linebreak_next())
5351            .unwrap_or(-1)
5352    }
5353
5354    // Registers \Uhostbox during format initialization so it dumps with the format eqtb and hash.
5355    pub(crate) unsafe fn register_host_box_primitive(self: &mut Self) -> EngineFlow<()> {
5356        let eqtb = self.state.zeqtb.as_mut_ptr();
5357        let name = b"Uhostbox";
5358        for (index, byte) in name.iter().enumerate() {
5359            *self.state.buffer.offset(index as isize) = *byte as UnicodeScalar;
5360        }
5361        let saved = self.state.nonewcontrolsequence;
5362        self.state.nonewcontrolsequence = 0 as boolean;
5363        let cs = (&mut *(self as *mut PortableTexEngine<'_>))
5364            .zidlookup(0 as integer, name.len() as integer)?;
5365        self.state.nonewcontrolsequence = saved;
5366        // eq_level one, eq_type extension (59), equiv = the host box extension chr code.
5367        (*eqtb.offset(cs as isize)).hh.u.B1 = 1 as i16;
5368        (*eqtb.offset(cs as isize)).hh.u.B0 = 59 as i16;
5369        (*eqtb.offset(cs as isize)).hh.v.RH = HOST_BOX_EXTENSION_CODE as halfword;
5370        Ok(())
5371    }
5372
5373    // Allocates a size 5 marker whatsit in the glyph node metric layout (width/depth/height at
5374    // words 1..3) so hpack measures it even after rebox dissolves the enclosing shell box.
5375    // Word 4 holds the payload, a pending token id or a resolved record index.
5376    unsafe fn host_box_marker(
5377        engine: *mut PortableTexEngine<'_>,
5378        subtype: i16,
5379        payload: integer,
5380        width: scaled,
5381        height: scaled,
5382        depth: scaled,
5383    ) -> EngineFlow<halfword> {
5384        let this = &mut *engine;
5385        let marker = this.zgetnode(5 as i32)?;
5386        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5387        (*mem.offset(marker as isize)).hh.u.B0 = 8 as i16;
5388        (*mem.offset(marker as isize)).hh.u.B1 = subtype;
5389        (*mem.offset(marker as isize)).hh.v.RH = -(268435455 as i64) as halfword;
5390        (*mem.offset((marker as i32 + 1 as i32) as isize)).u.CINT = width;
5391        (*mem.offset((marker as i32 + 2 as i32) as isize)).u.CINT = depth;
5392        (*mem.offset((marker as i32 + 3 as i32) as isize)).u.CINT = height;
5393        (*mem.offset((marker as i32 + 4 as i32) as isize)).u.CINT = payload;
5394        Ok(marker)
5395    }
5396
5397    // Asks the host for the box behind `token`, None falls back to a deterministic zero size box.
5398    unsafe fn host_box_build(
5399        engine: *mut PortableTexEngine<'_>,
5400        token: integer,
5401        style: PortableHostBoxStyle,
5402        font_size: scaled,
5403    ) -> EngineFlow<halfword> {
5404        let this = &mut *engine;
5405        let response = this.platform.host_box(PortableHostBoxRequest {
5406            token,
5407            style,
5408            font_size,
5409        });
5410        let hbox = this.newnullbox()?;
5411        let Some(host_box) = response else {
5412            return Ok(hbox);
5413        };
5414        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5415        (*mem.offset((hbox as i32 + 1 as i32) as isize)).u.CINT = host_box.width;
5416        (*mem.offset((hbox as i32 + 2 as i32) as isize)).u.CINT = host_box.depth;
5417        (*mem.offset((hbox as i32 + 3 as i32) as isize)).u.CINT = host_box.height;
5418        let index = this.hostbox_records.len();
5419        let (width, height, depth) = (host_box.width, host_box.height, host_box.depth);
5420        this.hostbox_records.push(host_box);
5421        let marker = Self::host_box_marker(
5422            engine,
5423            HOST_BOX_RESOLVED_SUBTYPE,
5424            index as integer,
5425            width,
5426            height,
5427            depth,
5428        )?;
5429        (*mem.offset((hbox as i32 + 5 as i32) as isize)).hh.v.RH = marker;
5430        Ok(hbox)
5431    }
5432
5433    // \Uhostbox scan site: math defers to the mlist pass for style, other modes resolve now as Text.
5434    pub(crate) unsafe fn host_box_insert(
5435        engine: *mut PortableTexEngine<'_>,
5436        token: integer,
5437    ) -> EngineFlow<()> {
5438        let Some(this) = engine.as_mut() else {
5439            return Ok(());
5440        };
5441        let mode = (this.state.curlist.modefield as i32).abs();
5442        if mode == 209 as i32 {
5443            let marker =
5444                Self::host_box_marker(engine, HOST_BOX_PENDING_SUBTYPE, token, 0, 0, 0)?;
5445            let this = &mut *engine;
5446            let placeholder = this.newnullbox()?;
5447            let noad = this.newnoad()?;
5448            let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5449            (*mem.offset((placeholder as i32 + 5 as i32) as isize)).hh.v.RH = marker;
5450            (*mem.offset((noad as i32 + 1 as i32) as isize)).hh.v.RH = 2 as i32 as halfword;
5451            (*mem.offset((noad as i32 + 1 as i32) as isize)).hh.v.LH = placeholder;
5452            (*mem.offset(this.state.curlist.tailfield as isize)).hh.v.RH = noad;
5453            this.state.curlist.tailfield = noad;
5454        } else {
5455            let eqtb = this.state.zeqtb.as_mut_ptr();
5456            let font = (*eqtb.offset(EQTB_CUR_FONT_LOC as isize)).hh.v.RH as integer;
5457            let font_size = this.font_at_size(font);
5458            let hbox =
5459                Self::host_box_build(engine, token, PortableHostBoxStyle::Text, font_size)?;
5460            let this = &mut *engine;
5461            let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5462            (*mem.offset(this.state.curlist.tailfield as isize)).hh.v.RH = hbox;
5463            this.state.curlist.tailfield = hbox;
5464        }
5465        Ok(())
5466    }
5467
5468    // mlist pass hook: swap a pending placeholder nucleus for the host's box under curstyle.
5469    pub(crate) unsafe fn host_box_resolve_noad(
5470        engine: *mut PortableTexEngine<'_>,
5471        q: halfword,
5472    ) -> EngineFlow<()> {
5473        let Some(this) = engine.as_mut() else {
5474            return Ok(());
5475        };
5476        if q < 0 || q >= this.state.himemmin {
5477            return Ok(());
5478        }
5479        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5480        // Only Ord noads (16) carry a \Uhostbox placeholder nucleus.
5481        if (*mem.offset(q as isize)).hh.u.B0 as i32 != 16 as i32 {
5482            return Ok(());
5483        }
5484        let style_code = this.state.curstyle as i32;
5485        Self::host_box_resolve_field(engine, (q as i32 + 1 as i32) as halfword, style_code)
5486    }
5487
5488    // clean_box hook: fields copied out of single atom groups (scripts, fractions) resolve here.
5489    pub(crate) unsafe fn host_box_resolve_field(
5490        engine: *mut PortableTexEngine<'_>,
5491        field: halfword,
5492        style_code: i32,
5493    ) -> EngineFlow<()> {
5494        let Some(this) = engine.as_mut() else {
5495            return Ok(());
5496        };
5497        if field < 0 {
5498            return Ok(());
5499        }
5500        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5501        // Field must be a sub box (2) whose box wraps a pending marker whatsit.
5502        if (*mem.offset(field as isize)).hh.v.RH as i32 != 2 as i32 {
5503            return Ok(());
5504        }
5505        let placeholder = (*mem.offset(field as isize)).hh.v.LH;
5506        if placeholder < 0 || placeholder as i64 == -(268435455 as i64) {
5507            return Ok(());
5508        }
5509        let marker = (*mem.offset((placeholder as i32 + 5 as i32) as isize)).hh.v.RH;
5510        if marker < 0 || marker as i64 == -(268435455 as i64) || marker >= this.state.himemmin {
5511            return Ok(());
5512        }
5513        if (*mem.offset(marker as isize)).hh.u.B0 as i32 != 8 as i32
5514            || (*mem.offset(marker as isize)).hh.u.B1 as i32 != HOST_BOX_PENDING_SUBTYPE as i32
5515        {
5516            return Ok(());
5517        }
5518        let token = (*mem.offset((marker as i32 + 4 as i32) as isize)).u.CINT;
5519        // Style codes map to sizes as in mlist_to_hlist: below 4 is text, then script sizes.
5520        let size = if style_code < 4 {
5521            0
5522        } else {
5523            256 * ((style_code - 2) / 2)
5524        };
5525        let style = match size {
5526            0 => PortableHostBoxStyle::Text,
5527            256 => PortableHostBoxStyle::Script,
5528            _ => PortableHostBoxStyle::ScriptScript,
5529        };
5530        // Size context comes from the family 2 symbol font at the active math size.
5531        let eqtb = this.state.zeqtb.as_mut_ptr();
5532        let font = (*eqtb.offset((EQTB_MATH_FONT_FAM2_BASE + size as i64) as isize))
5533            .hh
5534            .v
5535            .RH as integer;
5536        let font_size = this.font_at_size(font);
5537        let hbox = Self::host_box_build(engine, token, style, font_size)?;
5538        // The placeholder was stamped with the \hostbox call site span at scan time, carry it onto
5539        // the replacement and its marker so the atom maps to its own bytes, not the ambient span.
5540        Self::src_carry_copy(engine as *mut Self, placeholder, hbox);
5541        let mem: *mut memoryword = (*engine).state.zmem.as_mut_ptr();
5542        let resolved_marker = (*mem.offset((hbox as i32 + 5 as i32) as isize)).hh.v.RH;
5543        if resolved_marker >= 0 && resolved_marker as i64 != -(268435455 as i64) {
5544            Self::src_carry_copy(engine as *mut Self, placeholder, resolved_marker);
5545        }
5546        (&mut *engine).zflushnodelist(placeholder)?;
5547        let this = &mut *engine;
5548        let mem: *mut memoryword = this.state.zmem.as_mut_ptr();
5549        (*mem.offset(field as isize)).hh.v.LH = hbox;
5550        Ok(())
5551    }
5552
5553    pub(crate) unsafe fn abort_engine(
5554        engine: *mut PortableTexEngine<'_>,
5555        status: integer,
5556    ) -> EngineFlow<core::convert::Infallible> {
5557        if let Some(engine) = engine.as_mut() {
5558            engine.last_abort_status = Some(status);
5559        }
5560        Err(EngineBreak::Abort(EngineAbort { status }))
5561    }
5562
5563    /// Reject the current fragment with `message` when the sandbox is active; a
5564    /// no-op (e.g. during format construction) otherwise. The sandbox analogue of
5565    /// [`abort_engine`]: it breaks the run via the `?` chain with an [`EngineError`].
5566    pub(crate) unsafe fn sandbox_reject(
5567        engine: *mut PortableTexEngine<'_>,
5568        message: &str,
5569    ) -> EngineFlow<()> {
5570        if let Some(engine) = engine.as_ref() {
5571            if engine.sandbox {
5572                return Err(EngineBreak::Error(EngineError {
5573                    message: message.into(),
5574                }));
5575            }
5576        }
5577        Ok(())
5578    }
5579
5580    /// Sandbox `$`/math-shift guard, called from `init_math` (entering math). The
5581    /// fragment legitimately enters math via the wrapper `$` (depth 0 -> 1) and may NEST
5582    /// more math inside a text block -- `\hbox{$x$}`, `\text{$y$}` -- which opens at depth
5583    /// >= 1 and is allowed. A BREAKOUT is a user `$` that, in text mode, re-opens math at
5584    /// depth 0 AFTER the wrapper already opened (the wrapper's math was closed back to the
5585    /// outer level); reject only that. No-op outside the sandbox.
5586    pub(crate) unsafe fn sandbox_open_math(
5587        engine: *mut PortableTexEngine<'_>,
5588    ) -> EngineFlow<()> {
5589        if let Some(engine) = engine.as_mut() {
5590            if engine.sandbox {
5591                if engine.sandbox_math_depth == 0 && engine.sandbox_math_opened {
5592                    return Err(EngineBreak::Error(EngineError {
5593                        message: "math shift ($) is not allowed inside a math expression"
5594                            .into(),
5595                    }));
5596                }
5597                engine.sandbox_math_opened = true;
5598                engine.sandbox_math_depth += 1;
5599            }
5600        }
5601        Ok(())
5602    }
5603
5604    /// Sandbox companion to [`sandbox_open_math`], called from `after_math` (leaving
5605    /// math): pop one MATH nesting level. Depth returns to 0 only when the wrapper math
5606    /// closes, so a nested `$x$` closing (depth 2 -> 1) does NOT mark the expression
5607    /// finished and later math stays allowed. No-op off-sandbox.
5608    pub(crate) unsafe fn sandbox_close_math(
5609        engine: *mut PortableTexEngine<'_>,
5610    ) -> EngineFlow<()> {
5611        if let Some(engine) = engine.as_mut() {
5612            if engine.sandbox && engine.sandbox_math_depth > 0 {
5613                engine.sandbox_math_depth -= 1;
5614            }
5615        }
5616        Ok(())
5617    }
5618
5619    /// Sandbox work-budget tick, called once per `main_control` iteration. Rejects
5620    /// the fragment once [`SANDBOX_OP_BUDGET`] iterations are exceeded, bounding
5621    /// runaway expansion / infinite loops (`\def\x{\x}\x`). No-op outside sandbox.
5622    pub(crate) unsafe fn sandbox_tick(
5623        engine: *mut PortableTexEngine<'_>,
5624    ) -> EngineFlow<()> {
5625        if let Some(engine) = engine.as_mut() {
5626            if engine.sandbox {
5627                engine.sandbox_ops = engine.sandbox_ops.saturating_add(1);
5628                if engine.sandbox_ops > SANDBOX_OP_BUDGET {
5629                    return Err(EngineBreak::Error(EngineError {
5630                        message: "expression is too complex or did not terminate".into(),
5631                    }));
5632                }
5633            }
5634        }
5635        Ok(())
5636    }
5637
5638    /// Surfacing hook called from `error()` right after it prints the diagnostic
5639    /// and its context: turn the TeX error into a breaking [`EngineError`]
5640    /// carrying the captured message, threaded back to the driver via the `?`
5641    /// chain like an abort. TeX's normal log-and-recover never runs inside an
5642    /// equation render -- an error is always real and is reported, not swallowed.
5643    /// Declared `EngineFlow<()>` (not `<Infallible>`) so the unreachable tail of
5644    /// `error()` stays warning-free.
5645    pub(crate) unsafe fn surface_error(
5646        engine: *mut PortableTexEngine<'_>,
5647    ) -> EngineFlow<()> {
5648        let message = engine
5649            .as_ref()
5650            .map(|engine| engine.capture_last_error_message())
5651            .unwrap_or_else(|| "TeX error".into());
5652        Err(EngineBreak::Error(EngineError { message }))
5653    }
5654
5655    // =====================================================================
5656    // Source tracking (SpanField + CmdLatch). All of this is gated on the
5657    // runtime `source_tracking` flag; with it false every hook below is a
5658    // cheap predictable no-op and the default render path allocates nothing.
5659    // The only "inheritance" is the two principled rules: (a) a macro body
5660    // inherits its INVOCATION span (the call-site baseline), and (b) math
5661    // noads re-point `cmd_span` from their own parse-time `node_src`. There
5662    // is no byte-matching, input-stack scanning, nearest-macro guessing, or
5663    // blanket parent inheritance.
5664    // =====================================================================
5665
5666    /// Enable/disable source tracking, lazily (re)allocating the `node_src`
5667    /// shadow (sized to `mem`) and clearing the intern tables. Resets all the
5668    /// transient registers so a render starts clean. Default off.
5669    pub fn set_source_tracking(self: &mut Self, on: bool) {
5670        self.state.source_tracking = on;
5671        self.state.cmd_span = 0;
5672        self.state.pending_call_span = 0;
5673        self.state.src_token_start = 0;
5674        self.state.src_line_base = 0;
5675        self.state.src_line_buf_start = 0;
5676        self.state.src_prev_line_len = 0;
5677        self.state.src_line_initialized = false;
5678        self.state.src_call_start = 0;
5679        self.state.src_call_name = 0;
5680        self.state.src_call_state = 0;
5681        self.state.src_call_index = 0;
5682        self.state.src_call_span = 0;
5683        self.state.src_call_argspan = 0;
5684        self.state.src_user_cmd_span = 0;
5685        self.state.src_tok_span = 0;
5686        self.state.src_anchor_cmd = 0;
5687        self.state.src_grp_stack.clear();
5688        self.state.src_grp_closing = 0;
5689        self.state.src_call_user_span = 0;
5690        self.state.curinput.spanfield = 0;
5691        self.state.src_spans.clear();
5692        self.state.src_dedup.clear();
5693        self.state.src_native_offsets.clear();
5694        self.state.src_stack_cells.clear();
5695        self.state.cur_stack_head = 0;
5696        let end = if on { self.state.mem.len() } else { 0 };
5697        self.state.node_src = PagedArray::new(0, end, node_src_default, node_src_sig);
5698        self.state.node_stack = PagedArray::new(0, end, node_stack_default, node_stack_sig);
5699    }
5700
5701    /// Whether source tracking is currently enabled.
5702    pub fn source_tracking_enabled(&self) -> bool {
5703        self.state.source_tracking
5704    }
5705
5706    /// Intern a span into the dedup table, returning a stable first-touch
5707    /// `SrcId` (1-based; `0` is NONE). Uses the explicit `self: &mut Self`
5708    /// receiver form the patcher's passes expect (the `&mut self` shorthand is
5709    /// stripped).
5710    fn intern_span_raw(self: &mut Self, name: strnumber, start: u32, end: u32, role: u8) -> SrcId {
5711        let span = RawSpan { name, start, end, role };
5712        if let Some(&id) = self.state.src_dedup.get(&span) {
5713            return id;
5714        }
5715        self.state.src_spans.push(span);
5716        let id = self.state.src_spans.len() as SrcId;
5717        self.state.src_dedup.insert(span, id);
5718        id
5719    }
5720
5721    /// Absolute character offset, in the primary input's own coordinates, of a
5722    /// buffer position `loc`: `line_base + (loc - line_start)`.
5723    fn src_buf_offset(&self, loc: integer) -> u32 {
5724        let col = loc as i64 - self.state.src_line_buf_start as i64;
5725        (self.state.src_line_base as i64 + col).max(0) as u32
5726    }
5727
5728    /// Resolve a node's stamped span to a [`PortableSourceSpan`] in source-own
5729    /// coordinates, or `None` when the node is unstamped (SrcId 0) or its source
5730    /// name cannot be read. `&self`: safe on the read-only IR snapshot path.
5731    pub(crate) fn resolve_node_src(&self, node: halfword) -> Option<PortableSourceSpan> {
5732        if !self.state.source_tracking || node < 0 {
5733            return None;
5734        }
5735        let id = self.state.node_src.get_copy(node as usize);
5736        if id == 0 {
5737            return None;
5738        }
5739        let raw = *self.state.src_spans.get((id - 1) as usize)?;
5740        let name = unsafe { self.pool_string(raw.name) }?;
5741        Some(PortableSourceSpan {
5742            name,
5743            start: raw.start,
5744            end: raw.end,
5745            role: raw.role,
5746        })
5747    }
5748
5749    /// HOOK 1a (`get_next`, top of the outer loop): snapshot the buffer position
5750    /// of the token about to be lexed. Re-run each loop iteration so leading
5751    /// skipped material (comments / ignored chars) is excluded from the span.
5752    pub(crate) unsafe fn src_mark_token_start(engine: *mut Self) {
5753        let Some(engine) = engine.as_mut() else {
5754            return;
5755        };
5756        if engine.state.source_tracking && engine.state.curinput.statefield as i32 != 0 {
5757            engine.state.src_token_start = engine.state.curinput.locfield as integer;
5758        }
5759    }
5760
5761    /// HOOK 1b (`get_next` tail): the SOLE buffer producer. For real buffer
5762    /// input set the ambient `spanfield` to the just-lexed TOKEN range (so a
5763    /// control word spans backslash..last letter). Token-list input is handled
5764    /// by [`Self::src_tokenlist_span`], not here.
5765    pub(crate) unsafe fn src_record_buffer_span(engine: *mut Self) {
5766        let Some(engine) = engine.as_mut() else {
5767            return;
5768        };
5769        if !engine.state.source_tracking || engine.state.curinput.statefield as i32 == 0 {
5770            return;
5771        }
5772        let a = engine.src_buf_offset(engine.state.src_token_start);
5773        let b = engine.src_buf_offset(engine.state.curinput.locfield as integer);
5774        let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
5775        let name = engine.state.curinput.namefield as strnumber;
5776        let id = engine.intern_span_raw(name, lo, hi, 0);
5777        engine.state.curinput.spanfield = id;
5778        engine.state.src_tok_span = id;
5779        // When the just-lexed buffer token is a CONTROL SEQUENCE (`curcs != 0`) of
5780        // the primary fragment, it is a user-typed command; remember it as the
5781        // active user command. Kernel helper macros reached through its expansion
5782        // are read from token lists (not the buffer), so they never reset this, and
5783        // it survives token-list pops — letting a helper invoked from the buffer
5784        // after a `\futurelet`/`\@ifnextchar` peek recover the user command start.
5785        //
5786        // Gate on `scannerstatus == 0` (normal): a cs lexed while the scanner is
5787        // MATCHING/ABSORBING another macro's arguments is being COLLECTED, not
5788        // executed -- it belongs to that outer command's argument, not the active
5789        // command line. In `\sqrt[\phantom{x}]{x}` the `\phantom` is absorbed into
5790        // `\@sqrt`'s optional `[..]` while matching, so without this gate it
5791        // overwrites the real user command `\sqrt`; the later radicand helper then
5792        // anchors its buffer baseline to the stale `\phantom` start, framing the
5793        // degree box as `[\phantom .. radicand]` = the `[6,21)` overshoot. A digit
5794        // degree (`\sqrt[3]{x}`) has no cs to hijack, which is why it never bit.
5795        if engine.state.curcs != 0
5796            && name == engine.state.src_primary_name
5797            && engine.state.scannerstatus as i32 == 0
5798        {
5799            engine.state.src_user_cmd_span = id;
5800            // A buffer read means we have left any token-list replay context, so the
5801            // replay-tracked anchor command is now stale: clear it. (`src_user_cmd_span`,
5802            // by contrast, intentionally persists so a `\futurelet`-peeked helper can
5803            // still recover the buffer command.)
5804            engine.state.src_anchor_cmd = 0;
5805        }
5806    }
5807
5808    /// HOOK 1c (`get_next` token-list branch): when re-reading from an ARGUMENT
5809    /// / template / backed-up / inserted level (token type < `macro`=5), surface
5810    /// the re-read cell's own scan-time span so a macro argument recovers its
5811    /// typed position. Macro-body (`macro`=5) and `every_*` (>=6) levels keep the
5812    /// inherited call-site baseline, so synthesized body content maps to the
5813    /// invocation.
5814    pub(crate) unsafe fn src_tokenlist_span(engine: *mut Self) {
5815        let Some(engine) = engine.as_mut() else {
5816            return;
5817        };
5818        if !engine.state.source_tracking {
5819            return;
5820        }
5821        if engine.state.curinput.indexfield as i32 >= 5 {
5822            return;
5823        }
5824        // Freeze the token's OWN origin as it is read, so a later `back_input` can
5825        // re-stamp it with this (not the ambient, look-ahead-advanced span). Only for
5826        // NON-macro-body levels (`idx < 5`): a macro body's tokens map to their
5827        // invocation (rule a), never to their definition site, so letting their cell
5828        // span leak here would make a backed-up body token (e.g. `\frac`'s `\over`)
5829        // carry its definition position instead of the call-site baseline.
5830        {
5831            let lf = engine.state.curinput.locfield as i32;
5832            if lf >= 0 {
5833                let cid = engine.state.node_src.get_copy(lf as usize);
5834                if cid != 0 {
5835                    engine.state.src_tok_span = cid;
5836                }
5837            }
5838        }
5839        let cell = engine.state.curinput.locfield as i32;
5840        if cell < 0 {
5841            return;
5842        }
5843        let id = engine.state.node_src.get_copy(cell as usize);
5844        if id != 0 {
5845            engine.state.curinput.spanfield = id;
5846        }
5847    }
5848
5849    /// HOOK 1d (`back_input`): when a token is pushed back onto the input, stamp the
5850    /// freshly-allocated backed-up cell with the token's OWN origin (`src_tok_span`,
5851    /// frozen at the token's last read) rather than the ambient `spanfield`. The two
5852    /// diverge whenever the lexer has read PAST the token before backing it up -- the
5853    /// `\let`/`\futurelet` (and thus `\@ifnextchar`) two-token look-ahead reads a
5854    /// second token, advancing `spanfield`, then re-emits the first. Without this the
5855    /// re-emitted token (e.g. the single-char optional `[a]` degree of `\sqrt`, which
5856    /// LaTeX's `\@ifnextchar`-driven `\sqrt`/`\root` machinery shuttles through such a
5857    /// look-ahead) would inherit the look-ahead's span -- the construct baseline --
5858    /// and the typed char's byte-span would be lost. The token's real origin is still
5859    /// live in `src_tok_span`, so the leaf is recoverable here, at the re-emission.
5860    pub(crate) unsafe fn src_back_input_stamp(engine: *mut Self, p: halfword) {
5861        let Some(engine) = engine.as_mut() else {
5862            return;
5863        };
5864        if !engine.state.source_tracking || p < 0 {
5865            return;
5866        }
5867        let id = engine.state.src_tok_span;
5868        if id != 0 {
5869            engine.state.node_src.set(p as usize, id);
5870        }
5871    }
5872
5873    /// HOOK 2 (`main_control` dispatch, right after `get_x_token`): freeze the
5874    /// commanding token's span before it runs any argument sub-scan. This single
5875    /// site solves `\char`/`\mathchar`/`\accent` scan-loss for free.
5876    pub(crate) unsafe fn src_latch_cmd_span(engine: *mut Self) {
5877        let Some(engine) = engine.as_mut() else {
5878            return;
5879        };
5880        if engine.state.source_tracking {
5881            engine.state.cmd_span = engine.state.curinput.spanfield;
5882        }
5883    }
5884
5885    /// HOOK 3 (`get_avail`): stamp every single-word cell with the ambient span
5886    /// — token cells (so re-read arguments recover their typed position) and TFM
5887    /// char nodes provisionally (overwritten by [`Self::src_stamp_char`]). Also
5888    /// snapshots the live enclosing-construct stack head onto `node_stack`.
5889    pub(crate) unsafe fn src_stamp_avail(engine: *mut Self, node: halfword) {
5890        let Some(engine) = engine.as_mut() else {
5891            return;
5892        };
5893        if engine.state.source_tracking && node >= 0 {
5894            let id = engine.state.curinput.spanfield;
5895            engine.state.node_src.set(node as usize, id);
5896            engine.state.node_stack.set(node as usize, engine.state.cur_stack_head);
5897        }
5898    }
5899
5900    /// HOOK 4 (`get_node`): stamp every variable-size node AND every noad over
5901    /// its whole address range with the current construct span, so the nucleus
5902    /// subfield inherits the atom's span with no separate math-field writer. Also
5903    /// snapshots the live enclosing-construct stack head onto `node_stack` over the
5904    /// same range (independent of `cmd_span`, so a node allocated inside a
5905    /// construct still records its enclosure even when its primary is unstamped).
5906    pub(crate) unsafe fn src_stamp_node_range(engine: *mut Self, node: halfword, size: integer) {
5907        let Some(engine) = engine.as_mut() else {
5908            return;
5909        };
5910        if !engine.state.source_tracking || node < 0 || size <= 0 {
5911            return;
5912        }
5913        let id = engine.state.cmd_span;
5914        let head = engine.state.cur_stack_head;
5915        let base = node as usize;
5916        for i in 0..size as usize {
5917            if id != 0 {
5918                engine.state.node_src.set(base + i, id);
5919            }
5920            if head != 0 {
5921                engine.state.node_stack.set(base + i, head);
5922            }
5923        }
5924    }
5925
5926    /// HOOK (`copy_node_list`, after each node is duplicated): a COPY has the same
5927    /// source origin as its original, so propagate the original's tracked span (and
5928    /// enclosing-construct chain) onto the copy. Without this the copy keeps only the
5929    /// ambient `cmd_span` that `get_node` stamped at copy time — which for a
5930    /// `\mathchoice`-replicated `\sqrt[#1]{}` degree is the whole construct hull, so the
5931    /// degree digit `3` maps to `\sqrt[3]{..}` instead of its own `"3"`. Only overrides
5932    /// when the original is stamped (id != 0); an unstamped source leaves the copy's
5933    /// `get_node` stamp intact. Uniform across every copied node, by closure.
5934    pub(crate) unsafe fn src_carry_copy(engine: *mut Self, src: halfword, dst: halfword) {
5935        let Some(engine) = engine.as_mut() else {
5936            return;
5937        };
5938        if !engine.state.source_tracking || src < 0 || dst < 0 {
5939            return;
5940        }
5941        let id = engine.state.node_src.get_copy(src as usize);
5942        if id != 0 {
5943            engine.state.node_src.set(dst as usize, id);
5944            let head = engine.state.node_stack.get_copy(src as usize);
5945            engine.state.node_stack.set(dst as usize, head);
5946        }
5947    }
5948
5949    /// HOOK (token COPY): the analogue of [`Self::src_carry_copy`] for the TOKEN
5950    /// (not node) memory. Token-list copies go through `store_new_token(info(src))`,
5951    /// which copies only the token VALUE -- so the new cell `dest` would keep the
5952    /// ambient `get_avail` stamp and lose `src`'s real origin. Carry `src`'s tracked
5953    /// span (and enclosing chain) onto `dest`, so a source byte-span rides token
5954    /// copies the same way it rides the input stack. This is what lets a SINGLE-token
5955    /// macro argument (e.g. the optional `[a]` degree of `\sqrt`) keep its own leaf
5956    /// span through expl3's argument re-tokenisation, instead of collapsing to the
5957    /// `\sqrt[a]` construct hull. Uniform across every token copy, by closure: the
5958    /// anchor is the WEB-layer `src_token_copy` marker (added in the change file at
5959    /// every `store_new_token(info(..))` site), not a fragile inlined Rust pattern --
5960    /// so it holds for tex, etex and xetex identically.
5961    pub(crate) unsafe fn src_carry_token_span(engine: *mut Self, dest: halfword, src: halfword) {
5962        let Some(engine) = engine.as_mut() else {
5963            return;
5964        };
5965        if !engine.state.source_tracking || dest < 0 || src < 0 {
5966            return;
5967        }
5968        let id = engine.state.node_src.get_copy(src as usize);
5969        if id != 0 {
5970            engine.state.node_src.set(dest as usize, id);
5971            let head = engine.state.node_stack.get_copy(src as usize);
5972            engine.state.node_stack.set(dest as usize, head);
5973        }
5974    }
5975
5976    /// HOOK 5 (`new_character`): overwrite the provisional get_avail stamp on a
5977    /// TFM char glyph with the construct span, so `\char98` maps to the command,
5978    /// not the scanned digits. Also records the live enclosing-construct stack head
5979    /// (overridden for `make_ord` nuclei by [`Self::src_carry_nucleus`]).
5980    pub(crate) unsafe fn src_stamp_char(engine: *mut Self, node: halfword) {
5981        let Some(engine) = engine.as_mut() else {
5982            return;
5983        };
5984        if !engine.state.source_tracking || node < 0 {
5985            return;
5986        }
5987        let id = engine.state.cmd_span;
5988        if id != 0 {
5989            engine.state.node_src.set(node as usize, id);
5990        }
5991        engine.state.node_stack.set(node as usize, engine.state.cur_stack_head);
5992    }
5993
5994    /// HOOK 6 (`mlist_to_hlist` noad-loop head): re-point `cmd_span` to noad
5995    /// `q`'s own parse-time span, so every bar/surd/delimiter/kern synthesized
5996    /// for `q` inherits it — defeating end-of-math `$` staleness with one read.
5997    pub(crate) unsafe fn src_mlist_repoint(engine: *mut Self, q: halfword) {
5998        let Some(engine) = engine.as_mut() else {
5999            return;
6000        };
6001        if !engine.state.source_tracking || q < 0 {
6002            return;
6003        }
6004        let id = engine.state.node_src.get_copy(q as usize);
6005        if id != 0 {
6006            engine.state.cmd_span = id;
6007        }
6008    }
6009
6010    /// HOOK (`scan_math` field commit): a math FIELD (nucleus/sub/sup/accent/radical)
6011    /// holding a single math-char is filled by `scan_math` directly into the field
6012    /// word -- it is NOT a separately-allocated noad, so it carries the enclosing
6013    /// noad's stamp, not its own char's. Record the field char's tracked span (the
6014    /// ambient `spanfield` of the token that produced it, captured at the commit
6015    /// before any look-ahead moves it) keyed on the field ADDRESS, so `clean_box`
6016    /// can carry it onto the fresh noad it builds. Uniform: every scanned single-char
6017    /// field, by closure -- no per-construct code.
6018    pub(crate) unsafe fn src_stamp_field(engine: *mut Self, field: halfword) {
6019        let Some(engine) = engine.as_mut() else {
6020            return;
6021        };
6022        if !engine.state.source_tracking || field < 0 {
6023            return;
6024        }
6025        let id = engine.state.curinput.spanfield;
6026        if id != 0 {
6027            engine.state.node_src.set(field as usize, id);
6028        }
6029    }
6030
6031    /// HOOK (`clean_box` math-char case): when `clean_box` packages a single-math-char
6032    /// FIELD it allocates a FRESH noad and copies the field word into its nucleus; the
6033    /// fresh noad was stamped by `get_node` with the ambient (enclosing atom's)
6034    /// `cmd_span`, so the mlist re-point would map the cleaned glyph to the BASE. Carry
6035    /// the field's own tracked source (recorded at `src_stamp_field`) onto the fresh
6036    /// noad so the re-point yields the field char's real origin (fixes `x^2`->`2`,
6037    /// `^{\infty}`->`\infty`). Uniform copy-carrier; no glyph/construct logic.
6038    pub(crate) unsafe fn src_carry_field(engine: *mut Self, field: halfword, noad: halfword) {
6039        let Some(engine) = engine.as_mut() else {
6040            return;
6041        };
6042        if !engine.state.source_tracking || field < 0 || noad < 0 {
6043            return;
6044        }
6045        let id = engine.state.node_src.get_copy(field as usize);
6046        let head = engine.state.node_stack.get_copy(field as usize);
6047        if id != 0 {
6048            // noad_size = 4; stamp the whole noad range so the loop-head re-point
6049            // (reads node_src[noad]) and the nucleus both see the field's source.
6050            for i in 0..4usize {
6051                engine.state.node_src.set(noad as usize + i, id);
6052            }
6053        }
6054        // Carry the field's enclosing-construct chain too, so the cleaned glyph's
6055        // enclosing entries match the field char's nesting, not the fresh noad's.
6056        if head != 0 {
6057            for i in 0..4usize {
6058                engine.state.node_stack.set(noad as usize + i, head);
6059            }
6060        }
6061    }
6062
6063    /// HOOK (`mlist_to_hlist` make_ord nucleus attach): a directly-built math-char
6064    /// nucleus glyph is the non-`clean_box` analogue of [`Self::src_carry_field`].
6065    /// `get_node`/`new_character` stamped it with the enclosing noad's construct
6066    /// span, so a typed char wrapped in an atom (`\mathbin{+}` -> `+`) would map to
6067    /// the construct. Carry the nucleus FIELD's own leaf span -- recorded at
6068    /// `scan_math` by [`Self::src_stamp_field`], or by the brace-collapse carry --
6069    /// onto the freshly-built glyph node, so it maps to its own char. Per-glyph
6070    /// only; never touches `cmd_span`, so a structural rule built for the same noad
6071    /// afterward still inherits the construct span via the loop-head re-point.
6072    /// Uniform: every directly-built ord-like nucleus glyph, by closure -- no
6073    /// per-construct code. When the field carries no leaf span (`\char`/`\mathchar`,
6074    /// no `scan_math`) the carry is a no-op and the construct stamp stands.
6075    pub(crate) unsafe fn src_carry_nucleus(engine: *mut Self, noad: halfword, glyph: halfword) {
6076        let Some(engine) = engine.as_mut() else {
6077            return;
6078        };
6079        if !engine.state.source_tracking || noad < 0 || glyph < 0 {
6080            return;
6081        }
6082        let id = engine.state.node_src.get_copy(noad as usize + 1);
6083        if id != 0 {
6084            engine.state.node_src.set(glyph as usize, id);
6085        }
6086        // Carry the nucleus field's enclosing-construct chain onto the glyph, so a
6087        // typed char's enclosing entries are its parse-time nesting (e.g. the
6088        // `\mathbin{+}` group frame) rather than the layout-time stack.
6089        let head = engine.state.node_stack.get_copy(noad as usize + 1);
6090        engine.state.node_stack.set(glyph as usize, head);
6091    }
6092
6093    /// HOOK (`handle_right_brace` math-group collapse): when a braced sub-formula
6094    /// `^{\infty}` / `_{y}` reduces to a SINGLE math-char noad, its nucleus is copied
6095    /// into the saved FIELD word and the noad is freed -- losing the noad's tracked
6096    /// source. Carry `node_src[noad]` onto the field FIRST, so the later `clean_box`
6097    /// (via `src_carry_field`) maps the cleaned glyph to the braced char's own origin
6098    /// rather than the enclosing atom's. Uniform; no glyph/construct logic.
6099    pub(crate) unsafe fn src_carry_collapse(engine: *mut Self, noad: halfword, field: halfword) {
6100        let Some(engine) = engine.as_mut() else {
6101            return;
6102        };
6103        if !engine.state.source_tracking || noad < 0 || field < 0 {
6104            return;
6105        }
6106        let id = engine.state.node_src.get_copy(noad as usize);
6107        if id != 0 {
6108            engine.state.node_src.set(field as usize, id);
6109        }
6110        // Carry the collapsing noad's enclosing chain (e.g. the math-group frame
6111        // built between its `{`/`}`) onto the field, so the later nucleus carry
6112        // gives the cleaned glyph its braced construct as an enclosing entry.
6113        let head = engine.state.node_stack.get_copy(noad as usize);
6114        if head != 0 {
6115            engine.state.node_stack.set(field as usize, head);
6116        }
6117    }
6118
6119    /// HOOK 7-aux save (`clean_box` entry): snapshot `cmd_span` so it can be
6120    /// restored across recursive sub-box cleaning.
6121    pub(crate) unsafe fn src_save_cmd_span(engine: *mut Self) -> u32 {
6122        engine.as_ref().map_or(0, |engine| engine.state.cmd_span)
6123    }
6124
6125    /// HOOK 7-aux restore (`clean_box` exit): put the enclosing construct's span
6126    /// back so the structural rule built afterward inherits it.
6127    pub(crate) unsafe fn src_restore_cmd_span(engine: *mut Self, saved: u32) {
6128        if let Some(engine) = engine.as_mut() {
6129            if engine.state.source_tracking {
6130                engine.state.cmd_span = saved;
6131            }
6132        }
6133    }
6134
6135    // --- Enclosing-construct stack (source-tracking inc2) -------------------
6136    // A small arena of parent-linked frames snapshotting the construct nesting
6137    // (macro invocations + delimited primitive argument groups). `cur_stack_head`
6138    // is the live top; each node records it in `node_stack` at allocation. The
6139    // frames are resolved at IR-emit time into role-tagged EnclosingConstruct
6140    // entries, so a consumer can pick any altitude from the leaf primary up.
6141
6142    /// Push a finalized construct frame (its span already known, e.g. a macro
6143    /// invocation hull) and make it the live top. Returns the new 1-based head.
6144    fn src_stack_push_span(self: &mut Self, span: SrcId) -> u32 {
6145        self.state.src_stack_cells.push(SrcStackCell {
6146            span,
6147            parent: self.state.cur_stack_head,
6148            start: 0,
6149            name: 0,
6150            pending: false,
6151        });
6152        let head = self.state.src_stack_cells.len() as u32;
6153        self.state.cur_stack_head = head;
6154        head
6155    }
6156
6157    /// Push a PENDING group frame: its start offset + source name are known at the
6158    /// `{` open, its end is finalized at the matching `}` close. Made the live top.
6159    fn src_stack_push_pending(self: &mut Self, start: u32, name: strnumber) -> u32 {
6160        self.state.src_stack_cells.push(SrcStackCell {
6161            span: 0,
6162            parent: self.state.cur_stack_head,
6163            start,
6164            name,
6165            pending: true,
6166        });
6167        let head = self.state.src_stack_cells.len() as u32;
6168        self.state.cur_stack_head = head;
6169        head
6170    }
6171
6172    /// Pop the live top frame (LIFO), restoring its parent as the head.
6173    fn src_stack_pop(self: &mut Self) {
6174        let head = self.state.cur_stack_head;
6175        if head != 0 {
6176            if let Some(cell) = self.state.src_stack_cells.get((head - 1) as usize) {
6177                self.state.cur_stack_head = cell.parent;
6178            }
6179        }
6180    }
6181
6182    /// HOOK 8b (`end_token_list`): pop the macro-body frame when a macro-body level
6183    /// (token type 6) ends, keeping the stack symmetric with HOOK 8a. Also captures
6184    /// the FURTHEST-reaching last-token span across the levels popped after a
6185    /// `macro_call` (`src_call_argspan`, reset to 0 at `src_macro_begin`) -- the
6186    /// closing `}` of the macro's final brace argument -- for the argument hull in
6187    /// [`Self::src_macro_set_pending`]. The MAX-end choice (not just the first pop)
6188    /// recovers the trailing `}` for a `\mathchoice`-replayed robust `\frac␣`, whose
6189    /// pop order surfaces the cs span first and the closing brace later.
6190    pub(crate) unsafe fn src_end_token_list(engine: *mut Self, token_type: i32) {
6191        let Some(engine) = engine.as_mut() else {
6192            return;
6193        };
6194        if !engine.state.source_tracking {
6195            return;
6196        }
6197        let cand = engine.state.curinput.spanfield;
6198        if cand != 0 {
6199            let cand_end = engine
6200                .state
6201                .src_spans
6202                .get((cand - 1) as usize)
6203                .map(|r| r.end)
6204                .unwrap_or(0);
6205            let cur_end = if engine.state.src_call_argspan != 0 {
6206                engine
6207                    .state
6208                    .src_spans
6209                    .get((engine.state.src_call_argspan - 1) as usize)
6210                    .map(|r| r.end)
6211                    .unwrap_or(0)
6212            } else {
6213                0
6214            };
6215            if engine.state.src_call_argspan == 0 || cand_end > cur_end {
6216                engine.state.src_call_argspan = cand;
6217            }
6218        }
6219        if token_type == 6 {
6220            engine.src_stack_pop();
6221        }
6222    }
6223
6224    /// HOOK (`scan_math` `{`-argument open): push a PENDING enclosing frame for a
6225    /// delimited primitive argument (`\mathbin{+}`, `\sqrt[..]{..}` radicand, ...).
6226    /// Its extent starts at the enclosing command's own start (the live `cmd_span`,
6227    /// e.g. `\mathbin`) and is finalized at the matching `}` close to span the whole
6228    /// `cmd{...}` construct. When no command is active the frame is empty (skipped
6229    /// at resolve). General: every scan_math braced field, by closure.
6230    pub(crate) unsafe fn src_scan_math_group_open(engine: *mut Self) {
6231        let Some(engine) = engine.as_mut() else {
6232            return;
6233        };
6234        if !engine.state.source_tracking {
6235            return;
6236        }
6237        let cmd = engine.state.cmd_span;
6238        let (start, name) = if cmd != 0 {
6239            match engine.state.src_spans.get((cmd - 1) as usize) {
6240                Some(raw) => (raw.start, raw.name),
6241                None => (0, 0),
6242            }
6243        } else {
6244            (0, 0)
6245        };
6246        engine.src_stack_push_pending(start, name);
6247        // Consumed-extent: push the group's OPENING `{` token span (the live spanfield
6248        // of the brace just scanned) for `src_construct_extent`. Distinct from the
6249        // enclosing-frame start above (which is the enclosing COMMAND): `min(noad, {)`
6250        // lets a bare `{n\choose k}` group pull its fraction's start to the `{`.
6251        engine.state.src_grp_stack.push(engine.state.curinput.spanfield);
6252    }
6253
6254    /// HOOK (`handle_right_brace` math-group close): finalize the PENDING group
6255    /// frame (end = the post-`}` buffer offset, same source as the start) and pop
6256    /// it. The interned `[start,end)` is the full `cmd{...}` extent.
6257    pub(crate) unsafe fn src_scan_math_group_close(engine: *mut Self) {
6258        let eptr = engine;
6259        let Some(e) = engine.as_mut() else {
6260            return;
6261        };
6262        if !e.state.source_tracking {
6263            return;
6264        }
6265        // Consumed-extent: pop this group's opening `{` span, hand it to
6266        // `src_construct_extend_to_loc` (later in the same `9 =>` arm) as the construct's
6267        // group-open for the `min(noad, {)` start.
6268        e.state.src_grp_closing = e.state.src_grp_stack.pop().unwrap_or(0);
6269        let grp = e.state.src_grp_closing;
6270        // A `\over`/`\atop`/`\choose` in this group leaves the in-progress generalized
6271        // fraction noad in `curlist.auxfield` (still set here, before `fin_mlist`). Apply
6272        // the SAME group consumed-extent to it so its bar / `\atopwithdelims` delimiters
6273        // map to the whole `{..\choose..}` group -- the one rule reaches the fraction too.
6274        let aux = e.state.curlist.auxfield.u.CINT;
6275        let frac = if aux > 0 && aux != -(268435455 as i32) { aux } else { -1 };
6276        if e.state.cur_stack_head != 0 {
6277            let idx = (e.state.cur_stack_head - 1) as usize;
6278            if let Some(cell) = e.state.src_stack_cells.get(idx).copied() {
6279                // Only finalize a still-pending group frame whose source matches the live
6280                // buffer; otherwise just pop (defensive against any non-group top).
6281                if cell.pending
6282                    && cell.name != 0
6283                    && cell.name == e.state.curinput.namefield as strnumber
6284                {
6285                    let end = e.src_buf_offset(e.state.curinput.locfield as integer);
6286                    let (lo, hi) = if cell.start <= end {
6287                        (cell.start, end)
6288                    } else {
6289                        (end, cell.start)
6290                    };
6291                    let id = e.intern_span_raw(cell.name, lo, hi, 1);
6292                    if let Some(c) = e.state.src_stack_cells.get_mut(idx) {
6293                        c.span = id;
6294                        c.pending = false;
6295                    }
6296                }
6297                e.state.cur_stack_head = cell.parent;
6298            }
6299        }
6300        if frac >= 0 {
6301            Self::src_construct_extent(eptr, frac, grp);
6302        }
6303    }
6304
6305    /// HOOK (`math_radical` / `math_ac`, right after the construct noad is
6306    /// allocated): anchor its source START to the in-fragment USER command. The
6307    /// noad was just stamped by `get_node` with the ambient `cmd_span`, which for a
6308    /// `\@ifnextchar`-peeked construct (`\sqrt{y}` -> `\sqrtsign` dispatched while
6309    /// the buffer `spanfield` still points at the peeked `{`) is the radicand brace,
6310    /// not the command. The noad is allocated BEFORE its field is scanned, so the
6311    /// live `src_user_cmd_span` is exactly the command the user typed (no inner
6312    /// construct lexed yet). Pull the START left to it (same source, only leftward),
6313    /// keeping the end; the matching `src_construct_extend_to_loc` then grows the end
6314    /// past the field. Uniform: every mark-synthesizing construct primitive, by
6315    /// closure -- no per-construct code, no heuristic (the command<->noad link is
6316    /// the tracked user-command register, not source adjacency).
6317    pub(crate) unsafe fn src_construct_anchor(engine: *mut Self) {
6318        let Some(engine) = engine.as_mut() else {
6319            return;
6320        };
6321        if !engine.state.source_tracking {
6322            return;
6323        }
6324        let noad = engine.state.curlist.tailfield as i32;
6325        if noad < 0 {
6326            return;
6327        }
6328        let id = engine.state.node_src.get_copy(noad as usize);
6329        if id == 0 {
6330            return;
6331        }
6332        let Some(raw) = engine.state.src_spans.get((id - 1) as usize).copied() else {
6333            return;
6334        };
6335        // Prefer the replay-aware anchor command (set while a NESTED construct was
6336        // replayed from a token list); fall back to the buffer user command ONLY when
6337        // the anchor is absent or from a different source. `src_anchor_cmd` is reset
6338        // on every buffer read, so it never leaks across sibling constructs.
6339        //
6340        // The anchor and the fallback are NOT interchangeable candidates to pick
6341        // whichever "wins" a leftward-pull check: a valid, same-source anchor means
6342        // this noad IS the replayed nested construct, so the buffer user command (the
6343        // OUTER construct enclosing the replay, e.g. Cardano's outer `\sqrt[3]{..}`)
6344        // must never be consulted for it -- not even as a fallback -- regardless of
6345        // whether the anchor itself happens to already equal the noad's own start (no
6346        // pull needed: the noad is already correctly anchored to itself, not to the
6347        // buffer command of note). Only ever pulls the START leftward.
6348        let anchor = (engine.state.src_anchor_cmd != 0)
6349            .then(|| engine.state.src_spans.get((engine.state.src_anchor_cmd - 1) as usize).copied())
6350            .flatten()
6351            .filter(|u| u.name == raw.name);
6352        let chosen = match anchor {
6353            Some(u) => {
6354                if u.start < raw.start {
6355                    Some(u)
6356                } else {
6357                    None
6358                }
6359            }
6360            None => (engine.state.src_user_cmd_span != 0)
6361                .then(|| engine.state.src_spans.get((engine.state.src_user_cmd_span - 1) as usize).copied())
6362                .flatten()
6363                .filter(|u| u.name == raw.name && u.start < raw.start),
6364        };
6365        let Some(u) = chosen else {
6366            return;
6367        };
6368        let newid = engine.intern_span_raw(raw.name, u.start, raw.end, raw.role);
6369        engine.state.node_src.set(noad as usize, newid);
6370    }
6371
6372    /// HOOK (`handle_right_brace` math-group close, after the nucleus field is
6373    /// filled): the spec's "extend to loc at noad commit" half of the construct
6374    /// rule. A construct primitive (`\radical`, `\mathaccent`, hence `\sqrt{y}`,
6375    /// `\hat{x}`, bare `\radical..{y}`) scans its nucleus `{..}` AFTER its command:
6376    /// `scan_math` RETURNS at the opening `{` and the field is filled only when the
6377    /// group closes here, so the noad stamped at allocation covers only the command.
6378    /// Extend the construct noad's source END to the post-`}` buffer loc, giving the
6379    /// surd/vinculum/accent the FULL `cmd{..}` extent. The START (the latched
6380    /// command) is kept, so this is pure right-extension; the nucleus field span is
6381    /// never touched, so the radicand glyph keeps its own char. A token-list-replayed
6382    /// nucleus (a NESTED `\sqrt{..}`'s radicand inside a degree-form outer) has a mem-ptr
6383    /// loc, so the end comes from the just-closed `}` token's own span instead of the
6384    /// buffer loc. Uniform across every construct whose nucleus is its `+1` field, by
6385    /// closure -- no per-construct code.
6386    pub(crate) unsafe fn src_construct_extend_to_loc(engine: *mut Self) {
6387        let e = match engine.as_ref() {
6388            Some(e) => e,
6389            None => return,
6390        };
6391        if !e.state.source_tracking {
6392            return;
6393        }
6394        // Only the construct's OWN nucleus (its `+1` field): a sub/superscript field
6395        // (`+2`/`+3`) closing must not extend the base atom.
6396        let field = (*e
6397            .state
6398            .savestack
6399            .offset((e.state.saveptr as i32 + 0 as i32) as isize))
6400            .u
6401            .CINT;
6402        let noad = e.state.curlist.tailfield as i32;
6403        let grp = e.state.src_grp_closing;
6404        if noad < 0 || field != noad + 1 {
6405            return;
6406        }
6407        Self::src_construct_extent(engine, noad, grp);
6408    }
6409
6410    /// THE general construct-extent rule (replaces the per-construct delimiter/accent/
6411    /// nucleus extenders). Map a construct noad's SYNTHESIZED marks (radical surd +
6412    /// vinculum, fraction bar/delimiters, accent glyph, `\left/\right` delimiters) to the
6413    /// construct's full CONSUMED-SOURCE extent `[min(noad command start, group open),
6414    /// consumed end]`:
6415    /// - `group_open` = the opening-token span of the construct's group (`{` of a
6416    ///   `scan_math` field / bare math group, or `\left`), from `src_grp_stack`. `min`
6417    ///   keeps a PREFIX command's earlier start (`\sqrt{x}` -> `\sqrt`) and pulls an
6418    ///   ENCLOSING group's start to the bracket (`\left(..\right)`, `{n\choose k}`). `0`
6419    ///   means no group (END-only: an unbraced `\dot q`).
6420    /// - END = the live post-close buffer loc, or, when the close is replayed from a token
6421    ///   list (a nested construct), the just-closed token's OWN interned span end.
6422    /// Pure extension of `node_src[noad]`; the nucleus FIELD / leaf spans are never
6423    /// touched, so content chars keep their own origin. One rule, no per-construct code.
6424    pub(crate) unsafe fn src_construct_extent(engine: *mut Self, noad: halfword, group_open: SrcId) {
6425        let Some(engine) = engine.as_mut() else {
6426            return;
6427        };
6428        if !engine.state.source_tracking || noad < 0 {
6429            return;
6430        }
6431        let id = engine.state.node_src.get_copy(noad as usize);
6432        if id == 0 {
6433            return;
6434        }
6435        let Some(raw) = engine.state.src_spans.get((id - 1) as usize).copied() else {
6436            return;
6437        };
6438        let (end, name) = if engine.state.curinput.statefield as i32 != 0 {
6439            (
6440                engine.src_buf_offset(engine.state.curinput.locfield as integer),
6441                engine.state.curinput.namefield as strnumber,
6442            )
6443        } else {
6444            let sid = engine.state.curinput.spanfield;
6445            if sid == 0 {
6446                return;
6447            }
6448            let Some(braw) = engine.state.src_spans.get((sid - 1) as usize).copied() else {
6449                return;
6450            };
6451            (braw.end, braw.name)
6452        };
6453        if raw.name != name {
6454            return;
6455        }
6456        let end = end.max(raw.end);
6457        let mut start = raw.start;
6458        if group_open != 0 {
6459            if let Some(g) = engine.state.src_spans.get((group_open - 1) as usize).copied() {
6460                if g.name == name && g.start < start {
6461                    start = g.start;
6462                }
6463            }
6464        }
6465        if start == raw.start && end == raw.end {
6466            return;
6467        }
6468        let newid = engine.intern_span_raw(name, start, end, raw.role);
6469        engine.state.node_src.set(noad as usize, newid);
6470    }
6471
6472    /// HOOK (`math_left_right`, after `scan_delimiter`): drive the GENERAL extent for a
6473    /// `\left..\right` group, which is NOT a `scan_math` `{}` field so does not pass
6474    /// through `src_scan_math_group_*`. `\left` (t==30) pushes its command span as the
6475    /// group open; `\right` (t==31) pops it and applies `src_construct_extent` to the
6476    /// right delimiter noad `p` (from which `make_left_right` builds BOTH delimiter
6477    /// glyphs), giving them the whole `[\left, )]` extent through the same one rule.
6478    pub(crate) unsafe fn src_leftright(engine: *mut Self, p: halfword, t: integer) {
6479        let Some(eng) = engine.as_mut() else {
6480            return;
6481        };
6482        if !eng.state.source_tracking {
6483            return;
6484        }
6485        if t == 30 as i32 {
6486            let cmd = eng.state.cmd_span;
6487            eng.state.src_grp_stack.push(cmd);
6488        } else if t == 31 as i32 {
6489            let open = eng.state.src_grp_stack.pop().unwrap_or(0);
6490            Self::src_construct_extent(engine, p, open);
6491        }
6492    }
6493
6494    /// Resolve a node's enclosing-construct chain (innermost first) to display
6495    /// spans, gated to the node's own source and to frames that strictly enclose
6496    /// the node's primary range (a real enclosing construct contains the node),
6497    /// with consecutive duplicates and the primary-equal innermost frame dropped.
6498    /// `&self`: safe on the read-only IR snapshot path. Empty when tracking off.
6499    /// Consumed by the IR builder to emit role-tagged EnclosingConstruct entries.
6500    pub fn node_enclosing_spans(&self, handle: PortableNodeHandle) -> Vec<PortableSourceSpan> {
6501        let node = handle.0 as halfword;
6502        let mut out: Vec<PortableSourceSpan> = Vec::new();
6503        if !self.state.source_tracking || node < 0 {
6504            return out;
6505        }
6506        let primary = self.resolve_node_src(node);
6507        let mut head = self.state.node_stack.get_copy(node as usize);
6508        let mut guard = 0u32;
6509        while head != 0 {
6510            guard += 1;
6511            if guard > 4096 {
6512                break;
6513            }
6514            let Some(cell) = self.state.src_stack_cells.get((head - 1) as usize) else {
6515                break;
6516            };
6517            let parent = cell.parent;
6518            let span_id = cell.span;
6519            head = parent;
6520            if span_id == 0 {
6521                continue;
6522            }
6523            let Some(raw) = self.state.src_spans.get((span_id - 1) as usize) else {
6524                continue;
6525            };
6526            let Some(name) = (unsafe { self.pool_string(raw.name) }) else {
6527                continue;
6528            };
6529            if let Some(p) = primary.as_ref() {
6530                // Same-source + containment: an enclosing construct's range must
6531                // contain the node's primary range; drop the frame equal to it.
6532                if name != p.name || raw.start > p.start || raw.end < p.end {
6533                    continue;
6534                }
6535                if raw.start == p.start && raw.end == p.end {
6536                    continue;
6537                }
6538            }
6539            if let Some(last) = out.last() {
6540                if last.name == name && last.start == raw.start && last.end == raw.end {
6541                    continue;
6542                }
6543            }
6544            out.push(PortableSourceSpan {
6545                name,
6546                start: raw.start,
6547                end: raw.end,
6548                role: 1,
6549            });
6550        }
6551        out
6552    }
6553
6554    /// HOOK 8 (`begin_token_list`, after the input-stack push): set the new
6555    /// level's BASELINE. A macro body (`t == macro`) adopts the call-site span
6556    /// captured at `macro_call`; every other level keeps the parent's span (the
6557    /// generated push already copied it into `curinput`, so that is free).
6558    pub(crate) unsafe fn src_begin_token_list(engine: *mut Self, t: quarterword) {
6559        let Some(engine) = engine.as_mut() else {
6560            return;
6561        };
6562        if !engine.state.source_tracking {
6563            return;
6564        }
6565        // `macro` token type is 6 in this build's (XeTeX) numbering.
6566        if t as i32 == 6 {
6567            let call = engine.state.pending_call_span;
6568            if call != 0 {
6569                engine.state.curinput.spanfield = call;
6570            }
6571            engine.state.pending_call_span = 0;
6572            // HOOK 8a: push this macro invocation as an enclosing-construct frame
6573            // (popped at the matching end_token_list, HOOK 8b). Nodes the body
6574            // allocates record it on `node_stack`.
6575            engine.src_stack_push_span(call);
6576        }
6577    }
6578
6579    /// HOOK 12a (`\let`/`\futurelet` 2-token look-ahead, `prefixed_command`
6580    /// case `let` with `n != normal`, right after `q := cur_tok`): capture
6581    /// token A's (the first peeked token, held in `q`) OWN origin, frozen in
6582    /// `src_tok_span` by the `get_token` that just read it.
6583    ///
6584    /// tex.web: `get_token; q:=cur_tok; get_token; back_input; cur_tok:=q;
6585    /// back_input;`. The SECOND `get_token` (reading token B) overwrites
6586    /// `src_tok_span` with B's own origin; `q := cur_tok`/`cur_tok := q` are
6587    /// plain register copies that never re-freeze it. So without this capture
6588    /// (paired with [`Self::src_restore_tok_span`] right before the SECOND
6589    /// `back_input`), that `back_input` -- which re-emits token A -- fires
6590    /// with `src_tok_span` still pointing at B, stamping A's backed-up cell
6591    /// with B's origin instead of its own. `\@ifnextchar` (built on
6592    /// `\futurelet`) hits this in `\@tabularcr`'s `array`/`tabular`
6593    /// row-boundary check and in `\sqrt`'s degree scan, where token A is the
6594    /// token right after the command (e.g. the `{` of a degree-less
6595    /// `\sqrt{..}`) and B is unrelated look-ahead content.
6596    pub(crate) unsafe fn src_capture_tok_span(engine: *mut Self) -> u32 {
6597        engine.as_ref().map_or(0, |engine| engine.state.src_tok_span)
6598    }
6599
6600    /// HOOK 12b: the restore half of [`Self::src_capture_tok_span`].
6601    pub(crate) unsafe fn src_restore_tok_span(engine: *mut Self, saved: u32) {
6602        if let Some(engine) = engine.as_mut() {
6603            if engine.state.source_tracking {
6604                engine.state.src_tok_span = saved;
6605            }
6606        }
6607    }
6608
6609    /// HOOK 9a (`macro_call` entry): stash the invoking control sequence's
6610    /// origin so [`Self::src_macro_set_pending`] can build the whole-invocation
6611    /// span after the arguments are scanned.
6612    pub(crate) unsafe fn src_macro_begin(engine: *mut Self) {
6613        let Some(engine) = engine.as_mut() else {
6614            return;
6615        };
6616        if !engine.state.source_tracking {
6617            return;
6618        }
6619        let span = engine.state.curinput.spanfield;
6620        engine.state.src_call_span = span;
6621        engine.state.src_call_state = engine.state.curinput.statefield as integer;
6622        engine.state.src_call_index = engine.state.curinput.indexfield as integer;
6623        engine.state.src_call_name = engine.state.curinput.namefield as strnumber;
6624        engine.state.src_call_argspan = 0;
6625        // Capture the enclosing user command NOW (before this macro reads its
6626        // arguments, so a construct lexed while scanning the args does not become
6627        // the anchor). The buffer-branch baseline anchors its START here.
6628        engine.state.src_call_user_span = engine.state.src_user_cmd_span;
6629        engine.state.src_call_start = if span != 0 {
6630            engine
6631                .state
6632                .src_spans
6633                .get((span - 1) as usize)
6634                .map(|s| s.start)
6635                .unwrap_or(0)
6636        } else {
6637            0
6638        };
6639        // A macro whose cs was REPLAYED at PARAMETER level (`state == 0 && index < 3`,
6640        // span in the user fragment) is genuinely user-typed ARGUMENT content — e.g. a
6641        // nested `\sqrt{..}` replayed inside a degree-form radicand. Record it as the
6642        // anchor command (consumed ONLY by `src_construct_anchor`, never the arg-hull).
6643        // The gate is `< 3` (parameter/template levels) rather than `< 5`
6644        // (backed_up/inserted too): this hook only needs to see the macro CALL itself
6645        // (e.g. the inner `\sqrt`), which is read at parameter level; widening it to
6646        // also match backed_up/inserted reads (e.g. a `\futurelet`-peeked helper token)
6647        // would let unrelated look-ahead plumbing overwrite a real anchor. Macro BODIES
6648        // (index 6) are excluded for the usual definition-vs-invocation-site reason.
6649        if engine.state.src_call_state == 0 && engine.state.src_call_index < 3 && span != 0 {
6650            if let Some(raw) = engine.state.src_spans.get((span - 1) as usize).copied() {
6651                if raw.name == engine.state.src_primary_name {
6652                    engine.state.src_anchor_cmd = span;
6653                }
6654            }
6655        }
6656    }
6657
6658    /// Convex hull (same source) of the invoking cs token span (`src_call_span`),
6659    /// the scanned argument token spans (`pstack[0..n]`, each a token list walked to
6660    /// its end), and the last consumed argument span (`src_call_argspan`, the final
6661    /// `}`). Returns the interned role-1 hull, or `0` if nothing was found. This is
6662    /// the `\frac{q}{2}` -> "\frac{q}{2}" recovery for a token-list-replayed macro,
6663    /// gated to the cs token's OWN source (not the replay level's `namefield`).
6664    unsafe fn src_arg_hull(self: &mut Self, n: integer) -> SrcId {
6665        let mut lo = u32::MAX;
6666        let mut hi = 0u32;
6667        let mut found = false;
6668        let mut name: strnumber = self.state.src_primary_name;
6669        if self.state.src_call_span != 0 {
6670            if let Some(raw) = self
6671                .state
6672                .src_spans
6673                .get((self.state.src_call_span - 1) as usize)
6674            {
6675                name = raw.name;
6676                lo = raw.start;
6677                hi = raw.end;
6678                found = true;
6679            }
6680        }
6681        // Forward-only gate: when seeded from a known command span, only union args
6682        // that fall AT OR AFTER the command start (the macro's own `cmd{..}` region).
6683        // This keeps `\def\foo{\frac{1}{2}}\foo` mapping its bar to "\foo": the inner
6684        // `\frac`'s args sit at the `\def` site, BEFORE the `\foo` invocation the bar
6685        // inherited, so they are excluded rather than merged into a span straddling
6686        // the definition and the call. When there is no command span (a library
6687        // helper), the gate is open (`0`) and the first arg seeds the hull.
6688        let cmd_start = if found { lo } else { 0 };
6689        let zmem = self.state.zmem;
6690        let lo_b = self.state.memmin;
6691        let hi_b = self.state.memmax;
6692        for i in 0..n.max(0) {
6693            let mut p = self.state.pstack[i as usize];
6694            let mut guard = 0i32;
6695            while p >= lo_b && p <= hi_b && p as i64 != -(268435455 as i64) {
6696                guard += 1;
6697                if guard > 100000 {
6698                    break;
6699                }
6700                let id = self.state.node_src.get_copy(p as usize);
6701                if id != 0 {
6702                    if let Some(raw) = self.state.src_spans.get((id - 1) as usize) {
6703                        if raw.name == name && raw.start >= cmd_start {
6704                            if found {
6705                                lo = lo.min(raw.start);
6706                                hi = hi.max(raw.end);
6707                            } else {
6708                                lo = raw.start;
6709                                hi = raw.end;
6710                                found = true;
6711                            }
6712                        }
6713                    }
6714                }
6715                p = (*zmem.offset(p as isize)).hh.v.RH;
6716            }
6717        }
6718        // Extend to the last consumed argument token (the closing `}` of the final
6719        // brace group) so the construct includes its trailing delimiter. Two tracked
6720        // sources: `src_call_argspan` (captured at the first `end_token_list` pop)
6721        // and the live `curinput.spanfield` (the last token read while matching the
6722        // args). The latter recovers the trailing `}` for a `\mathchoice`-replayed
6723        // robust `\frac␣` where the pop-order leaves `src_call_argspan` stale.
6724        for cand in [self.state.src_call_argspan, self.state.curinput.spanfield] {
6725            if cand == 0 {
6726                continue;
6727            }
6728            if let Some(raw) = self.state.src_spans.get((cand - 1) as usize) {
6729                if raw.name == name && raw.start >= cmd_start {
6730                    if found {
6731                        lo = lo.min(raw.start);
6732                        hi = hi.max(raw.end);
6733                    } else {
6734                        lo = raw.start;
6735                        hi = raw.end;
6736                        found = true;
6737                    }
6738                }
6739            }
6740        }
6741        if found && hi > lo {
6742            self.intern_span_raw(name, lo, hi, 1)
6743        } else {
6744            0
6745        }
6746    }
6747
6748    /// HOOK 9b (`macro_call`, right before the body `begin_token_list`): publish
6749    /// the call-site span the body level will inherit, LEVEL-TYPED by where the
6750    /// invoking control sequence was read from (captured at `src_macro_begin`,
6751    /// before the exhausted-list pop loop perturbs `curinput`):
6752    ///
6753    /// * Genuine macro ARGUMENT replay (entry token type `parameter` = 0, e.g. a
6754    ///   `\frac{q}{2}` typed inside another macro's `{...}` or replayed by
6755    ///   `\mathchoice`): the buffer `loc` has popped past this invocation, so its
6756    ///   `[cs,loc)` would overshoot into the enclosing macro. Recover the extent as
6757    ///   the same-source convex hull of the cs token and the scanned argument token
6758    ///   spans (`pstack[0..n]`) -> the user's own `\frac{q}{2}`, not `\frac`.
6759    /// * Otherwise CURRENT buffer (`statefield != 0`, the common direct call and the
6760    ///   backed-up math-probe whose args were scanned from the buffer): the whole
6761    ///   `[cs_start, loc)` invocation (loc past the args) -> `\frac{a}{b}`.
6762    /// * Otherwise a still-live token list (a macro body / every-list): the
6763    ///   inherited baseline, so body-internal calls collapse to the outer
6764    ///   invocation (`\def\foo{\frac..}\foo` -> `\foo`).
6765    pub(crate) unsafe fn src_macro_set_pending(engine: *mut Self, n: integer) {
6766        let Some(engine) = engine.as_mut() else {
6767            return;
6768        };
6769        if !engine.state.source_tracking {
6770            return;
6771        }
6772        if engine.state.src_call_index == 0 && engine.state.src_call_span == 0 {
6773            // A LIBRARY HELPER macro -- one with no in-fragment cs span, e.g. `\@sqrt`
6774            // invoked by `\sqrt`, or `\root`/`\mathchoice` machinery -- is NOT a user
6775            // construct. Its in-fragment ARGUMENTS (`[3]{x}`) are the OUTER command's
6776            // args, not a new construct boundary. So it must NOT establish a new
6777            // baseline from those args (which is why the surd was landing on `[3]{x}`);
6778            // inherit the parent baseline (the transitively-propagated user command,
6779            // e.g. `\sqrt`) by leaving the level to inherit on push. The arg CONTENT
6780            // (3, x) still keeps its own cell spans. Only a USER macro (in-fragment cs,
6781            // handled below) defines its own `[cs..args]` hull.
6782            engine.state.pending_call_span = 0;
6783        } else if engine.state.src_call_index == 0 {
6784            // ARGUMENT / backed-up replay (`\frac{q}{2}` typed inside another macro's
6785            // `{..}`): recover its own `[cs..args]` hull from the scanned args.
6786            let hull = engine.src_arg_hull(n);
6787            engine.state.pending_call_span = if hull != 0 {
6788                hull
6789            } else {
6790                engine.state.src_call_span
6791            };
6792        } else if engine.state.curinput.statefield as i32 != 0 {
6793            // BUFFER invocation: the whole `[cmd_start, loc)` extent (loc is past the
6794            // args). Anchor the START to the ENCLOSING USER COMMAND (captured at
6795            // `src_macro_begin`), not this macro's own `src_call_span`: a kernel
6796            // helper reached through a user command's expansion (e.g. `\@sqrt`,
6797            // `\root`, `\mathpalette` for `\sqrt[3]{x}`) is invoked from the buffer
6798            // with a borrowed cs span (the `\@ifnextchar` peeked `[`), which would
6799            // drop the `\sqrt` origin. The user command's start transitively anchors
6800            // every helper to the command the user typed. For a directly-typed user
6801            // macro the user command IS this macro, so the start is unchanged.
6802            let end = engine.src_buf_offset(engine.state.curinput.locfield as integer);
6803            let name = engine.state.curinput.namefield as strnumber;
6804            let mut start = engine.state.src_call_start.min(end);
6805            let user = engine.state.src_call_user_span;
6806            if user != 0 {
6807                if let Some(raw) = engine.state.src_spans.get((user - 1) as usize) {
6808                    if raw.name == name && raw.start <= start {
6809                        start = raw.start;
6810                    }
6811                }
6812            }
6813            let id = engine.intern_span_raw(name, start, end, 1);
6814            engine.state.pending_call_span = id;
6815        } else if n > 0 {
6816            // TOKEN-LIST macro BODY (`index >= 5`) that consumed args. The inherited
6817            // baseline is the user invocation that produced this body (`\frac` ->
6818            // `\protect\frac␣`: `\frac␣`'s baseline is the user's `\frac`). UNION it
6819            // with the macro's OWN trailing args so a `\frac{q}{2}` the user typed as
6820            // a `\mathchoice`-replayed radicand recovers "\frac{q}{2}". `src_arg_hull`
6821            // only unions args that fall AFTER the command start (the macro's own
6822            // `cmd{..}` region), so a `\def\foo{\frac{1}{2}}\foo` body -- whose inner
6823            // `\frac` args precede the `\foo` invocation -- excludes them and keeps
6824            // the bar -> "\foo". The join is the command and its OWN tracked args
6825            // (the macro-call chain), never an unrelated adjacent range.
6826            let hull = engine.src_arg_hull(n);
6827            engine.state.pending_call_span = if hull != 0 {
6828                hull
6829            } else {
6830                engine.state.src_call_span
6831            };
6832        } else {
6833            engine.state.pending_call_span = engine.state.src_call_span;
6834        }
6835    }
6836
6837    /// HOOK 11 (`main_control` main loop, the `is_hyph` seam): record the source
6838    /// span of the input char just appended to `nativetext`, one entry per UTF-16
6839    /// code unit (a surrogate-pair char fills both units with the same id). This
6840    /// runs once per collected char while `curinput.spanfield` still points at it.
6841    /// The run begins when `nativelen` was 0 before this char (`prev == 0`), so the
6842    /// table self-resets at the head of each run without a second anchor; a later
6843    /// re-measure of an unrelated run is caught by the length guard in
6844    /// [`Self::src_resolve_native_glyphs`]. No-op when tracking off.
6845    pub(crate) unsafe fn src_native_run_push(engine: *mut Self) {
6846        let Some(engine) = engine.as_mut() else {
6847            return;
6848        };
6849        if !engine.state.source_tracking {
6850            return;
6851        }
6852        let nativelen = engine.state.nativelen.max(0) as usize;
6853        // The engine appends 2 UTF-16 units for a supplementary scalar, else 1
6854        // (mirrors the `curchr > 65535` branch just above this seam).
6855        let units = if engine.state.curchr as i64 > 65535 { 2 } else { 1 };
6856        let prev = nativelen.saturating_sub(units);
6857        if prev == 0 {
6858            engine.state.src_native_offsets.clear();
6859        }
6860        // Trim any stale overshoot (defensive; cleared at `prev == 0`), then fill
6861        // this char's units with its tracked span id.
6862        if engine.state.src_native_offsets.len() > prev {
6863            engine.state.src_native_offsets.truncate(prev);
6864        }
6865        let id = engine.state.curinput.spanfield;
6866        while engine.state.src_native_offsets.len() < nativelen {
6867            engine.state.src_native_offsets.push(id);
6868        }
6869    }
6870
6871    /// Map each shaped glyph of a native run to the EXACT source span of the input
6872    /// char(s) under its shaper cluster, setting `src_start`/`src_end` (in the
6873    /// node's `primary_source.source` coordinates). The shaper reports each glyph's
6874    /// `cluster_start` as a UTF-8 BYTE offset into `String::from_utf16_lossy(text)`
6875    /// (the engine hands the shaper UTF-16 `nativetext`, the adapter converts to a
6876    /// Rust `&str`, and rustybuzz clusters are UTF-8 byte indices). This rebuilds
6877    /// that string, maps each UTF-8 byte to its char's tracked span via a UTF-16
6878    /// code-unit cursor aligned with `src_native_offsets`, then for each glyph
6879    /// unions the (same-source) spans across its cluster's byte extent — the extent
6880    /// being `[cluster_start, next distinct cluster_start)`, so a ligature glyph
6881    /// covers the contiguous union of its source chars. CONSUME-ONCE: the offsets
6882    /// table is taken here, so a re-measure (reconstituted/hyphenated run, or an
6883    /// unrelated node) finds it empty and leaves clusters unmapped rather than
6884    /// mis-mapping. Zero heuristics: no text==source linear assumption — every span
6885    /// comes from a per-char tracked id. No-op when tracking off.
6886    pub(crate) unsafe fn src_resolve_native_glyphs(
6887        engine: *mut Self,
6888        node: halfword,
6889        text: &[u16],
6890        glyphs: &mut [PortableNativeGlyph],
6891    ) {
6892        let Some(engine) = engine.as_mut() else {
6893            return;
6894        };
6895        if !engine.state.source_tracking {
6896            return;
6897        }
6898        let offsets = core::mem::take(&mut engine.state.src_native_offsets);
6899        // Only resolve our freshly-collected run: the per-code-unit table must
6900        // exactly cover this node's text. Any mismatch => leave glyphs unmapped.
6901        if text.is_empty() || offsets.len() != text.len() {
6902            return;
6903        }
6904        if node < 0 {
6905            return;
6906        }
6907        // The cluster span lives in the node's source coordinates, so only chars
6908        // from the node's own source file may contribute (no cross-source span).
6909        let node_id = engine.state.node_src.get_copy(node as usize);
6910        if node_id == 0 {
6911            return;
6912        }
6913        let node_name = match engine.state.src_spans.get((node_id - 1) as usize) {
6914            Some(raw) => raw.name,
6915            None => return,
6916        };
6917        // Rebuild the exact UTF-8 string the shaper saw and tag each byte with the
6918        // source id of the char it belongs to (UTF-16 cursor aligns to `offsets`).
6919        // The shaper reports `cluster_start` as a UTF-8 BYTE offset into this
6920        // string (verified empirically: a supplementary-plane run char lands its
6921        // following glyph at the UTF-8 byte offset, not the UTF-16 code-unit one).
6922        let s = String::from_utf16_lossy(text);
6923        let n = s.len();
6924        let mut byte_src = vec![0u32; n];
6925        let mut u16i = 0usize;
6926        for (b, ch) in s.char_indices() {
6927            let id = offsets.get(u16i).copied().unwrap_or(0);
6928            let upper = (b + ch.len_utf8()).min(n);
6929            for slot in byte_src.iter_mut().take(upper).skip(b) {
6930                *slot = id;
6931            }
6932            u16i += ch.len_utf16();
6933        }
6934        for gi in 0..glyphs.len() {
6935            let cs = (glyphs[gi].cluster_start as usize).min(n);
6936            // Monotone (LTR) clusters: this cluster ends at the next strictly
6937            // greater `cluster_start`, else at end-of-text. Glyphs sharing `cs`
6938            // (a decomposed char) resolve to the same char span.
6939            let mut ce = n;
6940            for g2 in glyphs.iter() {
6941                let c2 = (g2.cluster_start as usize).min(n);
6942                if c2 > cs && c2 < ce {
6943                    ce = c2;
6944                }
6945            }
6946            let mut lo = u32::MAX;
6947            let mut hi = 0u32;
6948            let mut found = false;
6949            for &id in byte_src.iter().take(ce).skip(cs) {
6950                if id == 0 {
6951                    continue;
6952                }
6953                let Some(raw) = engine.state.src_spans.get((id - 1) as usize) else {
6954                    continue;
6955                };
6956                if raw.name != node_name {
6957                    continue;
6958                }
6959                lo = lo.min(raw.start);
6960                hi = hi.max(raw.end);
6961                found = true;
6962            }
6963            if found && hi > lo {
6964                glyphs[gi].src_start = lo;
6965                glyphs[gi].src_end = hi;
6966            }
6967        }
6968    }
6969
6970    /// All interned spans, resolved to display form. Restored from the stubbed
6971    /// `&[]`: exposes the populated table for inspection / tests.
6972    pub fn input_source_spans(&self) -> Vec<PortableSourceSpan> {
6973        self.state
6974            .src_spans
6975            .iter()
6976            .filter_map(|raw| {
6977                let name = unsafe { self.pool_string(raw.name) }?;
6978                Some(PortableSourceSpan {
6979                    name,
6980                    start: raw.start,
6981                    end: raw.end,
6982                    role: raw.role,
6983                })
6984            })
6985            .collect()
6986    }
6987
6988    pub(crate) unsafe fn resolve_font_handle(
6989        engine: *mut PortableTexEngine<'_>,
6990        name: *mut ASCIIcode,
6991        size: integer,
6992    ) -> FontHandle {
6993        let Some(engine) = engine.as_mut() else {
6994            return 0;
6995        };
6996        let name_len = engine.state.namelength.max(0) as usize;
6997        let name = if name.is_null() || name_len == 0 {
6998            &[]
6999        } else {
7000            core::slice::from_raw_parts(name as *const i32, name_len)
7001        };
7002        engine.fonts.resolve_font_handle(name, size).unwrap_or(0)
7003    }
7004
7005    pub(crate) unsafe fn measure_font_metrics(
7006        engine: *mut PortableTexEngine<'_>,
7007        font: FontHandle,
7008        ascent: *mut integer,
7009        descent: *mut integer,
7010        xheight: *mut integer,
7011        capheight: *mut integer,
7012        slant: *mut integer,
7013    ) {
7014        let metrics = engine
7015            .as_mut()
7016            .map(|engine| engine.fonts.font_metrics(font))
7017            .unwrap_or_default();
7018        if !ascent.is_null() {
7019            *ascent = metrics.ascent;
7020        }
7021        if !descent.is_null() {
7022            *descent = metrics.descent;
7023        }
7024        if !xheight.is_null() {
7025            *xheight = metrics.xheight;
7026        }
7027        if !capheight.is_null() {
7028            *capheight = metrics.capheight;
7029        }
7030        if !slant.is_null() {
7031            *slant = metrics.slant;
7032        }
7033    }
7034
7035    pub(crate) unsafe fn get_native_mathsy_parameter(
7036        engine: *mut PortableTexEngine<'resources>,
7037        font: integer,
7038        param: integer,
7039    ) -> integer {
7040        let Some(engine) = engine.as_mut() else {
7041            return 0;
7042        };
7043        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7044            return 0;
7045        };
7046        engine.fonts.math_symbol_parameter(font_handle, param)
7047    }
7048
7049    pub(crate) unsafe fn get_native_mathex_parameter(
7050        engine: *mut PortableTexEngine<'resources>,
7051        font: integer,
7052        param: integer,
7053    ) -> integer {
7054        let Some(engine) = engine.as_mut() else {
7055            return 0;
7056        };
7057        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7058            return 0;
7059        };
7060        engine.fonts.math_extension_parameter(font_handle, param)
7061    }
7062
7063    /// Italic correction for a native OpenType-math glyph, in scaled points.
7064    ///
7065    /// Mirrors XeTeX's `get_ot_math_ital_corr` (`XeTeXOTMath.cpp`): it reads the
7066    /// glyph's `MathItalicsCorrectionInfo` value and scales it through the same
7067    /// `unitsToPoints` + `D2Fix` path as every other font metric. The math list
7068    /// builder (`mlist_to_hlist`) appends a `\kern` of this size after an ord
7069    /// glyph when there is no following subscript, so a correct nonzero value
7070    /// produces the exact italic-correction kern real XeTeX emits.
7071    pub(crate) unsafe fn get_ot_math_ital_corr(
7072        engine: *mut PortableTexEngine<'resources>,
7073        font: integer,
7074        glyph: integer,
7075    ) -> integer {
7076        let Some(engine) = engine.as_mut() else {
7077            return 0;
7078        };
7079        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7080            return 0;
7081        };
7082        engine.fonts.math_glyph_italic_correction(font_handle, glyph)
7083    }
7084
7085    /// The `v`-th larger MATH glyph variant of `g` (horizontal or vertical),
7086    /// writing its scaled advance to `*adv`. Mirrors XeTeX's
7087    /// `get_ot_math_variant`: returns the glyph unchanged with `*adv = -1` when
7088    /// there is no such variant.
7089    pub(crate) unsafe fn get_ot_math_variant(
7090        engine: *mut PortableTexEngine<'resources>,
7091        f_0: integer,
7092        g_0: integer,
7093        v: integer,
7094        adv: *mut integer,
7095        horiz: integer,
7096    ) -> integer {
7097        if !adv.is_null() {
7098            *adv = -1;
7099        }
7100        let Some(engine) = engine.as_mut() else {
7101            return g_0;
7102        };
7103        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7104            return g_0;
7105        };
7106        let index = u16::try_from(v).unwrap_or(u16::MAX);
7107        match engine
7108            .fonts
7109            .math_glyph_variant(font_handle, g_0, index, horiz != 0)
7110        {
7111            Some(variant) => {
7112                if !adv.is_null() {
7113                    *adv = variant.advance;
7114                }
7115                variant.glyph
7116            }
7117            None => g_0,
7118        }
7119    }
7120
7121    /// Build a heap-owned [`GlyphAssembly`] for the stretchable glyph `g` and
7122    /// hand ownership to the engine as a `void*` (reclaimed by
7123    /// [`free_ot_assembly`]). Returns null when there is no assembly. Mirrors
7124    /// XeTeX's `get_ot_assembly_ptr`, but allocates a safe Rust struct with
7125    /// `Box::into_raw` instead of a libc C struct.
7126    pub(crate) unsafe fn get_ot_assembly_ptr(
7127        engine: *mut PortableTexEngine<'resources>,
7128        f_0: integer,
7129        g_0: integer,
7130        horiz: integer,
7131    ) -> voidpointer {
7132        let Some(engine) = engine.as_mut() else {
7133            return nullptr;
7134        };
7135        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7136            return nullptr;
7137        };
7138        let parts = engine
7139            .fonts
7140            .math_glyph_assembly(font_handle, g_0, horiz != 0);
7141        if parts.is_empty() {
7142            return nullptr;
7143        }
7144        let assembly = Box::new(GlyphAssembly { parts });
7145        Box::into_raw(assembly) as voidpointer
7146    }
7147
7148    /// Minimum connector overlap between assembly parts for font `f`, in scaled
7149    /// points (`ot_min_connector_overlap`).
7150    pub(crate) unsafe fn ot_min_connector_overlap(
7151        engine: *mut PortableTexEngine<'resources>,
7152        f_0: integer,
7153    ) -> integer {
7154        let Some(engine) = engine.as_mut() else {
7155            return 0;
7156        };
7157        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7158            return 0;
7159        };
7160        engine.fonts.math_min_connector_overlap(font_handle)
7161    }
7162
7163    /// MATH glyph height (scaled points) via the font platform.
7164    unsafe fn math_glyph_height(
7165        engine: &mut PortableTexEngine<'resources>,
7166        f_0: integer,
7167        g_0: integer,
7168    ) -> scaled {
7169        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7170            return 0;
7171        };
7172        let Ok(glyph) = u16::try_from(g_0) else {
7173            return 0;
7174        };
7175        engine.fonts.measure_native_glyph(font_handle, glyph, true).height
7176    }
7177
7178    /// MATH glyph depth (scaled points) via the font platform.
7179    unsafe fn math_glyph_depth(
7180        engine: &mut PortableTexEngine<'resources>,
7181        f_0: integer,
7182        g_0: integer,
7183    ) -> scaled {
7184        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7185            return 0;
7186        };
7187        let Ok(glyph) = u16::try_from(g_0) else {
7188            return 0;
7189        };
7190        engine.fonts.measure_native_glyph(font_handle, glyph, true).depth
7191    }
7192
7193    /// Evaluate one MATH kern corner of glyph `g` at `correction_height` (font
7194    /// design units), in raw font units, via the font platform.
7195    unsafe fn math_kern_at(
7196        engine: &mut PortableTexEngine<'resources>,
7197        f_0: integer,
7198        g_0: integer,
7199        height: integer,
7200        corner: PortableMathKernCorner,
7201    ) -> integer {
7202        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7203            return 0;
7204        };
7205        engine.fonts.math_kern_at(font_handle, g_0, corner, height)
7206    }
7207
7208    /// Superscript/subscript cut-in kerning between base glyph `g` in font `f`
7209    /// and script glyph `sg` in font `sf`. Faithful port of XeTeX's
7210    /// `get_ot_math_kern` (`XeTeXOTMath.cpp`): all intermediate arithmetic runs
7211    /// in base-glyph units with a `scale_factor = sf_size / f_size`, the "max not
7212    /// min" corner choice is preserved, and the result is scaled to scaled points
7213    /// through the same `unitsToPoints` + `D2Fix` path.
7214    pub(crate) unsafe fn get_ot_math_kern(
7215        engine: *mut PortableTexEngine<'resources>,
7216        f_0: integer,
7217        g_0: integer,
7218        sf: integer,
7219        sg: integer,
7220        cmd: integer,
7221        shift_scaled: integer,
7222    ) -> integer {
7223        const SUP_CMD: integer = 0;
7224        const SUB_CMD: integer = 1;
7225        let Some(engine) = engine.as_mut() else {
7226            return 0;
7227        };
7228        let Some(font_handle) = Self::font_handle_for_number(engine, f_0) else {
7229            return 0;
7230        };
7231        let Some(sfont_handle) = Self::font_handle_for_number(engine, sf) else {
7232            return 0;
7233        };
7234
7235        // Glyph height/depth in points (sp -> pt) for the base and script glyphs.
7236        let g_height_pt = Self::math_glyph_height(engine, f_0, g_0) as f32 / 65536.0;
7237        let g_depth_pt = Self::math_glyph_depth(engine, f_0, g_0) as f32 / 65536.0;
7238        let sg_height_pt = Self::math_glyph_height(engine, sf, sg) as f32 / 65536.0;
7239        let sg_depth_pt = Self::math_glyph_depth(engine, sf, sg) as f32 / 65536.0;
7240
7241        // Convert everything to base-glyph units.
7242        let g_height = engine.fonts.math_points_to_units(font_handle, g_height_pt) as integer;
7243        let g_depth = engine.fonts.math_points_to_units(font_handle, g_depth_pt) as integer;
7244        let sg_height = engine
7245            .fonts
7246            .math_points_to_units(sfont_handle, sg_height_pt) as integer;
7247        let sg_depth = engine
7248            .fonts
7249            .math_points_to_units(sfont_handle, sg_depth_pt) as integer;
7250        let shift_pt = shift_scaled as f32 / 65536.0;
7251        let shift = engine.fonts.math_points_to_units(font_handle, shift_pt) as integer;
7252
7253        let f_size = engine.fonts.math_point_size(font_handle);
7254        let sf_size = engine.fonts.math_point_size(sfont_handle);
7255        if f_size == 0.0 {
7256            return 0;
7257        }
7258        let scale_factor = sf_size / f_size;
7259
7260        let mut rval: integer;
7261        if cmd == SUP_CMD {
7262            let kern = Self::math_kern_at(
7263                engine,
7264                f_0,
7265                g_0,
7266                shift - (scale_factor * sg_depth as f32) as integer,
7267                PortableMathKernCorner::TopRight,
7268            );
7269            let skern =
7270                Self::math_kern_at(engine, sf, sg, -sg_depth, PortableMathKernCorner::BottomLeft);
7271            let top_kern = kern + (scale_factor * skern as f32) as integer;
7272
7273            let kern =
7274                Self::math_kern_at(engine, f_0, g_0, g_height, PortableMathKernCorner::TopRight);
7275            let skern = Self::math_kern_at(
7276                engine,
7277                sf,
7278                sg,
7279                ((g_height - shift) as f32 / scale_factor) as integer,
7280                PortableMathKernCorner::BottomLeft,
7281            );
7282            let bot_kern = kern + (scale_factor * skern as f32) as integer;
7283
7284            rval = if top_kern > bot_kern { top_kern } else { bot_kern };
7285        } else if cmd == SUB_CMD {
7286            let kern = Self::math_kern_at(
7287                engine,
7288                f_0,
7289                g_0,
7290                (scale_factor * sg_height as f32) as integer - shift,
7291                PortableMathKernCorner::BottomRight,
7292            );
7293            let skern =
7294                Self::math_kern_at(engine, sf, sg, sg_height, PortableMathKernCorner::TopLeft);
7295            let top_kern = kern + (scale_factor * skern as f32) as integer;
7296
7297            let kern =
7298                Self::math_kern_at(engine, f_0, g_0, -g_depth, PortableMathKernCorner::BottomRight);
7299            let skern = Self::math_kern_at(
7300                engine,
7301                sf,
7302                sg,
7303                ((shift - g_depth) as f32 / scale_factor) as integer,
7304                PortableMathKernCorner::TopLeft,
7305            );
7306            let bot_kern = kern + (scale_factor * skern as f32) as integer;
7307
7308            rval = if top_kern > bot_kern { top_kern } else { bot_kern };
7309        } else {
7310            return 0;
7311        }
7312
7313        rval = engine.fonts.math_units_to_scaled(font_handle, rval);
7314        rval
7315    }
7316
7317    pub(crate) unsafe fn get_native_word_cp(
7318        engine: *mut PortableTexEngine<'resources>,
7319        node: voidpointer,
7320        side: integer,
7321    ) -> integer {
7322        let Some(engine) = engine.as_mut() else {
7323            return 0;
7324        };
7325        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7326            return 0;
7327        };
7328        let Some(info) = engine.native_glyph_infos.get(&node_index) else {
7329            return 0;
7330        };
7331        let glyph = if side == 0 {
7332            info.glyphs.first()
7333        } else {
7334            info.glyphs.last()
7335        };
7336        let Some(glyph) = glyph else {
7337            return 0;
7338        };
7339        let font = Self::native_node_font(engine.state.zmem, node_index);
7340        Self::character_protrusion(engine, font, u32::from(glyph.glyph_id), side)
7341    }
7342
7343    pub(crate) unsafe fn get_native_glyph(
7344        engine: *mut PortableTexEngine<'resources>,
7345        node: voidpointer,
7346        index: u32,
7347    ) -> uint16_t {
7348        let Some(engine) = engine.as_mut() else {
7349            return 0;
7350        };
7351        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7352            return 0;
7353        };
7354        engine
7355            .native_glyph_infos
7356            .get(&node_index)
7357            .and_then(|info| info.glyphs.get(index as usize))
7358            .map_or(0, |glyph| glyph.glyph_id)
7359    }
7360
7361    pub(crate) unsafe fn get_character_protrusion(
7362        engine: *mut PortableTexEngine<'_>,
7363        font: integer,
7364        code: u32,
7365        side: integer,
7366    ) -> integer {
7367        engine
7368            .as_mut()
7369            .map_or(0, |engine| Self::character_protrusion(engine, font, code, side))
7370    }
7371
7372    pub(crate) fn character_protrusion(
7373        engine: &PortableTexEngine<'_>,
7374        font: integer,
7375        code: u32,
7376        side: integer,
7377    ) -> integer {
7378        engine
7379            .character_protrusions
7380            .get(&(font, code, side))
7381            .copied()
7382            .unwrap_or(0)
7383    }
7384
7385    pub(crate) fn set_character_protrusion(
7386        engine: &mut PortableTexEngine<'_>,
7387        font: integer,
7388        code: u32,
7389        side: integer,
7390        value: integer,
7391    ) {
7392        let key = (font, code, side);
7393        if value == 0 {
7394            engine.character_protrusions.remove(&key);
7395        } else {
7396            engine.character_protrusions.insert(key, value);
7397        }
7398    }
7399
7400    pub(crate) unsafe fn get_opentype_math_constant(
7401        engine: *mut PortableTexEngine<'resources>,
7402        font: integer,
7403        constant: integer,
7404    ) -> integer {
7405        let Some(engine) = engine.as_mut() else {
7406            return 0;
7407        };
7408        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7409            return 0;
7410        };
7411        engine.fonts.opentype_math_constant(font_handle, constant)
7412    }
7413
7414    pub(crate) unsafe fn get_opentype_math_accent_position(
7415        engine: *mut PortableTexEngine<'resources>,
7416        font: integer,
7417        glyph: integer,
7418    ) -> integer {
7419        let Some(engine) = engine.as_mut() else {
7420            return 0;
7421        };
7422        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7423            return 0;
7424        };
7425        engine.fonts.opentype_math_accent_position(font_handle, glyph)
7426    }
7427
7428    pub(crate) unsafe fn map_char_to_glyph(
7429        engine: *mut PortableTexEngine<'resources>,
7430        font: integer,
7431        ch: integer,
7432    ) -> integer {
7433        let Some(engine) = engine.as_mut() else {
7434            return 0;
7435        };
7436        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7437            return 0;
7438        };
7439        engine.fonts.map_char_to_glyph(font_handle, ch)
7440    }
7441
7442    pub(crate) unsafe fn map_glyph_to_index(
7443        engine: *mut PortableTexEngine<'resources>,
7444        font: integer,
7445    ) -> integer {
7446        let Some(engine) = engine.as_mut() else {
7447            return 0;
7448        };
7449        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7450            return 0;
7451        };
7452        let Some(name) = engine.pool_string(engine.state.curname) else {
7453            return 0;
7454        };
7455        engine.fonts.map_glyph_to_index(font_handle, name.as_str())
7456    }
7457
7458    /// Boundary for the `\XeTeXOT*` / `\XeTeXcountglyphs` `last_item` primitives.
7459    /// Resolves the font number to a platform handle (mirroring
7460    /// `map_char_to_glyph`) and dispatches to the font platform's OpenType
7461    /// layout enumeration. Replaces the old `otfontget*` no-op stubs.
7462    pub(crate) unsafe fn ot_font_get(
7463        engine: *mut PortableTexEngine<'resources>,
7464        what: integer,
7465        font: integer,
7466        param1: integer,
7467        param2: integer,
7468        param3: integer,
7469    ) -> integer {
7470        let Some(engine) = engine.as_mut() else {
7471            return 0;
7472        };
7473        let Some(font_handle) = Self::font_handle_for_number(engine, font) else {
7474            return 0;
7475        };
7476        engine
7477            .fonts
7478            .ot_font_get(font_handle, what, param1, param2, param3)
7479    }
7480
7481    pub(crate) unsafe fn is_opentype_math_font(
7482        engine: *mut PortableTexEngine<'_>,
7483        font: FontHandle,
7484    ) -> boolean {
7485        engine
7486            .as_mut()
7487            .map(|engine| engine.fonts.is_opentype_math_font(font) as boolean)
7488            .unwrap_or(false_0)
7489    }
7490
7491    pub(crate) unsafe fn using_opentype(
7492        engine: *mut PortableTexEngine<'_>,
7493        font: FontHandle,
7494    ) -> boolean {
7495        engine
7496            .as_mut()
7497            .map(|engine| engine.fonts.using_opentype(font) as boolean)
7498            .unwrap_or(false_0)
7499    }
7500
7501    pub(crate) unsafe fn release_font_engine(
7502        engine: *mut PortableTexEngine<'_>,
7503        font: FontHandle,
7504        type_flag: integer,
7505    ) {
7506        if let Some(engine) = engine.as_mut() {
7507            engine.fonts.release_font_handle(font, type_flag);
7508        }
7509    }
7510
7511    pub(crate) unsafe fn measure_opentype_font_metrics(
7512        engine: *mut PortableTexEngine<'_>,
7513        font: FontHandle,
7514        ascent: *mut integer,
7515        descent: *mut integer,
7516        xheight: *mut integer,
7517        capheight: *mut integer,
7518        slant: *mut integer,
7519    ) {
7520        let metrics = engine
7521            .as_mut()
7522            .map(|engine| engine.fonts.opentype_font_metrics(font))
7523            .unwrap_or_default();
7524        if !ascent.is_null() {
7525            *ascent = metrics.ascent;
7526        }
7527        if !descent.is_null() {
7528            *descent = metrics.descent;
7529        }
7530        if !xheight.is_null() {
7531            *xheight = metrics.xheight;
7532        }
7533        if !capheight.is_null() {
7534            *capheight = metrics.capheight;
7535        }
7536        if !slant.is_null() {
7537            *slant = metrics.slant;
7538        }
7539    }
7540
7541    pub(crate) unsafe fn measure_native_node(
7542        engine: *mut PortableTexEngine<'resources>,
7543        node: voidpointer,
7544        use_glyph_metrics: integer,
7545    ) {
7546        let Some(engine) = engine.as_mut() else {
7547            return;
7548        };
7549        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7550            return;
7551        };
7552        let mem = engine.state.zmem;
7553        let font_number = Self::native_node_font(mem, node_index);
7554        let Some(font_handle) = Self::font_handle_for_number(engine, font_number) else {
7555            return;
7556        };
7557        let text = Self::native_node_text(mem, node_index);
7558        let mut metrics =
7559            engine
7560                .fonts
7561                .shape_native_text(font_handle, text, use_glyph_metrics != 0);
7562        // Source tracking: map each shaped glyph back to the EXACT source span of
7563        // the input char(s) that produced its shaper cluster, via the per-code-unit
7564        // ids collected during the main-loop run (no-op when tracking off).
7565        Self::src_resolve_native_glyphs(
7566            engine as *mut PortableTexEngine<'resources>,
7567            node_index,
7568            text,
7569            metrics.glyphs.as_mut_slice(),
7570        );
7571        Self::write_native_node_metrics(
7572            mem,
7573            node_index,
7574            metrics.width,
7575            metrics.height,
7576            metrics.depth,
7577        );
7578        (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B3 =
7579            (metrics.glyphs.len().min(i32::MAX as usize) as quarterword) as u16;
7580        (*mem.offset((node_index + 5) as isize)).ptr = nullptr;
7581        engine.native_glyph_infos.insert(
7582            node_index,
7583            PortableNativeGlyphInfo {
7584                glyphs: metrics.glyphs,
7585            },
7586        );
7587    }
7588
7589    pub(crate) unsafe fn measure_native_glyph(
7590        engine: *mut PortableTexEngine<'resources>,
7591        node: voidpointer,
7592        use_glyph_metrics: integer,
7593    ) {
7594        let Some(engine) = engine.as_mut() else {
7595            return;
7596        };
7597        let Some(node_index) = Self::node_index_for_pointer(engine, node) else {
7598            return;
7599        };
7600        let mem = engine.state.zmem;
7601        let font_number = Self::native_node_font(mem, node_index);
7602        let Some(font_handle) = Self::font_handle_for_number(engine, font_number) else {
7603            return;
7604        };
7605        let glyph = (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B2 as u16;
7606        let metrics = engine
7607            .fonts
7608            .measure_native_glyph(font_handle, glyph, use_glyph_metrics != 0);
7609        Self::write_native_node_metrics(
7610            mem,
7611            node_index,
7612            metrics.width,
7613            metrics.height,
7614            metrics.depth,
7615        );
7616        (*mem.offset((node_index + 4) as isize)).v.QQQQ.u.B3 = (1 as quarterword) as u16;
7617        // NOTE: a `glyph_node` is allocated with `glyph_node_size = 5` words
7618        // (indices 0..=4), but XeTeX's `native_glyph_info_ptr` macro lives at word
7619        // `node + 5` -- one past this node. In the original C engine that word
7620        // aliases adjacent `mem`, which is tolerated; here `mem` is a bounds-real
7621        // Rust array and writing `node + 5` corrupts the *next* node (it crashed
7622        // `var_delimiter`, which builds a single-glyph delimiter box this way).
7623        // The glyph info this field would point at is held authoritatively in the
7624        // engine-side `native_glyph_infos` map (keyed by node index) and the raw
7625        // `node + 5` word is never dereferenced anywhere, so the write is omitted.
7626        engine.native_glyph_infos.insert(
7627            node_index,
7628            PortableNativeGlyphInfo {
7629                glyphs: Vec::from([PortableNativeGlyph {
7630                    glyph_id: glyph,
7631                    x: 0,
7632                    y: 0,
7633                    advance: metrics.width,
7634                    cluster_start: 0,
7635                    cluster_end: 0,
7636                    src_start: 0,
7637                    src_end: 0,
7638                }]),
7639            },
7640        );
7641    }
7642
7643    pub(crate) unsafe fn znotaatfonterror(
7644        self: &mut Self,
7645        cmd: integer,
7646        c_0: integer,
7647        f_0: integer,
7648    ) -> EngineFlow<()> {
7649        self.znototfonterror(cmd, c_0, f_0)?;
7650        Ok(())
7651    }
7652
7653    pub(crate) unsafe fn znotaatgrfonterror(
7654        self: &mut Self,
7655        cmd: integer,
7656        c_0: integer,
7657        f_0: integer,
7658    ) -> EngineFlow<()> {
7659        self.znototfonterror(cmd, c_0, f_0)?;
7660        Ok(())
7661    }
7662
7663    /// Append a native glyph for `(f_0, g_0)` to the end of box `b`, growing the
7664    /// box height/depth (hlist) or width (vlist). Port of XeTeX's
7665    /// `stack_glyph_into_box` (`xetex.web`). The glyph node is `glyph_node_size`
7666    /// (5) words, measured through `measure_native_glyph`.
7667    unsafe fn stack_glyph_into_box(
7668        self: &mut Self,
7669        b: halfword,
7670        f_0: internalfontnumber,
7671        g_0: integer,
7672    ) -> EngineFlow<()> {
7673        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7674        const NULL: halfword = -(268435455 as i64) as halfword;
7675        let p = (&mut *(self as *mut PortableTexEngine<'_>)).zgetnode(5)?;
7676        (*mem.offset(p as isize)).hh.u.B0 = 8;
7677        (*mem.offset(p as isize)).hh.u.B1 = 42;
7678        (*mem.offset((p + 4) as isize)).v.QQQQ.u.B1 = (f_0 as quarterword) as u16;
7679        (*mem.offset((p + 4) as isize)).v.QQQQ.u.B2 = (g_0 as quarterword) as u16;
7680        Self::measure_native_glyph(
7681            self as *mut PortableTexEngine<'resources>,
7682            mem.offset(p as isize) as *mut memoryword as *mut (),
7683            1,
7684        );
7685        Ok(
7686            if (*mem.offset(b as isize)).hh.u.B0 as i32 == 0 {
7687                let mut q = (*mem.offset((b + 5) as isize)).hh.v.RH;
7688                if q == NULL {
7689                    (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7690                } else {
7691                    while (*mem.offset(q as isize)).hh.v.RH != NULL {
7692                        q = (*mem.offset(q as isize)).hh.v.RH;
7693                    }
7694                    (*mem.offset(q as isize)).hh.v.RH = p;
7695                    if (*mem.offset((b + 3) as isize)).u.CINT
7696                        < (*mem.offset((p + 3) as isize)).u.CINT
7697                    {
7698                        (*mem.offset((b + 3) as isize)).u.CINT = (*mem
7699                            .offset((p + 3) as isize))
7700                            .u
7701                            .CINT;
7702                    }
7703                    if (*mem.offset((b + 2) as isize)).u.CINT
7704                        < (*mem.offset((p + 2) as isize)).u.CINT
7705                    {
7706                        (*mem.offset((b + 2) as isize)).u.CINT = (*mem
7707                            .offset((p + 2) as isize))
7708                            .u
7709                            .CINT;
7710                    }
7711                }
7712            } else {
7713                (*mem.offset(p as isize)).hh.v.RH = (*mem.offset((b + 5) as isize))
7714                    .hh
7715                    .v
7716                    .RH;
7717                (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7718                (*mem.offset((b + 3) as isize)).u.CINT = (*mem.offset((p + 3) as isize))
7719                    .u
7720                    .CINT;
7721                if (*mem.offset((b + 1) as isize)).u.CINT
7722                    < (*mem.offset((p + 1) as isize)).u.CINT
7723                {
7724                    (*mem.offset((b + 1) as isize)).u.CINT = (*mem
7725                        .offset((p + 1) as isize))
7726                        .u
7727                        .CINT;
7728                }
7729            },
7730        )
7731    }
7732
7733    /// Append a glue node with natural width `min` and stretch `max - min` to box
7734    /// `b`. Port of XeTeX's `stack_glue_into_box` (`xetex.web`).
7735    unsafe fn stack_glue_into_box(
7736        self: &mut Self,
7737        b: halfword,
7738        min: scaled,
7739        max: scaled,
7740    ) -> EngineFlow<()> {
7741        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7742        const NULL: halfword = -(268435455 as i64) as halfword;
7743        const ZERO_GLUE: halfword = 0;
7744        let q = (&mut *(self as *mut PortableTexEngine<'_>)).znewspec(ZERO_GLUE)?;
7745        (*mem.offset((q + 1) as isize)).u.CINT = min;
7746        (*mem.offset((q + 2) as isize)).u.CINT = max - min;
7747        let p = (&mut *(self as *mut PortableTexEngine<'_>)).znewglue(q)?;
7748        Ok(
7749            if (*mem.offset(b as isize)).hh.u.B0 as i32 == 0 {
7750                let mut r = (*mem.offset((b + 5) as isize)).hh.v.RH;
7751                if r == NULL {
7752                    (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7753                } else {
7754                    while (*mem.offset(r as isize)).hh.v.RH != NULL {
7755                        r = (*mem.offset(r as isize)).hh.v.RH;
7756                    }
7757                    (*mem.offset(r as isize)).hh.v.RH = p;
7758                }
7759            } else {
7760                (*mem.offset(p as isize)).hh.v.RH = (*mem.offset((b + 5) as isize))
7761                    .hh
7762                    .v
7763                    .RH;
7764                (*mem.offset((b + 5) as isize)).hh.v.RH = p;
7765                (*mem.offset((b + 3) as isize)).u.CINT = (*mem.offset((p + 3) as isize))
7766                    .u
7767                    .CINT;
7768                (*mem.offset((b + 1) as isize)).u.CINT = (*mem.offset((p + 1) as isize))
7769                    .u
7770                    .CINT;
7771            },
7772        )
7773    }
7774
7775    /// Build a box (height/width at least `s`) for the stretchable glyph assembly
7776    /// `assembly` in font `f_0`, stacking parts with overlap glue. Faithful port
7777    /// of XeTeX's `build_opentype_assembly` (`xetex.web`), reading parts from the
7778    /// heap-owned [`GlyphAssembly`] handed out by `get_ot_assembly_ptr`.
7779    pub(crate) unsafe fn zbuildopentypeassembly(
7780        self: &mut Self,
7781        f_0: internalfontnumber,
7782        assembly: voidpointer,
7783        s: scaled,
7784        horiz_flag: integer,
7785    ) -> EngineFlow<halfword> {
7786        let mem: *mut memoryword = self.state.zmem.as_mut_ptr();
7787        let horiz = horiz_flag != 0;
7788        let b = (&mut *(self as *mut PortableTexEngine<'_>)).newnullbox()?;
7789        (*mem.offset(b as isize)).hh.u.B0 = if horiz { 0 } else { 1 };
7790        let parts: &[PortableMathAssemblyPart] = if assembly.is_null() {
7791            &[]
7792        } else {
7793            &(*(assembly as *const GlyphAssembly)).parts
7794        };
7795        let part_count = parts.len();
7796        let min_o = Self::ot_min_connector_overlap(
7797            self as *mut PortableTexEngine<'resources>,
7798            f_0 as i32,
7799        );
7800        let mut n: integer = -1;
7801        let mut no_extenders = true;
7802        loop {
7803            n += 1;
7804            let mut s_max: scaled = 0;
7805            let mut prev_o: scaled = 0;
7806            for part in parts.iter() {
7807                if part.extender {
7808                    no_extenders = false;
7809                    for _ in 0..n {
7810                        let mut o = part.start_connector;
7811                        if min_o < o {
7812                            o = min_o;
7813                        }
7814                        if prev_o < o {
7815                            o = prev_o;
7816                        }
7817                        s_max = s_max - o + part.full_advance;
7818                        prev_o = part.end_connector;
7819                    }
7820                } else {
7821                    let mut o = part.start_connector;
7822                    if min_o < o {
7823                        o = min_o;
7824                    }
7825                    if prev_o < o {
7826                        o = prev_o;
7827                    }
7828                    s_max = s_max - o + part.full_advance;
7829                    prev_o = part.end_connector;
7830                }
7831            }
7832            if s_max >= s || no_extenders {
7833                break;
7834            }
7835        }
7836        let mut prev_o: scaled = 0;
7837        for i in 0..part_count {
7838            let part = parts[i];
7839            let reps = if part.extender { n } else { 1 };
7840            for _ in 0..reps {
7841                let mut o = part.start_connector;
7842                if prev_o < o {
7843                    o = prev_o;
7844                }
7845                let oo = o;
7846                if min_o < o {
7847                    o = min_o;
7848                }
7849                if oo > 0 {
7850                    (&mut *(self as *mut PortableTexEngine<'_>))
7851                        .stack_glue_into_box(b, -oo, -o)?;
7852                }
7853                let g = part.glyph;
7854                (&mut *(self as *mut PortableTexEngine<'_>))
7855                    .stack_glyph_into_box(b, f_0, g)?;
7856                prev_o = part.end_connector;
7857            }
7858        }
7859        const NULL: halfword = -(268435455 as i64) as halfword;
7860        let mut p = (*mem.offset((b + 5) as isize)).hh.v.RH;
7861        let mut nat: scaled = 0;
7862        let mut str_: scaled = 0;
7863        while p != NULL {
7864            let ty = (*mem.offset(p as isize)).hh.u.B0 as i32;
7865            if ty == 8 {
7866                if horiz {
7867                    nat += (*mem.offset((p + 1) as isize)).u.CINT;
7868                } else {
7869                    nat
7870                        += (*mem.offset((p + 3) as isize)).u.CINT
7871                            + (*mem.offset((p + 2) as isize)).u.CINT;
7872                }
7873            } else if ty == 10 {
7874                let spec = (*mem.offset((p + 1) as isize)).hh.v.LH;
7875                nat += (*mem.offset((spec + 1) as isize)).u.CINT;
7876                str_ += (*mem.offset((spec + 2) as isize)).u.CINT;
7877            }
7878            p = (*mem.offset(p as isize)).hh.v.RH;
7879        }
7880        if s > nat && str_ > 0 {
7881            let mut o = s - nat;
7882            if o > str_ {
7883                o = str_;
7884            }
7885            (*mem.offset((b + 5) as isize)).hh.u.B1 = 0;
7886            (*mem.offset((b + 5) as isize)).hh.u.B0 = 1;
7887            (*mem.offset((b + 6) as isize)).gr = o as f64 / str_ as f64;
7888            let stretched = nat
7889                + (str_ as f64 * (*mem.offset((b + 6) as isize)).gr).round() as scaled;
7890            if horiz {
7891                (*mem.offset((b + 1) as isize)).u.CINT = stretched;
7892            } else {
7893                (*mem.offset((b + 3) as isize)).u.CINT = stretched;
7894            }
7895        } else if horiz {
7896            (*mem.offset((b + 1) as isize)).u.CINT = nat;
7897        } else {
7898            (*mem.offset((b + 3) as isize)).u.CINT = nat;
7899        }
7900        Ok(b)
7901    }
7902
7903}
7904
7905pub(crate) unsafe fn fputs(_text: const_string, _file: NativeFileHandle) -> i32 {
7906    0
7907}
7908
7909pub(crate) unsafe fn free(_ptr: voidpointer) {}
7910
7911pub(crate) unsafe fn xrealloc(old_address: address, _new_size: size_t) -> address {
7912    old_address
7913}
7914
7915pub(crate) unsafe fn getcreationdate() {}
7916
7917pub(crate) unsafe fn getfilemoddate(_s: integer) {}
7918
7919pub(crate) unsafe fn getfilesize(_s: integer) {}
7920
7921pub(crate) unsafe fn getfiledump(_s: integer, _offset: i32, _length: i32) {}
7922
7923pub(crate) unsafe fn getmd5sum(_s: integer, _file: i32) {}
7924
7925pub(crate) unsafe fn u_close_file_or_pipe(file: *mut unicodefile) {
7926    if !file.is_null() {
7927        PortableTexEngine::boundary_close_file(*file);
7928        *file = core::ptr::null_mut();
7929    }
7930}
7931
7932pub(crate) unsafe fn setinputfileencoding(
7933    file: unicodefile,
7934    mode: integer,
7935    _encoding_data: integer,
7936) {
7937    // XeTeX modes: AUTO=0, UTF8=1, UTF16BE=2, UTF16LE=3, RAW=4, ICUMAPPING=5.
7938    // We do not support ICU; unknown/ICU modes degrade to raw bytes. `AUTO`
7939    // here resolves to UTF-8 (the default after a failed sniff).
7940    if file.is_null() {
7941        return;
7942    }
7943    let handle = &mut *file;
7944    handle.encoding = match mode {
7945        1 => InputEncoding::Utf8,
7946        2 => InputEncoding::Utf16Be,
7947        3 => InputEncoding::Utf16Le,
7948        4 => InputEncoding::Bytes,
7949        0 => InputEncoding::Utf8, // AUTO resolves to UTF-8.
7950        _ => InputEncoding::Bytes,
7951    };
7952}
7953
7954pub(crate) unsafe fn usingGraphite(_engine: FontHandle) -> boolean {
7955    false_0
7956}
7957
7958pub(crate) unsafe fn aatprintfontname(
7959    _what: i32,
7960    _attrs: CFDictionaryRef,
7961    _param1: i32,
7962    _param2: i32,
7963) {
7964}
7965
7966pub(crate) unsafe fn grprintfontname(
7967    _what: integer,
7968    _engine: voidpointer,
7969    _param1: integer,
7970    _param2: integer,
7971) {
7972}
7973
7974pub(crate) unsafe fn printglyphname(_font: integer, _gid: integer) {}
7975
7976pub(crate) unsafe fn getnativecharheightdepth(
7977    _font: integer,
7978    _ch: integer,
7979    height: *mut integer,
7980    depth: *mut integer,
7981) {
7982    if !height.is_null() {
7983        *height = 0;
7984    }
7985    if !depth.is_null() {
7986        *depth = 0;
7987    }
7988}
7989
7990pub(crate) unsafe fn getnativecharsidebearings(
7991    _font: integer,
7992    _ch: integer,
7993    lsb: *mut integer,
7994    rsb: *mut integer,
7995) {
7996    if !lsb.is_null() {
7997        *lsb = 0;
7998    }
7999    if !rsb.is_null() {
8000        *rsb = 0;
8001    }
8002}
8003
8004pub(crate) unsafe fn getnativecharwd(_font: integer, _ch: integer) -> integer {
8005    0
8006}
8007
8008pub(crate) unsafe fn getnativecharht(_font: integer, _ch: integer) -> integer {
8009    0
8010}
8011
8012pub(crate) unsafe fn getnativechardp(_font: integer, _ch: integer) -> integer {
8013    0
8014}
8015
8016pub(crate) unsafe fn getnativecharic(_font: integer, _ch: integer) -> integer {
8017    0
8018}
8019
8020pub(crate) unsafe fn getglyphbounds(_font: integer, _edge: integer, _gid: integer) -> integer {
8021    0
8022}
8023
8024pub(crate) unsafe fn getfontcharrange(_font: integer, _first: i32) -> integer {
8025    0
8026}
8027
8028pub(crate) unsafe fn get_native_italic_correction(_node: voidpointer) -> Fixed {
8029    0
8030}
8031
8032pub(crate) unsafe fn get_native_glyph_italic_correction(_node: voidpointer) -> Fixed {
8033    0
8034}
8035
8036pub(crate) unsafe fn applymapping(
8037    _mapping: voidpointer,
8038    _text: *mut uint16_t,
8039    text_len: i32,
8040) -> i32 {
8041    text_len
8042}
8043
8044pub(crate) unsafe fn checkfortfmfontmapping() {}
8045
8046pub(crate) unsafe fn loadtfmfontmapping() -> voidpointer {
8047    nullptr
8048}
8049
8050pub(crate) unsafe fn applytfmfontmapping(_mapping: voidpointer, c_0: i32) -> i32 {
8051    c_0
8052}
8053
8054pub(crate) unsafe fn set_cp_code(
8055    _font_num: i32,
8056    _code: u32,
8057    _side: i32,
8058    _value: i32,
8059) -> i32 {
8060    0
8061}
8062
8063pub(crate) unsafe fn countpdffilepages() -> i32 {
8064    0
8065}
8066
8067pub(crate) unsafe fn aatfontget(_what: i32, _attrs: CFDictionaryRef) -> i32 {
8068    0
8069}
8070
8071pub(crate) unsafe fn aatfontget1(_what: i32, _attrs: CFDictionaryRef, _param: i32) -> i32 {
8072    0
8073}
8074
8075pub(crate) unsafe fn aatfontget2(
8076    _what: i32,
8077    _attrs: CFDictionaryRef,
8078    _param1: i32,
8079    _param2: i32,
8080) -> i32 {
8081    0
8082}
8083
8084pub(crate) unsafe fn aatfontgetnamed(_what: i32, _attrs: CFDictionaryRef) -> i32 {
8085    0
8086}
8087
8088pub(crate) unsafe fn aatfontgetnamed1(
8089    _what: i32,
8090    _attrs: CFDictionaryRef,
8091    _param: i32,
8092) -> i32 {
8093    0
8094}
8095
8096pub(crate) unsafe fn grfontgetnamed(_what: integer, _engine: voidpointer) -> integer {
8097    0
8098}
8099
8100pub(crate) unsafe fn grfontgetnamed1(
8101    _what: integer,
8102    _engine: voidpointer,
8103    _param: integer,
8104) -> integer {
8105    0
8106}
8107
8108pub(crate) unsafe fn otfontget(_what: integer, _engine: voidpointer) -> integer {
8109    0
8110}
8111
8112pub(crate) unsafe fn otfontget1(
8113    _what: integer,
8114    _engine: voidpointer,
8115    _param: integer,
8116) -> integer {
8117    0
8118}
8119
8120pub(crate) unsafe fn otfontget2(
8121    _what: integer,
8122    _engine: voidpointer,
8123    _param1: integer,
8124    _param2: integer,
8125) -> integer {
8126    0
8127}
8128
8129pub(crate) unsafe fn otfontget3(
8130    _what: integer,
8131    _engine: voidpointer,
8132    _param1: integer,
8133    _param2: integer,
8134    _param3: integer,
8135) -> integer {
8136    0
8137}
8138
8139/// Reclaim a [`GlyphAssembly`] previously handed out by
8140/// `get_ot_assembly_ptr`. Mirrors XeTeX's `free_ot_assembly`, but reclaims the
8141/// safe Rust `Box` allocation instead of calling `libc::free`.
8142///
8143/// # Safety
8144/// `assembly`, if non-null, must be a pointer returned by `get_ot_assembly_ptr`
8145/// and not previously freed.
8146pub(crate) unsafe fn free_ot_assembly(assembly: *mut GlyphAssembly) {
8147    if !assembly.is_null() {
8148        drop(Box::from_raw(assembly));
8149    }
8150}
8151
8152#[cfg(test)]
8153mod tests {
8154    use super::*;
8155
8156    /// Build a text [`PortableFileHandle`] over `bytes` with the given encoding.
8157    fn text_handle(bytes: Vec<u8>, encoding: InputEncoding) -> PortableFileHandle {
8158        let mut handle = PortableFileHandle::new(
8159            "test.tex".to_string(),
8160            ResourceKind::TexInput,
8161            None,
8162            resource_format_tex_input,
8163            bytes,
8164        );
8165        handle.encoding = encoding;
8166        handle
8167    }
8168
8169    /// Drain every Unicode scalar the decoder produces until EOF.
8170    fn decode_all(handle: &mut PortableFileHandle) -> Vec<u32> {
8171        let mut out = Vec::new();
8172        while let Some(scalar) = handle.next_input_scalar() {
8173            out.push(scalar);
8174        }
8175        out
8176    }
8177
8178    #[test]
8179    fn utf8_decoder_reads_multibyte_scalars() {
8180        // "αβγ" = CE B1 CE B2 CE B3 -> U+03B1, U+03B2, U+03B3.
8181        let mut h = text_handle(vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3], InputEncoding::Utf8);
8182        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
8183        // ASCII stays one-scalar-per-byte; a 3-byte (U+20AC €) and 4-byte
8184        // (U+1F600 😀) sequence round-trip.
8185        let mut h = text_handle(
8186            vec![b'A', 0xE2, 0x82, 0xAC, 0xF0, 0x9F, 0x98, 0x80],
8187            InputEncoding::Utf8,
8188        );
8189        assert_eq!(decode_all(&mut h), vec![0x41, 0x20AC, 0x1F600]);
8190    }
8191
8192    #[test]
8193    fn utf8_decoder_replaces_bad_sequences() {
8194        // A lead byte 0xCE followed by a non-continuation 'A' -> U+FFFD, and the
8195        // 'A' is UNGETC'd so it decodes next.
8196        let mut h = text_handle(vec![0xCE, b'A'], InputEncoding::Utf8);
8197        assert_eq!(decode_all(&mut h), vec![0xFFFD, 0x41]);
8198        // Lone continuation byte (0x80..0xBF as a lead) decodes to itself with
8199        // zero extra bytes (matches bytesFromUTF8 == 0), i.e. C8.. raw -> 0xFFFD
8200        // only when range-checked; a bare 0x80 has extra=0 so rval=0x80.
8201        let mut h = text_handle(vec![0x80], InputEncoding::Utf8);
8202        assert_eq!(decode_all(&mut h), vec![0x80]);
8203    }
8204
8205    #[test]
8206    fn utf16_decoders_read_units_and_surrogates() {
8207        // "αβγ" UTF-16LE: B1 03 B2 03 B3 03.
8208        let mut h = text_handle(
8209            vec![0xB1, 0x03, 0xB2, 0x03, 0xB3, 0x03],
8210            InputEncoding::Utf16Le,
8211        );
8212        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
8213        // Same in UTF-16BE: 03 B1 03 B2 03 B3.
8214        let mut h = text_handle(
8215            vec![0x03, 0xB1, 0x03, 0xB2, 0x03, 0xB3],
8216            InputEncoding::Utf16Be,
8217        );
8218        assert_eq!(decode_all(&mut h), vec![0x3B1, 0x3B2, 0x3B3]);
8219        // Surrogate pair U+1F600 in UTF-16LE: D83D DE00 -> 3D D8 00 DE.
8220        let mut h = text_handle(vec![0x3D, 0xD8, 0x00, 0xDE], InputEncoding::Utf16Le);
8221        assert_eq!(decode_all(&mut h), vec![0x1F600]);
8222        // High surrogate followed by a non-low unit -> U+FFFD, and the stray unit
8223        // (here U+0041) is stashed in saved_char and decoded next.
8224        let mut h = text_handle(vec![0x3D, 0xD8, 0x41, 0x00], InputEncoding::Utf16Le);
8225        assert_eq!(decode_all(&mut h), vec![0xFFFD, 0x41]);
8226        // Lone low surrogate -> U+FFFD.
8227        let mut h = text_handle(vec![0x00, 0xDC], InputEncoding::Utf16Le);
8228        assert_eq!(decode_all(&mut h), vec![0xFFFD]);
8229    }
8230
8231    #[test]
8232    fn bytes_mode_reads_each_byte_raw() {
8233        // RAW/Bytes: every byte becomes its own scalar, no multibyte decoding.
8234        let mut h = text_handle(vec![0xCE, 0xB1, 0x41], InputEncoding::Bytes);
8235        assert_eq!(decode_all(&mut h), vec![0xCE, 0xB1, 0x41]);
8236    }
8237
8238    #[test]
8239    fn bom_sniff_selects_encoding_and_consumes_bom() {
8240        // UTF-8 BOM EF BB BF + "A" -> UTF8, BOM consumed, 'A' next.
8241        let mut h = text_handle(vec![0xEF, 0xBB, 0xBF, b'A'], InputEncoding::Bytes);
8242        h.resolve_text_encoding_auto();
8243        assert_eq!(h.encoding, InputEncoding::Utf8);
8244        assert_eq!(h.cursor, 3);
8245        assert_eq!(decode_all(&mut h), vec![0x41]);
8246        // UTF-16BE BOM FE FF + U+03B1 -> UTF16BE, BOM consumed.
8247        let mut h = text_handle(vec![0xFE, 0xFF, 0x03, 0xB1], InputEncoding::Bytes);
8248        h.resolve_text_encoding_auto();
8249        assert_eq!(h.encoding, InputEncoding::Utf16Be);
8250        assert_eq!(h.cursor, 2);
8251        assert_eq!(decode_all(&mut h), vec![0x3B1]);
8252        // UTF-16LE BOM FF FE + U+03B1 -> UTF16LE, BOM consumed.
8253        let mut h = text_handle(vec![0xFF, 0xFE, 0xB1, 0x03], InputEncoding::Bytes);
8254        h.resolve_text_encoding_auto();
8255        assert_eq!(h.encoding, InputEncoding::Utf16Le);
8256        assert_eq!(h.cursor, 2);
8257        assert_eq!(decode_all(&mut h), vec![0x3B1]);
8258        // No BOM, ASCII text -> UTF8, nothing consumed.
8259        let mut h = text_handle(vec![b'h', b'i'], InputEncoding::Bytes);
8260        h.resolve_text_encoding_auto();
8261        assert_eq!(h.encoding, InputEncoding::Utf8);
8262        assert_eq!(h.cursor, 0);
8263        // 00 xx (BOM-less UTF-16BE heuristic) -> UTF16BE, NOT consumed (rewind).
8264        let mut h = text_handle(vec![0x00, 0x41], InputEncoding::Bytes);
8265        h.resolve_text_encoding_auto();
8266        assert_eq!(h.encoding, InputEncoding::Utf16Be);
8267        assert_eq!(h.cursor, 0);
8268        assert_eq!(decode_all(&mut h), vec![0x41]);
8269    }
8270
8271    #[test]
8272    fn input_line_decodes_into_buffer_per_profile() {
8273        // End-to-end through the real `boundary_input_line` reader: a text input
8274        // under the XeTeX profile is decoded (encoding resolved by the open-path
8275        // AUTO sniff), while the same bytes under the non-XeTeX (tex) profile are
8276        // read raw (Bytes mode). The engine's `buffer[first..last]` must hold the
8277        // expected Unicode scalars.
8278        fn buffer_after_input_line(profile: EngineProfile, bytes: Vec<u8>) -> Vec<u32> {
8279            let image = PortableFormatImage::empty();
8280            let mut engine = PortableTexEngine::from_format(profile, &image, EmptyResourceProvider);
8281            engine.initialize_format_state();
8282            // Build a text handle and resolve its encoding via the same open-path
8283            // helper the boundary open sites use.
8284            let mut handle = PortableFileHandle::new(
8285                "input.tex".to_string(),
8286                ResourceKind::TexInput,
8287                None,
8288                resource_format_tex_input,
8289                bytes,
8290            );
8291            PortableTexEngine::resolve_input_encoding(&engine, &mut handle);
8292            let raw = Box::into_raw(Box::new(handle));
8293            // Read one line into the buffer starting at `state.first`.
8294            engine.state.first = 0;
8295            let ok = unsafe {
8296                PortableTexEngine::boundary_input_line(
8297                    &mut engine as *mut PortableTexEngine<'_>,
8298                    raw as NativeFileHandle,
8299                )
8300            };
8301            assert_ne!(ok, 0, "boundary_input_line should succeed");
8302            let first = engine.state.first.max(0) as usize;
8303            let last = engine.state.last.max(0) as usize;
8304            let out = (first..last)
8305                .map(|i| unsafe { *engine.state.buffer.offset(i as isize) as u32 })
8306                .collect();
8307            unsafe { drop(Box::from_raw(raw)) };
8308            out
8309        }
8310        // "αβγ" = CE B1 CE B2 CE B3.
8311        let utf8 = vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3];
8312        assert_eq!(
8313            buffer_after_input_line(EngineProfile::xetex(), utf8.clone()),
8314            vec![0x3B1, 0x3B2, 0x3B3],
8315            "xetex must UTF-8 decode the input line"
8316        );
8317        assert_eq!(
8318            buffer_after_input_line(EngineProfile::tex(), utf8),
8319            vec![0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3],
8320            "non-xetex must read raw bytes"
8321        );
8322        // UTF-16LE with BOM under xetex decodes to the same scalars.
8323        let utf16le_bom = vec![0xFF, 0xFE, 0xB1, 0x03, 0xB2, 0x03, 0xB3, 0x03];
8324        assert_eq!(
8325            buffer_after_input_line(EngineProfile::xetex(), utf16le_bom),
8326            vec![0x3B1, 0x3B2, 0x3B3],
8327            "xetex must UTF-16LE decode a BOM'd input line"
8328        );
8329        // UTF-16BE with BOM under xetex.
8330        let utf16be_bom = vec![0xFE, 0xFF, 0x03, 0xB1, 0x03, 0xB2, 0x03, 0xB3];
8331        assert_eq!(
8332            buffer_after_input_line(EngineProfile::xetex(), utf16be_bom),
8333            vec![0x3B1, 0x3B2, 0x3B3],
8334            "xetex must UTF-16BE decode a BOM'd input line"
8335        );
8336    }
8337
8338    #[test]
8339    fn engine_abort_is_captured_at_runtime_boundary() {
8340        let image = PortableFormatImage::empty();
8341        let mut engine =
8342            PortableTexEngine::from_format(EngineProfile::tex(), &image, EmptyResourceProvider);
8343
8344        let completed = engine.catch_engine_abort(|engine| unsafe {
8345            PortableTexEngine::abort_engine(engine as *mut PortableTexEngine<'_>, 7)?;
8346            Ok(())
8347        });
8348
8349        assert!(!completed);
8350        assert_eq!(engine.last_abort_status(), Some(7));
8351    }
8352
8353    #[test]
8354    fn successful_engine_abort_completes_runtime_boundary() {
8355        let image = PortableFormatImage::empty();
8356        let mut engine =
8357            PortableTexEngine::from_format(EngineProfile::tex(), &image, EmptyResourceProvider);
8358
8359        let completed = engine.catch_engine_abort(|engine| unsafe {
8360            PortableTexEngine::abort_engine(engine as *mut PortableTexEngine<'_>, 0)?;
8361            Ok(())
8362        });
8363
8364        assert!(completed);
8365        assert_eq!(engine.last_abort_status(), None);
8366    }
8367}