Skip to main content

mathtex_portable_engine_generated/
runtime.rs

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