Skip to main content

retex_engine/
shipout.rs

1//! Shipping pages out (*TeX: The Program* §592–§640, LuaTeX's PDF
2//! backend positions).
3//!
4//! `ship_out` prints the `[n]` progress report, walks the box exactly as
5//! TeX's `hlist_out`/`vlist_out` do (including the cumulative glue
6//! rounding and leaders, §619–§637), executes `\openout`/`\write`/
7//! `\closeout` whatsits, and hands glyphs, rules and literals either to a
8//! [`Backend`] in page coordinates (`\outputmode>0`) or to the
9//! [`crate::dvi`] writer in DVI coordinates (`\outputmode=0`).
10
11use crate::Engine;
12use crate::fonts::PacketCommand;
13use crate::glue::Glue;
14use crate::nodes::{NodeData, NodeRef, RUNNING, Whatsit, glue_sub, id};
15use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
16
17/// A destination for shipped pages (the PDF writer). Coordinates are in
18/// scaled points from the lower-left corner of the page, y upwards.
19pub trait Backend {
20    /// Starts a page of the given size.
21    fn begin_page(&mut self, engine: &Engine, width: i32, height: i32);
22    /// Draws character `chr` of font `font` with its origin at (x, y).
23    fn glyph(&mut self, engine: &Engine, font: u32, chr: u32, x: i32, y: i32);
24    /// Draws a glyph expanded horizontally by `ef` millionths of its width
25    /// (font expansion; `ef` is never 0).
26    fn glyph_expanded(&mut self, engine: &Engine, font: u32, chr: u32, x: i32, y: i32, ef: i32) {
27        let _ = ef;
28        self.glyph(engine, font, chr, x, y);
29    }
30    /// Draws a filled rule with its lower-left corner at (x, y).
31    fn rule(&mut self, x: i32, y: i32, width: i32, height: i32);
32    /// Emits a literal (`\pdfextension literal`) at (x, y).
33    fn literal(&mut self, mode: u8, data: &[u8], x: i32, y: i32);
34    /// Emits text written by `pdf.print` at (x, y): a literal without
35    /// the newline after it (observed).
36    fn print(&mut self, mode: u8, data: &[u8], x: i32, y: i32) {
37        self.literal(mode, data, x, y);
38    }
39    /// Finishes the page.
40    fn end_page(&mut self, engine: &Engine);
41    /// Finishes the document.
42    fn finish(&mut self, engine: &mut Engine) -> Option<Finished>;
43
44    /// `\pdfextension save` at (x, y).
45    fn save(&mut self, x: i32, y: i32) {
46        self.literal(0, b"q", x, y);
47    }
48
49    /// `\pdfextension restore` at (x, y).
50    fn restore(&mut self, x: i32, y: i32) {
51        self.literal(0, b"Q", x, y);
52    }
53
54    /// `\pdfextension setmatrix` at (x, y).
55    fn set_matrix(&mut self, x: i32, y: i32, matrix: &[u8]) {
56        let mut d = matrix.to_vec();
57        d.extend_from_slice(b" 0 0 cm");
58        self.literal(0, &d, x, y);
59    }
60
61    /// Before the process forks for a preamble snapshot: writes out what
62    /// is buffered (see [`resume`](Self::resume)).
63    fn suspend(&mut self) -> std::io::Result<()> {
64        Ok(())
65    }
66
67    /// Whether a pass can resume from the snapshot taken at
68    /// [`suspend`](Self::suspend): the output file is still there, at least
69    /// as long as then (a failed build may have removed it).
70    fn resumable(&self) -> bool {
71        true
72    }
73
74    /// A fingerprint of the state that decides what the backend writes
75    /// from now on (see [`crate::fingerprint`]): not what it wrote.
76    fn fingerprint(&mut self) -> u64 {
77        0
78    }
79
80    /// For `--watch` (see `docs/design/incremental-rebuilds.md`): what the
81    /// output holds so far, flushed to the file, as an encoded
82    /// `retex_pdf::Prefix`; `None` if there is no output file.
83    fn prefix(&mut self) -> Option<Vec<u8>> {
84        None
85    }
86
87    /// The output file, and where it was first opened (it keeps that name
88    /// once the run is over).
89    fn output_file(&self) -> Option<(std::path::PathBuf, std::path::PathBuf)> {
90        None
91    }
92
93    /// Gives the output file the second name `to`, under which it goes on
94    /// (its first name may then be given to another file).
95    fn divert(&mut self, _to: &std::path::Path) -> std::io::Result<()> {
96        Err(std::io::Error::other("not supported"))
97    }
98
99    /// Goes on in file `to` with another run's `prefix` (see
100    /// [`prefix`](Self::prefix)), read from that run's file `from`,
101    /// followed by `saved` from `cut` on: this backend's output up to the
102    /// end (taken from its file before the other run changed it), the two
103    /// runs being in the same state at that point.
104    fn splice(
105        &mut self,
106        _prefix: &[u8],
107        _from: &std::path::Path,
108        _saved: &[u8],
109        _cut: u64,
110        _to: &std::path::Path,
111    ) -> std::io::Result<()> {
112        Err(std::io::Error::other("not supported"))
113    }
114
115    /// In a process resumed from a preamble snapshot: reopens the output
116    /// file opened before the snapshot, cut back to its length then (the
117    /// forked processes share the file's position otherwise).
118    fn resume(&mut self) -> std::io::Result<()> {
119        Ok(())
120    }
121
122    /// Gives up the output file: with `remove`, it is deleted (after a
123    /// fatal error), otherwise left empty (no pages), as LuaTeX does.
124    fn abandon(&mut self, _remove: bool) {}
125
126    /// Prepares for writing an object whose text is still to be made
127    /// (allocates the object stream it will go into, as LuaTeX does).
128    fn begin_object(&mut self, _engine: &Engine) {}
129
130    /// The object number of the page begun last.
131    fn current_page_object(&self) -> Option<i32> {
132        None
133    }
134
135    /// Writes the page's contents and dictionary: the page attributes and
136    /// resources of `items` are final, and whether there are annotations.
137    fn close_page_contents(&mut self, _engine: &Engine, _items: &crate::pdfext::PageItems) {}
138
139    /// Hands over the page's annotations, links, destinations and
140    /// referenced objects, just before [`end_page`](Self::end_page).
141    fn page_items(&mut self, _engine: &Engine, _items: &crate::pdfext::PageItems) {}
142
143    /// Starts writing box resource `index` (natural size `size` = width,
144    /// height, depth) as a form XObject; drawing goes into the form until
145    /// [`end_box_form`](Self::end_box_form).
146    fn begin_box_form(
147        &mut self,
148        _engine: &Engine,
149        _index: u32,
150        _size: (i32, i32, i32),
151        _attr: &[u8],
152        _resources: &[u8],
153    ) {
154    }
155
156    /// Finishes the box resource being written (its content stream).
157    fn end_box_form(&mut self, _engine: &Engine) {}
158
159    /// Writes the resources of the box resource finished last, after the
160    /// forms it uses.
161    fn close_box_form_resources(&mut self, _engine: &Engine) {}
162
163    /// Writes the resources of the page ended last, after the forms it
164    /// uses.
165    fn close_page_resources(&mut self, _engine: &Engine) {}
166
167    /// Draws box resource `index` with its lower-left corner at (x, y),
168    /// scaled to `width` × `height` (height + depth).
169    fn box_form(
170        &mut self,
171        _engine: &Engine,
172        _index: u32,
173        _x: i32,
174        _y: i32,
175        _width: i32,
176        _height: i32,
177    ) {
178    }
179
180    /// Writes image resource `index` (its object number) now (`\immediate`).
181    fn write_image(&mut self, _engine: &Engine, _index: u32) {}
182
183    /// Draws image resource `index` with its lower-left corner at (x, y),
184    /// `width` wide and `height` (height + depth) tall.
185    fn image(
186        &mut self,
187        _engine: &Engine,
188        _index: u32,
189        _x: i32,
190        _y: i32,
191        _width: i32,
192        _height: i32,
193    ) {
194    }
195
196    /// The images written (when the pages and forms using them were
197    /// closed) since the last call, if the backend writes them that way;
198    /// `None` to have them announced at the end of the page using them.
199    fn written_images(&mut self) -> Option<Vec<u32>> {
200        None
201    }
202
203    /// Writes an object created with `\pdfextension obj`.
204    fn write_object(&mut self, _engine: &Engine, _obj: &crate::pdfext::PdfObject) {}
205}
206
207/// What the backend reports when the document is finished.
208#[derive(Clone, Debug, Default)]
209pub struct Finished {
210    /// The output file name as printed in "Output written on …".
211    pub file: String,
212    /// Its size in bytes.
213    pub size: u64,
214    /// Files embedded, as printed: `<font file>`, `{encoding file}`
215    /// (LuaTeX order).
216    pub embedded: Vec<String>,
217    /// Objects written into object streams, and the number of streams.
218    pub objstm: (u32, u32),
219}
220
221/// The width of the replacement nodes before `q` (from `head`).
222fn items_before_width(e: &Engine, head: NodeRef, q: NodeRef) -> i32 {
223    let mut w = 0;
224    let mut r = head;
225    while !r.is_null() && r != q {
226        w += e.replace_item_width(r);
227        r = e.nodes.next(r);
228    }
229    w
230}
231
232/// The glue-rounding state of a box being shipped (`cur_g`, `cur_glue`).
233#[derive(Clone)]
234struct GlueState {
235    cur_g: i32,
236    cur_glue: f64,
237    order: u8,
238    sign: u8,
239    set: f64,
240}
241
242impl GlueState {
243    /// TeX's glue computation for one glue node: returns its width.
244    fn advance(&mut self, g: &Glue) -> i32 {
245        let mut w = g.width - self.cur_g;
246        if self.sign != 0 {
247            let applies = if self.sign == 1 {
248                g.stretch_order == self.order
249            } else {
250                g.shrink_order == self.order
251            };
252            if applies {
253                if self.sign == 1 {
254                    self.cur_glue += f64::from(g.stretch);
255                } else {
256                    self.cur_glue -= f64::from(g.shrink);
257                }
258                let t = (self.set * self.cur_glue).clamp(-1_000_000_000.0, 1_000_000_000.0);
259                self.cur_g = t.round() as i32;
260            }
261        }
262        w += self.cur_g;
263        w
264    }
265}
266
267impl Engine {
268    /// Calls the page-number callback `name` if one is registered; whether
269    /// it was.
270    fn page_number_callback(&mut self, name: &str) -> bool {
271        let Some(lua) = self.lua.clone() else {
272            return false;
273        };
274        if !lua.has_callback(name) {
275            return false;
276        }
277        if let Err(e) = lua.call_simple(self, name) {
278            self.lua_error(&e);
279        }
280        true
281    }
282
283    /// Ships box `p` out (`ship_out`).
284    pub fn ship_out(&mut self, p: NodeRef) {
285        self.work_begin(crate::profile::Work::Shipout);
286        self.ship_out_inner(p);
287        self.work_end();
288    }
289
290    fn ship_out_inner(&mut self, p: NodeRef) {
291        self.pdf.page_annots.clear();
292        let tracing = self.eqtb.int(ip::TRACINGOUTPUT) > 0;
293        if tracing {
294            self.printer.print_nl("");
295            self.printer.print_ln();
296            self.printer.print_str("Completed box being shipped out");
297        }
298        // The `start_page_number` callback replaces ` [` and the page
299        // numbers, `stop_page_number` the closing `]` (observed).
300        if !self.page_number_callback("start_page_number") {
301            if self.printer.term_offset > self.printer.max_print_line.saturating_sub(9) {
302                self.printer.print_ln();
303            } else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
304                self.printer.print_char(' ' as u32);
305            }
306            self.printer.print_char('[' as u32);
307            let mut j = 9;
308            while j > 0 && self.eqtb.count[j].val == 0 {
309                j -= 1;
310            }
311            for k in 0..=j {
312                self.printer.print_int(self.eqtb.count[k].val.into());
313                if k < j {
314                    self.printer.print_char('.' as u32);
315                }
316            }
317        }
318        self.printer.update_terminal();
319        if p.is_null() {
320            // `tex.shipout` of a void box (observed).
321            self.luatex_warning(
322                "backend",
323                "pagedir differs from bodydir, the output may be placed wrongly on the page",
324            );
325            let msg = "no vlist or hlist in (xform) shipout";
326            if self.eqtb.int(ip::OUTPUTMODE) > 0 {
327                self.pdf_fatal(msg);
328            }
329            self.luatex_fatal("pdf backend", msg);
330        }
331        if tracing {
332            if !self.page_number_callback("stop_page_number") {
333                self.printer.print_char(']' as u32);
334            }
335            self.begin_diagnostic();
336            self.show_box(p);
337            self.end_diagnostic(true);
338        }
339        self.ship_box(p);
340        if !tracing && !self.page_number_callback("stop_page_number") {
341            self.printer.print_char(']' as u32);
342        }
343        self.dead_cycles = 0;
344        self.printer.update_terminal();
345        self.nodes.flush_list(p);
346    }
347
348    /// Ships the contents of box `p`.
349    fn ship_box(&mut self, p: NodeRef) {
350        let Some(b) = self.nodes.box_data(p).cloned() else {
351            return;
352        };
353        let pdf = self.eqtb.int(ip::OUTPUTMODE) > 0;
354        // Page size and origin.
355        let h_origin = self.eqtb.dimen(crate::backend::H_ORIGIN);
356        let v_origin = self.eqtb.dimen(crate::backend::V_ORIGIN);
357        let h_off = self.eqtb.dimen(dp::HOFFSET);
358        let v_off = self.eqtb.dimen(dp::VOFFSET);
359        let mut width = self.eqtb.dimen(dp::PAGEWIDTH);
360        let mut height = self.eqtb.dimen(dp::PAGEHEIGHT);
361        // Observed: without \pagewidth (\pageheight), the page is the box
362        // plus 1in and the offset on each side, whatever the origin.
363        const ONE_INCH: i32 = 4_736_286;
364        if width <= 0 {
365            width = b.width + 2 * (ONE_INCH + h_off);
366        }
367        if height <= 0 {
368            height = b.height + b.depth + 2 * (ONE_INCH + v_off);
369        }
370        let mut backend = if pdf { self.backend.take() } else { None };
371        let forms_depth = self.pending_forms.len();
372        self.pending_forms.push(Vec::new());
373        if let Some(be) = backend.as_mut() {
374            self.pdf.page = Default::default();
375            be.begin_page(self, width, height);
376            if let Some(n) = be.current_page_object() {
377                let page = self.total_pages as i32 + 1;
378                self.pdf.page_refs.insert(page, n);
379                self.pdf.kinds.insert(n, crate::pdfext::ObjKind::Page);
380            }
381            // Observed: written at the page box's reference point.
382            let (x, y) = (h_origin + h_off, height - (v_origin + v_off + b.height));
383            for (mode, lit) in self.page_colorstack_literals() {
384                be.literal(mode, &lit, x, y);
385            }
386        }
387        let dvi = if pdf {
388            None
389        } else {
390            let mut d = std::mem::take(&mut self.dvi);
391            d.begin_page(self, (b.width, b.height, b.depth), (h_off, v_off));
392            Some(d)
393        };
394        let sync = self.synctex_recording();
395        if sync {
396            self.synctex_begin_page((h_origin + h_off, v_origin + v_off));
397        }
398        let mut walker = Walker {
399            backend,
400            dvi,
401            h_origin: h_origin + h_off,
402            v_origin: v_origin + v_off,
403            page_height: height,
404            dvi_h: h_off,
405            dvi_v: v_off,
406            cur_s: -1,
407            doing_leaders: false,
408            page: true,
409            sync,
410        };
411        // Observed: a right-to-left hbox shipped out as the page has its
412        // right edge at the origin.
413        let cur_h = if self.nodes.id(p) == id::HLIST && b.dir == 1 {
414            -b.width
415        } else {
416            0
417        };
418        let cur_v = b.height;
419        if self.nodes.id(p) == id::VLIST {
420            self.vlist_out(&mut walker, p, cur_h, cur_v);
421        } else {
422            self.hlist_out(&mut walker, p, cur_h, cur_v);
423        }
424        if let Some(mut be) = walker.backend.take() {
425            self.flush_pending_objects(be.as_mut());
426            self.collect_page_texts();
427            let items = std::mem::take(&mut self.pdf.page);
428            be.close_page_contents(self, &items);
429            self.pdf.page = items;
430            self.resolve_link_actions();
431            let items = std::mem::take(&mut self.pdf.page);
432            be.page_items(self, &items);
433            // Observed order: the page's own objects, the forms it uses,
434            // its resources.
435            be.end_page(self);
436            let mut be = self.write_pending_forms(be);
437            be.close_page_resources(self);
438            self.flush_pending_objects(be.as_mut());
439            self.backend = Some(be);
440            let page_images = std::mem::take(&mut self.pdf.page_images);
441            let written = self.backend.as_mut().and_then(|b| b.written_images());
442            for n in written.unwrap_or(page_images) {
443                self.print_image_name(n);
444            }
445        }
446        self.pending_forms.truncate(forms_depth);
447        if let Some(mut d) = walker.dvi.take() {
448            d.end_page();
449            self.dvi = d;
450        }
451        if sync {
452            self.synctex_end_page();
453        }
454        self.total_pages += 1;
455        if let Some(f) = self.progress_hook {
456            f(self);
457        }
458        if let Some(f) = self.page_hook {
459            f(self);
460        }
461    }
462
463    /// Finishes the PDF file and nothing else, quietly: for `--watch`,
464    /// in a process that writes a PDF spliced from two runs (see
465    /// [`Backend::splice`]). Returns whether it was written.
466    pub fn finish_pdf_only(&mut self) -> bool {
467        let Some(mut be) = self.backend.take() else {
468            return false;
469        };
470        self.pdf_finish_document();
471        self.flush_pending_objects(be.as_mut());
472        let done = be.finish(self).is_some();
473        self.backend = Some(be);
474        done
475    }
476
477    /// Writes the objects queued while the backend was busy.
478    fn flush_pending_objects(&mut self, be: &mut dyn Backend) {
479        for obj in std::mem::take(&mut self.pdf.pending) {
480            be.write_object(self, &obj);
481        }
482    }
483
484    /// Prints LuaTeX's note about reading font map file `name`
485    /// (`{/path/pdftex.map}`), when it exists.
486    pub(crate) fn print_map_file_note(&mut self, name: &str) {
487        let path = self
488            .kpse
489            .as_ref()
490            .and_then(|k| k.borrow_mut().find_file(name, retex_kpath::FileFormat::Map));
491        if let Some(path) = path {
492            // Printed like a C string (log lines up to 80 characters).
493            let mut text = b"{".to_vec();
494            text.extend_from_slice(path.to_string_lossy().as_bytes());
495            text.push(b'}');
496            self.printer.print_lua_bytes(&text);
497        }
498    }
499
500    /// Outputs a glyph with its reference point at TeX position (`h`,
501    /// `v`).
502    fn out_glyph(&mut self, w: &mut Walker, font: u32, chr: u32, h: i32, v: i32, ef: i32) {
503        self.out_glyph_at_depth(w, font, chr, h, v, ef, 0);
504    }
505
506    /// [`out_glyph`](Self::out_glyph) inside `depth` virtual characters.
507    #[allow(clippy::too_many_arguments)]
508    fn out_glyph_at_depth(
509        &mut self,
510        w: &mut Walker,
511        font: u32,
512        chr: u32,
513        h: i32,
514        v: i32,
515        ef: i32,
516        depth: u32,
517    ) {
518        if let Some(vf) = &self.font(font).vf
519            && vf.packets.contains_key(&chr)
520        {
521            if depth < 8 {
522                let mut st = PacketState {
523                    h,
524                    v,
525                    font: 0,
526                    stack: Vec::new(),
527                    ef,
528                    depth,
529                    vf: vf.clone(),
530                    vfont: font,
531                    chr,
532                };
533                let vf = vf.clone();
534                for c in &vf.packets[&chr] {
535                    self.packet_command(w, &mut st, c);
536                }
537            }
538            return;
539        }
540        if !self.pdf.map_note_done
541            && w.backend.is_some()
542            && self.font(font).chars.contains_key(&chr)
543        {
544            // Observed: LuaTeX reads the map file when the first character
545            // is output (not a virtual one, nor one missing from its font,
546            // in \nullfont, say).
547            self.pdf.map_note_done = true;
548            self.print_map_file_note("pdftex.map");
549        }
550        let (x, y) = w.pos(h, v);
551        // Observed: a character missing from its font is not output.
552        if let Some(be) = w.backend.as_deref_mut()
553            && self.font(font).chars.contains_key(&chr)
554        {
555            if ef == 0 {
556                be.glyph(self, font, chr, x, y);
557            } else {
558                be.glyph_expanded(self, font, chr, x, y, ef);
559            }
560        }
561        if let Some(d) = w.dvi.as_mut()
562            && self.font(font).chars.contains_key(&chr)
563        {
564            let wd = crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
565            d.glyph(self, font, chr, w.dvi_h + h, w.dvi_v + v, wd);
566        }
567    }
568
569    /// Runs one command of a virtual character's packet: characters of
570    /// the local fonts and rules are output at the positions the packet
571    /// moves to (in DVI as in PDF, LuaTeX writes the local fonts'
572    /// characters, not the virtual one).
573    fn packet_command(&mut self, w: &mut Walker, st: &mut PacketState, c: &PacketCommand) {
574        match c {
575            PacketCommand::Font(i) => st.font = st.vf.fonts.get(*i).copied().unwrap_or(0),
576            PacketCommand::FontId(f) => st.font = *f,
577            PacketCommand::Char(c) => {
578                // The first local font until one is selected.
579                if st.font == 0 {
580                    st.font = st.vf.fonts.first().copied().unwrap_or(0);
581                }
582                let f = st.font;
583                if f != 0 && self.font(f).chars.contains_key(c) {
584                    self.out_glyph_at_depth(w, f, *c, st.h, st.v, st.ef, st.depth + 1);
585                    st.h = st.h.wrapping_add(crate::expansion::expanded_width(
586                        self.font_char_dimen(f, *c, 0),
587                        st.ef,
588                    ));
589                }
590            }
591            PacketCommand::Rule { height, width } => {
592                if *height > 0 && *width > 0 {
593                    Self::out_rule(w, st.h, st.v, *width, *height);
594                }
595                st.h = st.h.wrapping_add(*width);
596            }
597            PacketCommand::Right(x) => st.h = st.h.wrapping_add(*x),
598            PacketCommand::Down(y) => st.v = st.v.wrapping_add(*y),
599            PacketCommand::Push => st.stack.push((st.h, st.v)),
600            PacketCommand::Pop => {
601                if let Some((h, v)) = st.stack.pop() {
602                    (st.h, st.v) = (h, v);
603                }
604            }
605            PacketCommand::Special(data) => self.out_special(w, st.h, st.v, data),
606            PacketCommand::Pdf(mode, data) => {
607                let (x, y) = w.pos(st.h, st.v);
608                if let Some(be) = w.backend.as_deref_mut() {
609                    be.literal(*mode, data, x, y);
610                }
611            }
612            PacketCommand::Lua(r) => self.packet_lua(w, st, *r),
613            // Observed: only a box is output, with its reference point at
614            // the current point (its shift is ignored), and the position
615            // stays.
616            PacketCommand::Node(p) => {
617                if matches!(self.nodes.id(*p), id::HLIST | id::VLIST) {
618                    self.out_box(w, *p, st.h, st.v);
619                }
620            }
621        }
622    }
623
624    /// Runs the `lua` command of a packet, kept under reference `r`.
625    /// While it runs, the `vf` library draws into the packet (from its
626    /// current state, which it leaves for the commands after) and
627    /// `pdf.getpos` gives the packet's current point.
628    fn packet_lua(&mut self, w: &mut Walker, st: &mut PacketState, r: i32) {
629        let Some(host) = self.lua.clone() else {
630            return;
631        };
632        let walker = std::mem::take(w);
633        let saved_pos = self.pdf.pos;
634        self.pdf.pos = walker.pos(st.h, st.v);
635        let args = crate::ReferenceArgs::Glyph(st.vfont, st.chr);
636        self.vf_frames.push(VfFrame {
637            walker,
638            state: st.clone(),
639        });
640        let result = host.call_reference(self, r, args);
641        if let Some(frame) = self.vf_frames.pop() {
642            *w = frame.walker;
643            // Observed: the font `vf.fontid` selects is not kept for the
644            // commands after.
645            let font = st.font;
646            *st = frame.state;
647            st.font = font;
648        }
649        self.pdf.pos = saved_pos;
650        if let Err(e) = result {
651            self.lua_error(&e);
652        }
653    }
654
655    /// Runs `cmd` in the packet whose `lua` command is running (the `vf`
656    /// library); false outside one.
657    pub fn vf_command(&mut self, cmd: &PacketCommand) -> bool {
658        let Some(mut frame) = self.vf_frames.pop() else {
659            return false;
660        };
661        // Observed: `vf.char` outputs nothing until the packet's commands
662        // or `vf.fontid` selected a font (no first local font by
663        // default).
664        if matches!(cmd, PacketCommand::Char(_)) && frame.state.font == 0 {
665            self.vf_frames.push(frame);
666            return true;
667        }
668        self.packet_command(&mut frame.walker, &mut frame.state, cmd);
669        self.pdf.pos = frame.walker.pos(frame.state.h, frame.state.v);
670        self.vf_frames.push(frame);
671        true
672    }
673
674    /// A dimension given to the `vf` library (`vf.right(x)`) in the
675    /// packet whose `lua` command is running: observed, `x` is a
676    /// fix_word scaled by the size of the virtual font, as dimensions in
677    /// VF files are, and one beyond 16 times the size is a fatal error.
678    /// `None` outside a packet.
679    pub fn vf_scaled(&mut self, x: i64) -> Option<i32> {
680        let f = self.vf_frames.last()?.state.vfont;
681        if !(-(1 << 24)..1 << 24).contains(&x) {
682            self.pdf_fatal_in("vf", "vf scaling");
683        }
684        let size = self.font(f).size;
685        Some(retex_font::tfm::FixScaler::new(size).scale(x as i32))
686    }
687
688    /// Outputs a rule with its lower left corner at TeX position (`h`,
689    /// `v`).
690    fn out_rule(w: &mut Walker, h: i32, v: i32, width: i32, height: i32) {
691        let (x, y) = w.pos(h, v);
692        if let Some(be) = w.backend.as_deref_mut() {
693            be.rule(x, y, width, height);
694        }
695        if let Some(d) = w.dvi.as_mut() {
696            d.rule(w.dvi_h + h, w.dvi_v + v, width, height);
697        }
698    }
699
700    /// Draws box resource `index` with its lower-left corner at (`h`, `v`).
701    /// A form not written yet is written after the page or form being
702    /// written (observed object numbering of LuaTeX).
703    fn out_box_resource(
704        &mut self,
705        w: &mut Walker,
706        index: i32,
707        h: i32,
708        v: i32,
709        width: i32,
710        height: i32,
711    ) {
712        let n = index as u32;
713        let (x, y) = w.pos(h, v);
714        let Some(mut be) = w.backend.take() else {
715            return;
716        };
717        if self
718            .box_resource_slot(n)
719            .is_some_and(|i| !self.box_resources[i].written)
720        {
721            match self.pending_forms.last_mut() {
722                Some(p) => {
723                    if !p.contains(&n) {
724                        p.push(n);
725                    }
726                }
727                None => be = self.write_box_resource(be, n),
728            }
729        }
730        be.box_form(self, n, x, y, width, height);
731        w.backend = Some(be);
732    }
733
734    /// Writes box resource `n` as a form: ships its box into the form with
735    /// the box's reference point at (0, depth), then the forms it uses that
736    /// are not written yet, then its resources.
737    pub fn write_box_resource(&mut self, be: Box<dyn Backend>, n: u32) -> Box<dyn Backend> {
738        let Some(slot) = self.box_resource_slot(n) else {
739            return be;
740        };
741        let r = self.box_resources[slot].clone();
742        if r.written {
743            return be;
744        }
745        // Observed: a form is shipped out like a page, so with
746        // \tracingoutput positive the line is shown (but neither the
747        // page number nor the box).
748        if self.eqtb.int(ip::TRACINGOUTPUT) > 0 {
749            self.printer.print_nl("");
750            self.printer.print_ln();
751            self.printer.print_str("Completed box being shipped out");
752        }
753        let mut be = be;
754        let (bw, bh, bd) = r.size;
755        self.pending_forms.push(Vec::new());
756        self.form_objects.push(Vec::new());
757        be.begin_box_form(self, n, (bw, bh, bd), &r.attr, &r.resources);
758        let sync = self.synctex_recording();
759        if sync {
760            self.synctex_begin_form(bh + bd);
761        }
762        let mut walker = Walker {
763            backend: Some(be),
764            dvi: None,
765            h_origin: 0,
766            v_origin: 0,
767            page_height: bh + bd,
768            dvi_h: 0,
769            dvi_v: 0,
770            cur_s: -1,
771            doing_leaders: false,
772            page: false,
773            sync,
774        };
775        if self.nodes.id(r.list) == id::VLIST {
776            self.vlist_out(&mut walker, r.list, 0, bh);
777        } else {
778            self.hlist_out(&mut walker, r.list, 0, bh);
779        }
780        let mut be = walker.backend.take().expect("the walker keeps the backend");
781        be.end_box_form(self);
782        self.box_resources[slot].written = true;
783        for obj in self.form_objects.pop().unwrap_or_default() {
784            be.write_object(self, &obj);
785        }
786        be = self.write_pending_forms(be);
787        // Observed: the forms this one uses are written inside its sheet.
788        if sync {
789            self.synctex_end_form();
790        }
791        be.close_box_form_resources(self);
792        // Observed: an image is announced when it is written, after the
793        // first form or page using it.
794        for n in be.written_images().unwrap_or_default() {
795            self.print_image_name(n);
796        }
797        be
798    }
799
800    /// Writes the forms used by the page or form just finished that are
801    /// not written yet.
802    fn write_pending_forms(&mut self, mut be: Box<dyn Backend>) -> Box<dyn Backend> {
803        for n in self.pending_forms.pop().unwrap_or_default() {
804            be = self.write_box_resource(be, n);
805        }
806        be
807    }
808
809    /// Draws image `index` with its lower-left corner at (`h`, `v`).
810    fn out_image(&mut self, w: &mut Walker, index: i32, h: i32, v: i32, width: i32, height: i32) {
811        let (x, y) = w.pos(h, v);
812        if let Some(be) = w.backend.as_deref_mut() {
813            be.image(self, index as u32, x, y, width, height);
814            if self.mark_image_written(index as u32) {
815                self.pdf.page_images.push(index as u32);
816            }
817        }
818    }
819
820    /// Outputs box `p` (`hlist_out` or `vlist_out`) with its reference
821    /// point at (`h`, `v`).
822    fn out_box(&mut self, w: &mut Walker, p: NodeRef, h: i32, v: i32) {
823        // A box that contains itself (made by Lua code) is not followed
824        // forever (LuaTeX crashes).
825        if w.cur_s >= crate::nodes::MAX_NESTING as i32 {
826            return;
827        }
828        if self.nodes.id(p) == id::VLIST {
829            self.vlist_out(w, p, h, v);
830        } else {
831            self.hlist_out(w, p, h, v);
832        }
833    }
834
835    /// Enters a box: `cur_s` goes up and nested boxes get a DVI `push`.
836    fn enter_box(w: &mut Walker) {
837        w.cur_s += 1;
838        if let Some(d) = w.dvi.as_mut() {
839            if w.cur_s > 0 {
840                d.push(w.cur_s);
841            } else {
842                d.note_depth(w.cur_s);
843            }
844        }
845    }
846
847    /// Leaves a box (`dvi_pop`).
848    fn leave_box(w: &mut Walker) {
849        if w.cur_s > 0
850            && let Some(d) = w.dvi.as_mut()
851        {
852            d.pop();
853        }
854        w.cur_s -= 1;
855    }
856
857    /// Outputs an hlist box (`hlist_out`); (`cur_h`, `cur_v`) is the box's
858    /// reference point in TeX coordinates (v downwards).
859    fn hlist_out(&mut self, w: &mut Walker, this_box: NodeRef, mut cur_h: i32, cur_v: i32) {
860        let Some(b) = self.nodes.box_data(this_box).cloned() else {
861            return;
862        };
863        let mut gs = GlueState {
864            cur_g: 0,
865            cur_glue: 0.0,
866            order: b.glue_order,
867            sign: b.glue_sign,
868            set: b.glue_set,
869        };
870        Self::enter_box(w);
871        let base_line = cur_v;
872        let left_edge = cur_h;
873        let ctx = crate::pdfship::BoxCtx {
874            left: left_edge,
875            base: base_line,
876            width: b.width,
877            height: b.height,
878            depth: b.depth,
879        };
880        if !w.doing_leaders {
881            self.ship_continue_links(w, &ctx);
882        }
883        if w.sync {
884            let f = self.sync_of(this_box);
885            self.synctex
886                .box_open(true, f, cur_h, cur_v, [b.width, b.height, b.depth]);
887        }
888        // Right-to-left boxes and direction nodes: where each node goes
889        // (the nodes are still output in list order).
890        let places = self.rtl_places(b.head, b.dir == 1, &gs);
891        let mut i = 0;
892        let mut p = b.head;
893        while !p.is_null() {
894            let next = self.nodes.next(p);
895            // (Nodes a `\latelua` function inserted have no place.)
896            let rtl = match places.as_ref().and_then(|pl| pl.get(i)) {
897                Some(&(x, rtl)) => {
898                    cur_h = left_edge + x;
899                    i += 1;
900                    rtl
901                }
902                None => false,
903            };
904            if w.sync {
905                self.sync_hlist_node(p, cur_h, base_line);
906            }
907            let n = self.nodes.get(p);
908            let n_id = n.id;
909            match &n.data {
910                NodeData::Glyph(g) => {
911                    let (font, chr, xo, yo, ef) =
912                        (g.font, g.chr, g.xoffset, g.yoffset, g.expansion_factor);
913                    if self.fonts.char_metrics(font, chr).is_none() {
914                        self.shipout_lost_char(font, chr);
915                    }
916                    // In right-to-left text the offset goes leftwards.
917                    let x = if rtl { cur_h - xo } else { cur_h + xo };
918                    self.out_glyph(w, font, chr, x, base_line - yo, ef);
919                    cur_h +=
920                        crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
921                }
922                NodeData::Box(inner) => {
923                    let (iw, shift, head) = (inner.width, inner.shift, inner.head);
924                    if !head.is_null() {
925                        self.out_box(w, p, cur_h, base_line + shift);
926                    } else if w.sync {
927                        let size = [iw, inner.height, inner.depth];
928                        let f = self.sync_of(p);
929                        self.synctex
930                            .void_box(n_id == id::HLIST, f, cur_h, base_line, size);
931                    }
932                    cur_h += iw;
933                }
934                NodeData::Rule {
935                    width,
936                    depth,
937                    height,
938                    index,
939                    ..
940                } => {
941                    let (wd, ht, dpt, idx) = (*width, *height, *depth, *index);
942                    if n.subtype == crate::image::IMAGE_RULE {
943                        self.out_image(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
944                    } else if n.subtype == crate::image::BOX_RESOURCE_RULE {
945                        self.out_box_resource(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
946                    } else {
947                        Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, &b);
948                    }
949                    cur_h += wd;
950                    if w.sync {
951                        let f = self.sync_of(p);
952                        self.synctex.rule(f, cur_h, base_line, [wd, ht, dpt]);
953                    }
954                }
955                NodeData::Glue { spec, leader } => {
956                    let (spec, leader, sub) = (*spec, *leader, n.subtype);
957                    let wd = gs.advance(&spec);
958                    let box_leaders = sub >= glue_sub::A_LEADERS
959                        && !leader.is_null()
960                        && self.nodes.id(leader) != id::RULE;
961                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
962                        self.hlist_leaders(w, leader, sub, cur_h, left_edge, base_line, wd, &b);
963                    }
964                    cur_h += wd;
965                    if w.sync && !box_leaders {
966                        let f = self.sync_of(p);
967                        self.synctex.glue(f, cur_h, base_line);
968                    }
969                }
970                NodeData::Kern {
971                    kern,
972                    expansion_factor,
973                } => {
974                    let wd = kern + expansion_factor;
975                    if w.sync {
976                        // Observed: the width recorded is the kern's,
977                        // without font expansion.
978                        let f = self.sync_of(p);
979                        self.synctex.kern(f, *kern);
980                    }
981                    cur_h += wd;
982                }
983                NodeData::Math { surround, glue } => {
984                    let g = *glue;
985                    if w.sync {
986                        let f = self.sync_of(p);
987                        self.synctex.math(f, cur_h, base_line);
988                    }
989                    cur_h += surround;
990                    if g != Glue::ZERO {
991                        cur_h += gs.advance(&g);
992                    }
993                }
994                NodeData::MarginKern { width, .. } => cur_h += width,
995                NodeData::Whatsit(_) => {
996                    self.out_what(w, p, cur_h, base_line, &ctx);
997                    // A `\latelua` function can insert nodes after it.
998                    p = self.nodes.next(p);
999                    continue;
1000                }
1001                NodeData::Disc { replace, .. } => {
1002                    let replace = *replace;
1003                    // A discretionary that was not broken shows its
1004                    // replacement text (from its right end in
1005                    // right-to-left text, as other nodes there).
1006                    let mut items = Vec::new();
1007                    let mut q = replace;
1008                    while !q.is_null() {
1009                        items.push(q);
1010                        q = self.nodes.next(q);
1011                    }
1012                    let start = cur_h;
1013                    let total: i32 = items.iter().map(|&q| self.replace_item_width(q)).sum();
1014                    for q in items {
1015                        if rtl {
1016                            // Placed at its left edge, from the right end.
1017                            cur_h = start + total
1018                                - items_before_width(self, replace, q)
1019                                - self.replace_item_width(q);
1020                        }
1021                        match &self.nodes.get(q).data {
1022                            NodeData::Glyph(g) => {
1023                                let (font, chr, ef) = (g.font, g.chr, g.expansion_factor);
1024                                self.out_glyph(w, font, chr, cur_h, base_line, ef);
1025                                if w.sync {
1026                                    self.synctex.glyph();
1027                                }
1028                                cur_h += crate::expansion::expanded_width(
1029                                    self.font_char_dimen(font, chr, 0),
1030                                    ef,
1031                                );
1032                            }
1033                            NodeData::Kern {
1034                                kern,
1035                                expansion_factor,
1036                            } => {
1037                                let wd = kern + expansion_factor;
1038                                if w.sync {
1039                                    self.synctex.end_run(cur_h, base_line);
1040                                    let f = self.sync_of(q);
1041                                    self.synctex.kern(f, *kern);
1042                                }
1043                                cur_h += wd;
1044                            }
1045                            // Boxes and rules, as in the list (crop's
1046                            // `\discretionary{}{}{\hbox{---}}`).
1047                            NodeData::Box(inner) => {
1048                                let (iw, shift, head) = (inner.width, inner.shift, inner.head);
1049                                if !head.is_null() {
1050                                    self.out_box(w, q, cur_h, base_line + shift);
1051                                }
1052                                cur_h += iw;
1053                            }
1054                            NodeData::Rule {
1055                                width,
1056                                depth,
1057                                height,
1058                                ..
1059                            } => {
1060                                let (wd, ht, dpt) = (*width, *height, *depth);
1061                                Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, &b);
1062                                cur_h += wd;
1063                            }
1064                            _ => {}
1065                        }
1066                    }
1067                }
1068                _ => {}
1069            }
1070            p = next;
1071        }
1072        if places.is_some() {
1073            cur_h = left_edge + b.width;
1074        }
1075        if !w.doing_leaders {
1076            self.ship_end_box_links(w, &ctx);
1077        }
1078        if w.sync {
1079            self.synctex.end_run(cur_h, base_line);
1080            let f = self.sync_of(this_box);
1081            self.synctex.box_close(true, f, cur_h, base_line);
1082        }
1083        Self::leave_box(w);
1084    }
1085
1086    /// The width a node of an unbroken discretionary's replacement adds.
1087    fn replace_item_width(&self, q: NodeRef) -> i32 {
1088        match &self.nodes.get(q).data {
1089            NodeData::Glyph(g) => crate::expansion::expanded_width(
1090                self.font_char_dimen(g.font, g.chr, 0),
1091                g.expansion_factor,
1092            ),
1093            NodeData::Kern {
1094                kern,
1095                expansion_factor,
1096            } => kern + expansion_factor,
1097            NodeData::Box(b) => b.width,
1098            NodeData::Rule { width, .. } => *width,
1099            _ => 0,
1100        }
1101    }
1102
1103    /// The horizontal room each node of an hlist takes when output, in
1104    /// list order (glue as set, with TeX's rounding in that order).
1105    fn hlist_widths(&self, head: NodeRef, gs: &GlueState) -> Vec<i32> {
1106        let mut gs = gs.clone();
1107        let mut out = Vec::new();
1108        let mut p = head;
1109        while !p.is_null() {
1110            let n = self.nodes.get(p);
1111            let w = match &n.data {
1112                NodeData::Glyph(g) => crate::expansion::expanded_width(
1113                    self.font_char_dimen(g.font, g.chr, 0),
1114                    g.expansion_factor,
1115                ),
1116                NodeData::Box(inner) => inner.width,
1117                NodeData::Rule { width, .. } => *width,
1118                NodeData::Glue { spec, .. } => gs.advance(spec),
1119                NodeData::Kern {
1120                    kern,
1121                    expansion_factor,
1122                } => kern + expansion_factor,
1123                NodeData::Math { surround, glue } => {
1124                    surround
1125                        + if *glue != Glue::ZERO {
1126                            gs.advance(glue)
1127                        } else {
1128                            0
1129                        }
1130                }
1131                NodeData::MarginKern { width, .. } => *width,
1132                NodeData::Disc { replace, .. } => {
1133                    let mut q = *replace;
1134                    let mut w = 0;
1135                    while !q.is_null() {
1136                        w += self.replace_item_width(q);
1137                        q = self.nodes.next(q);
1138                    }
1139                    w
1140                }
1141                _ => 0,
1142            };
1143            out.push(w);
1144            p = n.next;
1145        }
1146        out
1147    }
1148
1149    /// Where the nodes of an hlist go, in list order: (left edge from the
1150    /// box's left edge, whether in right-to-left text), or `None` for a
1151    /// left-to-right box without direction nodes. As LuaTeX (observed in
1152    /// its PDF output): direction nodes delimit segments; a left-to-right
1153    /// segment places its nodes from its left end rightwards, a
1154    /// right-to-left one (a `TRT` box, or text after `\begindir TRT`) from
1155    /// its right end leftwards, both in list order, a nested segment
1156    /// taking the room of its width.
1157    fn rtl_places(&self, head: NodeRef, box_rtl: bool, gs: &GlueState) -> Option<Vec<(i32, bool)>> {
1158        let mut has_dirs = false;
1159        let mut p = head;
1160        while !p.is_null() {
1161            if self.nodes.id(p) == id::DIR {
1162                has_dirs = true;
1163                break;
1164            }
1165            p = self.nodes.next(p);
1166        }
1167        if !box_rtl && !has_dirs {
1168            return None;
1169        }
1170        let widths = self.hlist_widths(head, gs);
1171        enum Item {
1172            Node(usize),
1173            Seg(bool, Vec<Item>),
1174        }
1175        let mut stack: Vec<(bool, Vec<Item>)> = vec![(box_rtl, Vec::new())];
1176        let close = |stack: &mut Vec<(bool, Vec<Item>)>| {
1177            let (rtl, items) = stack.pop().expect("open segment");
1178            stack
1179                .last_mut()
1180                .expect("outer segment")
1181                .1
1182                .push(Item::Seg(rtl, items));
1183        };
1184        let mut p = head;
1185        let mut k = 0;
1186        while !p.is_null() {
1187            if let NodeData::Dir { dir, .. } = self.nodes.data(p) {
1188                let begin = self.nodes.subtype(p) != crate::dirs::dir_sub::END;
1189                if !begin && stack.len() > 1 {
1190                    // The direction node belongs to the segment it ends.
1191                    stack.last_mut().expect("segment").1.push(Item::Node(k));
1192                    close(&mut stack);
1193                } else if begin {
1194                    stack.push((*dir == 1, vec![Item::Node(k)]));
1195                } else {
1196                    stack.last_mut().expect("segment").1.push(Item::Node(k));
1197                }
1198            } else {
1199                stack.last_mut().expect("segment").1.push(Item::Node(k));
1200            }
1201            k += 1;
1202            p = self.nodes.next(p);
1203        }
1204        while stack.len() > 1 {
1205            close(&mut stack);
1206        }
1207        fn width(items: &[Item], widths: &[i32]) -> i32 {
1208            items
1209                .iter()
1210                .map(|it| match it {
1211                    Item::Node(i) => widths[*i],
1212                    Item::Seg(_, sub) => width(sub, widths),
1213                })
1214                .sum()
1215        }
1216        fn place(rtl: bool, items: &[Item], left: i32, widths: &[i32], out: &mut [(i32, bool)]) {
1217            let mut x = if rtl {
1218                left + width(items, widths)
1219            } else {
1220                left
1221            };
1222            for it in items {
1223                match it {
1224                    Item::Node(i) => {
1225                        if rtl {
1226                            x -= widths[*i];
1227                            out[*i] = (x, true);
1228                        } else {
1229                            out[*i] = (x, false);
1230                            x += widths[*i];
1231                        }
1232                    }
1233                    Item::Seg(r, sub) => {
1234                        let w = width(sub, widths);
1235                        if rtl {
1236                            x -= w;
1237                            place(*r, sub, x, widths, out);
1238                        } else {
1239                            place(*r, sub, x, widths, out);
1240                            x += w;
1241                        }
1242                    }
1243                }
1244            }
1245        }
1246        let (rtl, items) = stack.pop().expect("box segment");
1247        let mut out = vec![(0, false); widths.len()];
1248        place(rtl, &items, 0, &widths, &mut out);
1249        Some(out)
1250    }
1251
1252    /// SyncTeX before node `p` of an hlist: a glyph continues a run of
1253    /// glyphs, the nodes that do not end one leave it, others end it.
1254    #[inline]
1255    fn sync_hlist_node(&mut self, p: NodeRef, cur_h: i32, base_line: i32) {
1256        match self.nodes.id(p) {
1257            id::GLYPH => self.synctex.glyph(),
1258            id::MARK | id::INS | id::ADJUST | id::BOUNDARY | id::LOCAL_PAR => {}
1259            _ => self.synctex.end_run(cur_h, base_line),
1260        }
1261    }
1262
1263    /// A rule in an hlist whose left end is at `cur_h` (TeX's `fin_rule`
1264    /// in §624); running dimensions come from the enclosing box.
1265    fn hlist_rule(
1266        w: &mut Walker,
1267        cur_h: i32,
1268        base_line: i32,
1269        wd: i32,
1270        ht: i32,
1271        dpt: i32,
1272        this_box: &crate::nodes::BoxData,
1273    ) {
1274        let ht = if ht == RUNNING { this_box.height } else { ht };
1275        let dpt = if dpt == RUNNING { this_box.depth } else { dpt };
1276        let total = ht + dpt;
1277        if total > 0 && wd > 0 {
1278            Self::out_rule(w, cur_h, base_line + dpt, wd, total);
1279        }
1280    }
1281
1282    /// Outputs a vlist box (`vlist_out`).
1283    fn vlist_out(&mut self, w: &mut Walker, this_box: NodeRef, cur_h: i32, cur_v: i32) {
1284        let Some(b) = self.nodes.box_data(this_box).cloned() else {
1285            return;
1286        };
1287        let mut gs = GlueState {
1288            cur_g: 0,
1289            cur_glue: 0.0,
1290            order: b.glue_order,
1291            sign: b.glue_sign,
1292            set: b.glue_set,
1293        };
1294        Self::enter_box(w);
1295        let left_edge = cur_h;
1296        let ctx = crate::pdfship::BoxCtx {
1297            left: left_edge,
1298            base: cur_v,
1299            width: b.width,
1300            height: b.height,
1301            depth: b.depth,
1302        };
1303        let mut cur_v = cur_v - b.height;
1304        let top_edge = cur_v;
1305        if w.sync {
1306            let f = self.sync_of(this_box);
1307            self.synctex
1308                .box_open(false, f, cur_h, cur_v, [b.width, b.height, b.depth]);
1309        }
1310        let mut p = b.head;
1311        while !p.is_null() {
1312            let next = self.nodes.next(p);
1313            let n = self.nodes.get(p);
1314            match &n.data {
1315                NodeData::Box(inner) => {
1316                    let (ih, idp, shift, head) =
1317                        (inner.height, inner.depth, inner.shift, inner.head);
1318                    if head.is_null() {
1319                        cur_v += ih + idp;
1320                        if w.sync {
1321                            let size = [inner.width, ih, idp];
1322                            let hlist = n.id == id::HLIST;
1323                            let f = self.sync_of(p);
1324                            self.synctex.void_box(hlist, f, left_edge, cur_v, size);
1325                        }
1326                    } else {
1327                        cur_v += ih;
1328                        self.out_box(w, p, left_edge + shift, cur_v);
1329                        cur_v += idp;
1330                    }
1331                }
1332                NodeData::Rule {
1333                    width,
1334                    depth,
1335                    height,
1336                    index,
1337                    ..
1338                } => {
1339                    let wd = if *width == RUNNING { b.width } else { *width };
1340                    let total = height + depth;
1341                    let idx = *index;
1342                    cur_v += total;
1343                    if n.subtype == crate::image::IMAGE_RULE {
1344                        self.out_image(w, idx, left_edge, cur_v, wd, total);
1345                    } else if n.subtype == crate::image::BOX_RESOURCE_RULE {
1346                        self.out_box_resource(w, idx, left_edge, cur_v, wd, total);
1347                    } else if total > 0 && wd > 0 {
1348                        Self::out_rule(w, left_edge, cur_v, wd, total);
1349                    }
1350                }
1351                NodeData::Glue { spec, leader } => {
1352                    let (spec, leader, sub) = (*spec, *leader, n.subtype);
1353                    let ht = gs.advance(&spec);
1354                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
1355                        cur_v =
1356                            self.vlist_leaders(w, leader, sub, cur_v, left_edge, top_edge, ht, &b);
1357                    } else {
1358                        cur_v += ht;
1359                    }
1360                }
1361                NodeData::Kern { kern, .. } => cur_v += kern,
1362                NodeData::Whatsit(_) => {
1363                    self.out_what(w, p, left_edge, cur_v, &ctx);
1364                    // A `\latelua` function can insert nodes after it.
1365                    p = self.nodes.next(p);
1366                    continue;
1367                }
1368                _ => {}
1369            }
1370            p = next;
1371        }
1372        if w.sync {
1373            let f = self.sync_of(this_box);
1374            self.synctex.box_close(false, f, left_edge, cur_v);
1375        }
1376        Self::leave_box(w);
1377    }
1378
1379    /// Where leaders of the given kind start and how much space follows
1380    /// each copy (§626, §635): returns (start, gap) for a space of size
1381    /// `size` at `cur` filled with boxes of size `step`.
1382    fn leader_start(sub: u16, cur: i32, edge0: i32, size: i32, step: i32) -> (i32, i32) {
1383        if sub == glue_sub::C_LEADERS || sub == glue_sub::X_LEADERS {
1384            let lq = size / step;
1385            let lr = size % step;
1386            if sub == glue_sub::C_LEADERS {
1387                (cur + lr / 2, 0)
1388            } else {
1389                let lx = lr / (lq + 1);
1390                (cur + (lr - (lq - 1) * lx) / 2, lx)
1391            }
1392        } else {
1393            let mut c = edge0 + step * ((cur - edge0) / step);
1394            if c < cur {
1395                c += step;
1396            }
1397            (c, 0)
1398        }
1399    }
1400
1401    /// Leaders in an hlist (§625–§628) filling `wd` from `cur_h`.
1402    #[allow(clippy::too_many_arguments)]
1403    fn hlist_leaders(
1404        &mut self,
1405        w: &mut Walker,
1406        leader: NodeRef,
1407        sub: u16,
1408        cur_h: i32,
1409        left_edge: i32,
1410        base_line: i32,
1411        wd: i32,
1412        this_box: &crate::nodes::BoxData,
1413    ) {
1414        let lb = match &self.nodes.get(leader).data {
1415            NodeData::Rule { height, depth, .. } => {
1416                let (ht, dpt) = (*height, *depth);
1417                Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, this_box);
1418                return;
1419            }
1420            NodeData::Box(lb) => lb.clone(),
1421            _ => return,
1422        };
1423        let leader_wd = lb.width;
1424        if leader_wd <= 0 || wd <= 0 {
1425            return;
1426        }
1427        let rule_wd = wd + 10; // compensates for floating-point rounding
1428        let edge = cur_h + rule_wd;
1429        let (mut h, lx) = Self::leader_start(sub, cur_h, left_edge, rule_wd, leader_wd);
1430        // The box is output even if it is empty, which counts towards the
1431        // DVI stack depth (§628).
1432        while h + leader_wd <= edge {
1433            let outer = w.doing_leaders;
1434            w.doing_leaders = true;
1435            self.out_box(w, leader, h, base_line + lb.shift);
1436            w.doing_leaders = outer;
1437            h += leader_wd + lx;
1438        }
1439    }
1440
1441    /// Leaders in a vlist (§634–§637) filling `ht` below `cur_v`; returns
1442    /// the new `cur_v`.
1443    #[allow(clippy::too_many_arguments)]
1444    fn vlist_leaders(
1445        &mut self,
1446        w: &mut Walker,
1447        leader: NodeRef,
1448        sub: u16,
1449        cur_v: i32,
1450        left_edge: i32,
1451        top_edge: i32,
1452        ht: i32,
1453        this_box: &crate::nodes::BoxData,
1454    ) -> i32 {
1455        let lb = match &self.nodes.get(leader).data {
1456            NodeData::Rule { width, .. } => {
1457                let wd = if *width == RUNNING {
1458                    this_box.width
1459                } else {
1460                    *width
1461                };
1462                if ht > 0 && wd > 0 {
1463                    Self::out_rule(w, left_edge, cur_v + ht, wd, ht);
1464                }
1465                return cur_v + ht;
1466            }
1467            NodeData::Box(lb) => lb.clone(),
1468            _ => return cur_v + ht,
1469        };
1470        let leader_ht = lb.height + lb.depth;
1471        if leader_ht <= 0 || ht <= 0 {
1472            return cur_v + ht;
1473        }
1474        let rule_ht = ht + 10; // compensates for floating-point rounding
1475        let edge = cur_v + rule_ht;
1476        let (mut v, lx) = Self::leader_start(sub, cur_v, top_edge, rule_ht, leader_ht);
1477        while v + leader_ht <= edge {
1478            let outer = w.doing_leaders;
1479            w.doing_leaders = true;
1480            self.out_box(w, leader, left_edge + lb.shift, v + lb.height);
1481            w.doing_leaders = outer;
1482            v += leader_ht + lx;
1483        }
1484        edge - 10
1485    }
1486
1487    /// Executes a whatsit at TeX position (`h`, `v`) (`out_what`).
1488    fn out_what(
1489        &mut self,
1490        w: &mut Walker,
1491        p: NodeRef,
1492        h: i32,
1493        v: i32,
1494        ctx: &crate::pdfship::BoxCtx,
1495    ) {
1496        let NodeData::Whatsit(wh) = &self.nodes.get(p).data else {
1497            return;
1498        };
1499        let (x, y) = w.pos(h, v);
1500        match (**wh).clone() {
1501            Whatsit::Open { stream, name } if !w.doing_leaders => {
1502                self.open_write_stream(stream as i32, &name)
1503            }
1504            Whatsit::Write { stream, data } if !w.doing_leaders => self.write_out(stream, &data),
1505            Whatsit::Close { stream } if !w.doing_leaders => self.close_write_stream(stream as i32),
1506            Whatsit::Special { data } => self.out_special(w, h, v, &data),
1507            Whatsit::PdfLiteral { mode, data, .. } => {
1508                let bytes = match data {
1509                    crate::nodes::LateData::Bytes(b)
1510                    | crate::nodes::LateData::Lua { data: b, .. } => b,
1511                    crate::nodes::LateData::Function { .. } => Vec::new(),
1512                    crate::nodes::LateData::Tokens(t) => {
1513                        let toks = self.expand_late_tokens(&t);
1514                        self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
1515                    }
1516                };
1517                if let Some(be) = w.backend.as_deref_mut() {
1518                    be.literal(mode, &bytes, x, y);
1519                }
1520            }
1521            Whatsit::LateLua { data, .. } => {
1522                let code = match data {
1523                    crate::nodes::LateData::Bytes(b)
1524                    | crate::nodes::LateData::Lua { data: b, .. } => Ok(b),
1525                    crate::nodes::LateData::Tokens(t) => {
1526                        let toks = self.expand_late_tokens(&t);
1527                        Ok(self.lua_code(&toks))
1528                    }
1529                    crate::nodes::LateData::Function { reference } => Err(reference),
1530                };
1531                // `pdf.getpos`, `pdf.print` and `pdf.registerannot` work on
1532                // this context while the code runs.
1533                self.pdf.pos = (x, y);
1534                let outer = self.pdf.late.replace(Default::default());
1535                match code {
1536                    Ok(code) => self.run_lua(&code),
1537                    // Observed: a function is called with the whatsit;
1538                    // the nodes it inserts after it are output next.
1539                    Err(r) => {
1540                        if let Some(host) = self.lua.clone()
1541                            && let Err(e) =
1542                                host.call_reference(self, r, crate::ReferenceArgs::Node(p))
1543                        {
1544                            self.lua_error(&e);
1545                        }
1546                    }
1547                }
1548                let ctx = std::mem::replace(&mut self.pdf.late, outer).unwrap_or_default();
1549                if let Some(be) = w.backend.as_deref_mut() {
1550                    for (mode, text) in &ctx.prints {
1551                        be.print(*mode, text, x, y);
1552                    }
1553                }
1554                self.pdf
1555                    .page
1556                    .annots
1557                    .extend(ctx.annots.into_iter().map(|a| (a, None)));
1558            }
1559            Whatsit::PdfRefobj { objnum } if !w.doing_leaders => self.ship_refobj(w, objnum),
1560            Whatsit::PdfAnnot { dims, objnum, data } if !w.doing_leaders => {
1561                self.ship_annot(w, ctx, h, v, dims, objnum, &data)
1562            }
1563            Whatsit::PdfStartLink {
1564                dims,
1565                objnum,
1566                attr,
1567                action,
1568            } if !w.doing_leaders => {
1569                self.ship_start_link(w, ctx, h, v, dims, objnum, &attr, &action)
1570            }
1571            Whatsit::PdfEndLink if !w.doing_leaders => self.ship_end_link(w, h, v),
1572            Whatsit::PdfDest {
1573                id,
1574                view,
1575                structure,
1576            } if !w.doing_leaders => self.ship_dest(w, ctx, h, v, &id, &view, structure),
1577            Whatsit::SavePos => {
1578                self.last_x_pos = x;
1579                self.last_y_pos = y;
1580            }
1581            Whatsit::PdfSave => {
1582                if let Some(be) = w.backend.as_deref_mut() {
1583                    be.save(x, y);
1584                }
1585            }
1586            Whatsit::PdfRestore => {
1587                if let Some(be) = w.backend.as_deref_mut() {
1588                    be.restore(x, y);
1589                }
1590            }
1591            Whatsit::PdfSetmatrix { data } => {
1592                if let Some(be) = w.backend.as_deref_mut() {
1593                    be.set_matrix(x, y, &data);
1594                }
1595            }
1596            Whatsit::PdfColorstack { stack, cmd, data } => {
1597                let out = self.colorstack_op(stack, cmd, data);
1598                if let (Some((mode, lit)), Some(be)) = (out, w.backend.as_deref_mut()) {
1599                    be.literal(mode, &lit, x, y);
1600                }
1601            }
1602            _ => {}
1603        }
1604    }
1605
1606    /// Outputs a `\special` at TeX position (`h`, `v`).
1607    fn out_special(&mut self, w: &mut Walker, h: i32, v: i32, data: &[u8]) {
1608        let (x, y) = w.pos(h, v);
1609        if let Some(d) = w.dvi.as_mut() {
1610            d.special(w.dvi_h + h, w.dvi_v + v, data);
1611        } else if let Some(be) = w.backend.as_deref_mut() {
1612            // Observed in PDF mode: `pdf:` specials (any case) are
1613            // literals, with an optional mode keyword; every other
1614            // special is dropped with a warning.
1615            if data.len() >= 4 && data[..4].eq_ignore_ascii_case(b"pdf:") {
1616                let rest = &data[4..];
1617                let (mode, text) = [
1618                    (&b"direct:"[..], 2),
1619                    (b"page:", 1),
1620                    (b"raw:", 3),
1621                    (b"text:", 4),
1622                    (b"origin:", 0),
1623                ]
1624                .iter()
1625                .find_map(|(k, m)| rest.strip_prefix(*k).map(|t| (*m, t)))
1626                .unwrap_or((crate::pdfext::literal_mode::ORIGIN, rest));
1627                be.literal(mode, text, x, y);
1628            } else {
1629                self.printer.print_nl("Non-PDF special ignored!");
1630            }
1631        }
1632    }
1633
1634    /// Applies a colour-stack command; returns the literal to emit.
1635    fn colorstack_op(&mut self, stack: i32, cmd: u8, data: Vec<u8>) -> Option<(u8, Vec<u8>)> {
1636        use crate::pdfext::colorstack_cmd as c;
1637        self.pdf_ensure_colorstacks();
1638        let def = self.pdf.colorstacks.get(stack.max(0) as usize).cloned()?;
1639        let st = self
1640            .colorstack_values
1641            .entry(stack)
1642            .or_insert_with(|| vec![def.init.clone()]);
1643        match cmd {
1644            c::SET => {
1645                if let Some(top) = st.last_mut() {
1646                    *top = data;
1647                } else {
1648                    st.push(data);
1649                }
1650            }
1651            c::PUSH => st.push(data),
1652            c::POP => {
1653                if st.len() > 1 {
1654                    st.pop();
1655                }
1656            }
1657            _ => {}
1658        }
1659        let lit = st.last().cloned().unwrap_or_default();
1660        Some((def.mode, lit))
1661    }
1662
1663    /// Expands a late token list (as `\write` does) and returns the
1664    /// expanded tokens.
1665    fn expand_late_tokens(&mut self, list: &crate::token::TokenList) -> Vec<crate::token::Token> {
1666        use crate::token::Token;
1667        use std::rc::Rc;
1668        self.ins_list(Rc::from(vec![
1669            Token::RIGHT_BRACE,
1670            Token::cs(self.cs.end_write),
1671        ]));
1672        self.begin_token_list(list.clone(), crate::input::TokKind::WriteText);
1673        self.ins_list(Rc::from(vec![Token::LEFT_BRACE]));
1674        let toks = self.scan_toks(false, true);
1675        self.get_token();
1676        toks
1677    }
1678
1679    /// Finishes the output file at the end of the run and reports it.
1680    pub fn finish_backend(&mut self) {
1681        if let Some(f) = self.final_hook {
1682            f(self);
1683        }
1684        if self.dvi.pages() > 0 {
1685            self.finish_dvi();
1686        } else if self.pdf.output_opened && self.eqtb.int(ip::OUTPUTMODE) <= 0 {
1687            // LuaTeX opened the DVI file for `pdf.obj` and leaves it empty.
1688            let _ = std::fs::write(self.output_path("dvi"), b"");
1689        }
1690        let Some(mut be) = self.backend.take() else {
1691            return;
1692        };
1693        if self.pdf.fatal || self.total_pages == 0 {
1694            be.abandon(self.pdf.fatal);
1695            self.backend = Some(be);
1696            return;
1697        }
1698        if self.eqtb.int(ip::DRAFTMODE) > 0 && self.eqtb.int(ip::OUTPUTMODE) > 0 {
1699            // `-draftmode`: the document is typeset but no PDF is written
1700            // (the backend wrote to a scratch file, removed here). LuaTeX
1701            // prints this warning; unlike LuaTeX, which empties an
1702            // existing PDF file, retex leaves it as it is.
1703            be.abandon(true);
1704            self.backend = Some(be);
1705            self.printer
1706                .print_nl("warning  (pdf backend): draftmode enabled, not changing output pdf");
1707            self.printer.print_ln();
1708            return;
1709        }
1710        if self.total_pages > 0 {
1711            self.pdf_finish_document();
1712            self.flush_pending_objects(be.as_mut());
1713        }
1714        if self.total_pages > 0
1715            && let Some(fin) = be.finish(self)
1716        {
1717            // Observed: printed one after the other like C strings (log
1718            // lines up to 80 characters), right after what came before.
1719            for f in &fin.embedded {
1720                self.printer.print_lua_bytes(f.as_bytes());
1721            }
1722            self.pdf.objstm_stats = fin.objstm;
1723            self.print_output_written(&fin.file, self.total_pages, fin.size);
1724        }
1725        self.backend = Some(be);
1726    }
1727
1728    /// The path of output file `<jobname>.<ext>` (in `-output-directory`).
1729    pub(crate) fn output_path(&self, ext: &str) -> std::path::PathBuf {
1730        let job =
1731            String::from_utf8_lossy(self.job_name.as_deref().unwrap_or(b"texput")).into_owned();
1732        let name = format!("{job}.{ext}");
1733        match &self.files.output_dir {
1734            Some(d) => d.join(&name),
1735            None => std::path::PathBuf::from(&name),
1736        }
1737    }
1738
1739    /// Writes the postamble and the DVI file `<jobname>.dvi` (§642).
1740    fn finish_dvi(&mut self) {
1741        let mut dvi = std::mem::take(&mut self.dvi);
1742        let pages = dvi.pages();
1743        let data = dvi.finish(self);
1744        let path = self.output_path("dvi");
1745        let name = path
1746            .file_name()
1747            .map(|n| n.to_string_lossy().into_owned())
1748            .unwrap_or_default();
1749        if std::fs::write(&path, &data).is_err() {
1750            self.printer.print_nl("! I can't write on file `");
1751            self.printer.print_bytes(name.as_bytes());
1752            self.printer.print_str("'.");
1753            return;
1754        }
1755        self.print_output_written(&name, pages, data.len() as u64);
1756    }
1757
1758    /// Prints `Output written on NAME (N pages, B bytes).`.
1759    fn print_output_written(&mut self, name: &str, pages: u32, size: u64) {
1760        self.output_written = Some((name.to_owned(), pages, size));
1761        self.printer.print_nl("Output written on ");
1762        self.printer.print_bytes(name.as_bytes());
1763        self.printer.print_str(" (");
1764        self.printer.print_int(i64::from(pages));
1765        self.printer
1766            .print_str(if pages == 1 { " page" } else { " pages" });
1767        self.printer.print_str(", ");
1768        self.printer.print_int(size as i64);
1769        self.printer.print_str(" bytes).");
1770    }
1771}
1772
1773/// The state of a page being shipped: the outputs and the coordinate
1774/// systems they use.
1775#[derive(Default)]
1776pub(crate) struct Walker {
1777    /// The PDF backend (PDF mode).
1778    backend: Option<Box<dyn Backend>>,
1779    /// The DVI writer (DVI mode).
1780    dvi: Option<crate::dvi::DviWriter>,
1781    h_origin: i32,
1782    v_origin: i32,
1783    page_height: i32,
1784    /// The DVI position of TeX's origin (`\hoffset`, `\voffset`).
1785    dvi_h: i32,
1786    dvi_v: i32,
1787    /// Box nesting depth (`cur_s`): -1 outside the page box.
1788    pub(crate) cur_s: i32,
1789    /// Whether a leader box is being output (`doing_leaders`).
1790    doing_leaders: bool,
1791    /// Whether this is a page (not a box resource): annotations, links
1792    /// and destinations belong to pages.
1793    pub(crate) page: bool,
1794    /// Whether SyncTeX records this sheet.
1795    sync: bool,
1796}
1797
1798impl Walker {
1799    /// Page coordinates (from the lower left, y up) of TeX point
1800    /// (`h`, `v`) measured from the origin, v downwards.
1801    pub(crate) fn pos(&self, h: i32, v: i32) -> (i32, i32) {
1802        (self.h_origin + h, self.page_height - (self.v_origin + v))
1803    }
1804}
1805
1806/// The state of a virtual character's packet being run.
1807#[derive(Clone)]
1808pub(crate) struct PacketState {
1809    /// The current point.
1810    h: i32,
1811    v: i32,
1812    /// The current font.
1813    font: u32,
1814    /// The points saved by `push`.
1815    stack: Vec<(i32, i32)>,
1816    /// The expansion factor of the virtual character.
1817    ef: i32,
1818    /// How deeply virtual characters are nested.
1819    depth: u32,
1820    /// The virtual font, its id and the character.
1821    vf: std::rc::Rc<crate::fonts::VirtualFont>,
1822    vfont: u32,
1823    chr: u32,
1824}
1825
1826/// A packet whose `lua` command is running, with the output it draws
1827/// into.
1828pub(crate) struct VfFrame {
1829    walker: Walker,
1830    state: PacketState,
1831}