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