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