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    /// Gives up the output file: with `remove`, it is deleted (after a
56    /// fatal error), otherwise left empty (no pages), as LuaTeX does.
57    fn abandon(&mut self, _remove: bool) {}
58
59    /// Prepares for writing an object whose text is still to be made
60    /// (allocates the object stream it will go into, as LuaTeX does).
61    fn begin_object(&mut self, _engine: &Engine) {}
62
63    /// The object number of the page begun last.
64    fn current_page_object(&self) -> Option<i32> {
65        None
66    }
67
68    /// Writes the page's contents and dictionary: the page attributes and
69    /// resources of `items` are final, and whether there are annotations.
70    fn close_page_contents(&mut self, _engine: &Engine, _items: &crate::pdfext::PageItems) {}
71
72    /// Hands over the page's annotations, links, destinations and
73    /// referenced objects, just before [`end_page`](Self::end_page).
74    fn page_items(&mut self, _engine: &Engine, _items: &crate::pdfext::PageItems) {}
75
76    /// Starts writing box resource `index` (natural size `size` = width,
77    /// height, depth) as a form XObject; drawing goes into the form until
78    /// [`end_box_form`](Self::end_box_form).
79    fn begin_box_form(
80        &mut self,
81        _engine: &Engine,
82        _index: u32,
83        _size: (i32, i32, i32),
84        _attr: &[u8],
85        _resources: &[u8],
86    ) {
87    }
88
89    /// Finishes the box resource being written (its content stream).
90    fn end_box_form(&mut self, _engine: &Engine) {}
91
92    /// Writes the resources of the box resource finished last, after the
93    /// forms it uses.
94    fn close_box_form_resources(&mut self, _engine: &Engine) {}
95
96    /// Writes the resources of the page ended last, after the forms it
97    /// uses.
98    fn close_page_resources(&mut self, _engine: &Engine) {}
99
100    /// Draws box resource `index` with its lower-left corner at (x, y),
101    /// scaled to `width` × `height` (height + depth).
102    fn box_form(
103        &mut self,
104        _engine: &Engine,
105        _index: u32,
106        _x: i32,
107        _y: i32,
108        _width: i32,
109        _height: i32,
110    ) {
111    }
112
113    /// Writes image resource `index` (its object number) now (`\immediate`).
114    fn write_image(&mut self, _engine: &Engine, _index: u32) {}
115
116    /// Draws image resource `index` with its lower-left corner at (x, y),
117    /// `width` wide and `height` (height + depth) tall.
118    fn image(
119        &mut self,
120        _engine: &Engine,
121        _index: u32,
122        _x: i32,
123        _y: i32,
124        _width: i32,
125        _height: i32,
126    ) {
127    }
128
129    /// Writes an object created with `\pdfextension obj`.
130    fn write_object(&mut self, _engine: &Engine, _obj: &crate::pdfext::PdfObject) {}
131}
132
133/// What the backend reports when the document is finished.
134#[derive(Clone, Debug, Default)]
135pub struct Finished {
136    /// The output file name as printed in "Output written on …".
137    pub file: String,
138    /// Its size in bytes.
139    pub size: u64,
140    /// Font files embedded, printed as `<path>` (LuaTeX order).
141    pub embedded: Vec<String>,
142    /// Objects written into object streams, and the number of streams.
143    pub objstm: (u32, u32),
144}
145
146/// The glue-rounding state of a box being shipped (`cur_g`, `cur_glue`).
147struct GlueState {
148    cur_g: i32,
149    cur_glue: f64,
150    order: u8,
151    sign: u8,
152    set: f64,
153}
154
155impl GlueState {
156    /// TeX's glue computation for one glue node: returns its width.
157    fn advance(&mut self, g: &Glue) -> i32 {
158        let mut w = g.width - self.cur_g;
159        if self.sign != 0 {
160            let applies = if self.sign == 1 {
161                g.stretch_order == self.order
162            } else {
163                g.shrink_order == self.order
164            };
165            if applies {
166                if self.sign == 1 {
167                    self.cur_glue += f64::from(g.stretch);
168                } else {
169                    self.cur_glue -= f64::from(g.shrink);
170                }
171                let t = (self.set * self.cur_glue).clamp(-1_000_000_000.0, 1_000_000_000.0);
172                self.cur_g = t.round() as i32;
173            }
174        }
175        w += self.cur_g;
176        w
177    }
178}
179
180impl Engine {
181    /// Ships box `p` out (`ship_out`).
182    pub fn ship_out(&mut self, p: NodeRef) {
183        self.pdf.page_annots.clear();
184        let tracing = self.eqtb.int(ip::TRACINGOUTPUT) > 0;
185        if tracing {
186            self.printer.print_nl("");
187            self.printer.print_ln();
188            self.printer.print_str("Completed box being shipped out");
189        }
190        if self.printer.term_offset > self.printer.max_print_line.saturating_sub(9) {
191            self.printer.print_ln();
192        } else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
193            self.printer.print_char(' ' as u32);
194        }
195        self.printer.print_char('[' as u32);
196        let mut j = 9;
197        while j > 0 && self.eqtb.count[j].val == 0 {
198            j -= 1;
199        }
200        for k in 0..=j {
201            self.printer.print_int(self.eqtb.count[k].val.into());
202            if k < j {
203                self.printer.print_char('.' as u32);
204            }
205        }
206        self.printer.update_terminal();
207        if tracing {
208            self.printer.print_char(']' as u32);
209            self.begin_diagnostic();
210            self.show_box(p);
211            self.end_diagnostic(true);
212        }
213        self.ship_box(p);
214        if !tracing {
215            self.printer.print_char(']' as u32);
216        }
217        self.dead_cycles = 0;
218        self.printer.update_terminal();
219        self.nodes.flush_list(p);
220    }
221
222    /// Ships the contents of box `p`.
223    fn ship_box(&mut self, p: NodeRef) {
224        let Some(b) = self.nodes.box_data(p).cloned() else {
225            return;
226        };
227        let pdf = self.eqtb.int(ip::OUTPUTMODE) > 0;
228        // Page size and origin.
229        let h_origin = self.eqtb.dimen(crate::backend::H_ORIGIN);
230        let v_origin = self.eqtb.dimen(crate::backend::V_ORIGIN);
231        let h_off = self.eqtb.dimen(dp::HOFFSET);
232        let v_off = self.eqtb.dimen(dp::VOFFSET);
233        let mut width = self.eqtb.dimen(dp::PAGEWIDTH);
234        let mut height = self.eqtb.dimen(dp::PAGEHEIGHT);
235        if width <= 0 {
236            width = b.width + 2 * (h_origin + h_off);
237        }
238        if height <= 0 {
239            height = b.height + b.depth + 2 * (v_origin + v_off);
240        }
241        let mut backend = if pdf { self.backend.take() } else { None };
242        let forms_depth = self.pending_forms.len();
243        self.pending_forms.push(Vec::new());
244        if let Some(be) = backend.as_mut() {
245            self.pdf.page = Default::default();
246            be.begin_page(self, width, height);
247            if let Some(n) = be.current_page_object() {
248                let page = self.total_pages as i32 + 1;
249                self.pdf.page_refs.insert(page, n);
250                self.pdf.kinds.insert(n, crate::pdfext::ObjKind::Page);
251            }
252            // Observed: written at the page box's reference point.
253            let (x, y) = (h_origin + h_off, height - (v_origin + v_off + b.height));
254            for (mode, lit) in self.page_colorstack_literals() {
255                be.literal(mode, &lit, x, y);
256            }
257        }
258        let dvi = if pdf {
259            None
260        } else {
261            let mut d = std::mem::take(&mut self.dvi);
262            d.begin_page(self, (b.width, b.height, b.depth), (h_off, v_off));
263            Some(d)
264        };
265        let mut walker = Walker {
266            backend,
267            dvi,
268            h_origin: h_origin + h_off,
269            v_origin: v_origin + v_off,
270            page_height: height,
271            dvi_h: h_off,
272            dvi_v: v_off,
273            cur_s: -1,
274            doing_leaders: false,
275            page: true,
276        };
277        let cur_h = 0;
278        let cur_v = b.height;
279        if self.nodes.id(p) == id::VLIST {
280            self.vlist_out(&mut walker, p, cur_h, cur_v);
281        } else {
282            self.hlist_out(&mut walker, p, cur_h, cur_v);
283        }
284        if let Some(mut be) = walker.backend.take() {
285            self.flush_pending_objects(be.as_mut());
286            self.collect_page_texts();
287            let items = std::mem::take(&mut self.pdf.page);
288            be.close_page_contents(self, &items);
289            self.pdf.page = items;
290            self.resolve_link_actions();
291            let items = std::mem::take(&mut self.pdf.page);
292            be.page_items(self, &items);
293            // Observed order: the page's own objects, the forms it uses,
294            // its resources.
295            be.end_page(self);
296            let mut be = self.write_pending_forms(be);
297            be.close_page_resources(self);
298            self.flush_pending_objects(be.as_mut());
299            self.backend = Some(be);
300            for n in std::mem::take(&mut self.pdf.page_images) {
301                self.print_image_name(n);
302            }
303        }
304        self.pending_forms.truncate(forms_depth);
305        if let Some(mut d) = walker.dvi.take() {
306            d.end_page();
307            self.dvi = d;
308        }
309        self.total_pages += 1;
310    }
311
312    /// Writes the objects queued while the backend was busy.
313    fn flush_pending_objects(&mut self, be: &mut dyn Backend) {
314        for obj in std::mem::take(&mut self.pdf.pending) {
315            be.write_object(self, &obj);
316        }
317    }
318
319    /// Prints LuaTeX's note about reading font map file `name`
320    /// (`{/path/pdftex.map}`), when it exists.
321    pub(crate) fn print_map_file_note(&mut self, name: &str) {
322        let path = self
323            .kpse
324            .as_ref()
325            .and_then(|k| k.borrow_mut().find_file(name, retex_kpath::FileFormat::Map));
326        if let Some(path) = path {
327            // Printed like a C string (log lines up to 80 characters).
328            let mut text = b"{".to_vec();
329            text.extend_from_slice(path.to_string_lossy().as_bytes());
330            text.push(b'}');
331            self.printer.print_lua_bytes(&text);
332        }
333    }
334
335    /// Outputs a glyph with its reference point at TeX position (`h`,
336    /// `v`).
337    fn out_glyph(&self, w: &mut Walker, font: u32, chr: u32, h: i32, v: i32, ef: i32) {
338        self.out_glyph_at_depth(w, font, chr, h, v, ef, 0);
339    }
340
341    /// [`out_glyph`](Self::out_glyph) inside `depth` virtual characters.
342    #[allow(clippy::too_many_arguments)]
343    fn out_glyph_at_depth(
344        &self,
345        w: &mut Walker,
346        font: u32,
347        chr: u32,
348        h: i32,
349        v: i32,
350        ef: i32,
351        depth: u32,
352    ) {
353        if let Some(vf) = &self.font(font).vf
354            && let Some(cmds) = vf.packets.get(&chr)
355        {
356            if depth < 8 {
357                self.out_packet(w, vf, cmds, h, v, ef, depth);
358            }
359            return;
360        }
361        let (x, y) = w.pos(h, v);
362        if let Some(be) = w.backend.as_deref_mut() {
363            if ef == 0 {
364                be.glyph(self, font, chr, x, y);
365            } else {
366                be.glyph_expanded(self, font, chr, x, y, ef);
367            }
368        }
369        if let Some(d) = w.dvi.as_mut()
370            && self.font(font).chars.contains_key(&chr)
371        {
372            let wd = crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
373            d.glyph(self, font, chr, w.dvi_h + h, w.dvi_v + v, wd);
374        }
375    }
376
377    /// Runs the packet of a virtual character whose reference point is at
378    /// (`h`, `v`): characters of the local fonts and rules are output at
379    /// the positions the packet moves to (in DVI as in PDF, LuaTeX writes
380    /// the local fonts' characters, not the virtual one). Specials in
381    /// packets are dropped, as LuaTeX does.
382    #[allow(clippy::too_many_arguments)]
383    fn out_packet(
384        &self,
385        w: &mut Walker,
386        vf: &crate::fonts::VirtualFont,
387        cmds: &[retex_font::vf::Command],
388        mut h: i32,
389        mut v: i32,
390        ef: i32,
391        depth: u32,
392    ) {
393        use retex_font::vf::Command;
394        let mut f = vf.fonts.first().copied().unwrap_or(0);
395        let mut stack: Vec<(i32, i32)> = Vec::new();
396        for c in cmds {
397            match c {
398                Command::Font(i) => f = vf.fonts.get(*i).copied().unwrap_or(0),
399                Command::Char(c) => {
400                    if f != 0 && self.font(f).chars.contains_key(c) {
401                        self.out_glyph_at_depth(w, f, *c, h, v, ef, depth + 1);
402                        h = h.wrapping_add(crate::expansion::expanded_width(
403                            self.font_char_dimen(f, *c, 0),
404                            ef,
405                        ));
406                    }
407                }
408                Command::Rule { height, width } => {
409                    if *height > 0 && *width > 0 {
410                        Self::out_rule(w, h, v, *width, *height);
411                    }
412                    h = h.wrapping_add(*width);
413                }
414                Command::Right(x) => h = h.wrapping_add(*x),
415                Command::Down(y) => v = v.wrapping_add(*y),
416                Command::Push => stack.push((h, v)),
417                Command::Pop => {
418                    if let Some((sh, sv)) = stack.pop() {
419                        (h, v) = (sh, sv);
420                    }
421                }
422                Command::Special(_) => {}
423            }
424        }
425    }
426
427    /// Outputs a rule with its lower left corner at TeX position (`h`,
428    /// `v`).
429    fn out_rule(w: &mut Walker, h: i32, v: i32, width: i32, height: i32) {
430        let (x, y) = w.pos(h, v);
431        if let Some(be) = w.backend.as_deref_mut() {
432            be.rule(x, y, width, height);
433        }
434        if let Some(d) = w.dvi.as_mut() {
435            d.rule(w.dvi_h + h, w.dvi_v + v, width, height);
436        }
437    }
438
439    /// Draws box resource `index` with its lower-left corner at (`h`, `v`).
440    /// A form not written yet is written after the page or form being
441    /// written (observed object numbering of LuaTeX).
442    fn out_box_resource(
443        &mut self,
444        w: &mut Walker,
445        index: i32,
446        h: i32,
447        v: i32,
448        width: i32,
449        height: i32,
450    ) {
451        let n = index as u32;
452        let (x, y) = w.pos(h, v);
453        let Some(mut be) = w.backend.take() else {
454            return;
455        };
456        if self
457            .box_resource_slot(n)
458            .is_some_and(|i| !self.box_resources[i].written)
459        {
460            match self.pending_forms.last_mut() {
461                Some(p) => {
462                    if !p.contains(&n) {
463                        p.push(n);
464                    }
465                }
466                None => be = self.write_box_resource(be, n),
467            }
468        }
469        be.box_form(self, n, x, y, width, height);
470        w.backend = Some(be);
471    }
472
473    /// Writes box resource `n` as a form: ships its box into the form with
474    /// the box's reference point at (0, depth), then the forms it uses that
475    /// are not written yet, then its resources.
476    pub fn write_box_resource(&mut self, be: Box<dyn Backend>, n: u32) -> Box<dyn Backend> {
477        let Some(slot) = self.box_resource_slot(n) else {
478            return be;
479        };
480        let r = self.box_resources[slot].clone();
481        if r.written {
482            return be;
483        }
484        let mut be = be;
485        let (bw, bh, bd) = r.size;
486        self.pending_forms.push(Vec::new());
487        self.form_objects.push(Vec::new());
488        be.begin_box_form(self, n, (bw, bh, bd), &r.attr, &r.resources);
489        let mut walker = Walker {
490            backend: Some(be),
491            dvi: None,
492            h_origin: 0,
493            v_origin: 0,
494            page_height: bh + bd,
495            dvi_h: 0,
496            dvi_v: 0,
497            cur_s: -1,
498            doing_leaders: false,
499            page: false,
500        };
501        if self.nodes.id(r.list) == id::VLIST {
502            self.vlist_out(&mut walker, r.list, 0, bh);
503        } else {
504            self.hlist_out(&mut walker, r.list, 0, bh);
505        }
506        let mut be = walker.backend.take().expect("the walker keeps the backend");
507        be.end_box_form(self);
508        self.box_resources[slot].written = true;
509        for obj in self.form_objects.pop().unwrap_or_default() {
510            be.write_object(self, &obj);
511        }
512        be = self.write_pending_forms(be);
513        be.close_box_form_resources(self);
514        be
515    }
516
517    /// Writes the forms used by the page or form just finished that are
518    /// not written yet.
519    fn write_pending_forms(&mut self, mut be: Box<dyn Backend>) -> Box<dyn Backend> {
520        for n in self.pending_forms.pop().unwrap_or_default() {
521            be = self.write_box_resource(be, n);
522        }
523        be
524    }
525
526    /// Draws image `index` with its lower-left corner at (`h`, `v`).
527    fn out_image(&mut self, w: &mut Walker, index: i32, h: i32, v: i32, width: i32, height: i32) {
528        let (x, y) = w.pos(h, v);
529        if let Some(be) = w.backend.as_deref_mut() {
530            be.image(self, index as u32, x, y, width, height);
531            if self.mark_image_written(index as u32) {
532                self.pdf.page_images.push(index as u32);
533            }
534        }
535    }
536
537    /// Outputs box `p` (`hlist_out` or `vlist_out`) with its reference
538    /// point at (`h`, `v`).
539    fn out_box(&mut self, w: &mut Walker, p: NodeRef, h: i32, v: i32) {
540        if self.nodes.id(p) == id::VLIST {
541            self.vlist_out(w, p, h, v);
542        } else {
543            self.hlist_out(w, p, h, v);
544        }
545    }
546
547    /// Enters a box: `cur_s` goes up and nested boxes get a DVI `push`.
548    fn enter_box(w: &mut Walker) {
549        w.cur_s += 1;
550        if let Some(d) = w.dvi.as_mut() {
551            if w.cur_s > 0 {
552                d.push(w.cur_s);
553            } else {
554                d.note_depth(w.cur_s);
555            }
556        }
557    }
558
559    /// Leaves a box (`dvi_pop`).
560    fn leave_box(w: &mut Walker) {
561        if w.cur_s > 0
562            && let Some(d) = w.dvi.as_mut()
563        {
564            d.pop();
565        }
566        w.cur_s -= 1;
567    }
568
569    /// Outputs an hlist box (`hlist_out`); (`cur_h`, `cur_v`) is the box's
570    /// reference point in TeX coordinates (v downwards).
571    fn hlist_out(&mut self, w: &mut Walker, this_box: NodeRef, mut cur_h: i32, cur_v: i32) {
572        let Some(b) = self.nodes.box_data(this_box).cloned() else {
573            return;
574        };
575        let mut gs = GlueState {
576            cur_g: 0,
577            cur_glue: 0.0,
578            order: b.glue_order,
579            sign: b.glue_sign,
580            set: b.glue_set,
581        };
582        Self::enter_box(w);
583        let base_line = cur_v;
584        let left_edge = cur_h;
585        let ctx = crate::pdfship::BoxCtx {
586            left: left_edge,
587            base: base_line,
588            width: b.width,
589            height: b.height,
590            depth: b.depth,
591        };
592        if !w.doing_leaders {
593            self.ship_continue_links(w, &ctx);
594        }
595        let mut p = b.head;
596        while !p.is_null() {
597            let next = self.nodes.next(p);
598            let n = self.nodes.get(p);
599            match &n.data {
600                NodeData::Glyph(g) => {
601                    let (font, chr, xo, yo, ef) =
602                        (g.font, g.chr, g.xoffset, g.yoffset, g.expansion_factor);
603                    if !self.pdf.map_note_done && w.backend.is_some() {
604                        // Observed: LuaTeX reads the map file when the
605                        // first character is output.
606                        self.pdf.map_note_done = true;
607                        self.print_map_file_note("pdftex.map");
608                    }
609                    self.out_glyph(w, font, chr, cur_h + xo, base_line - yo, ef);
610                    cur_h +=
611                        crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
612                }
613                NodeData::Box(inner) => {
614                    let (iw, shift, head) = (inner.width, inner.shift, inner.head);
615                    if !head.is_null() {
616                        self.out_box(w, p, cur_h, base_line + shift);
617                    }
618                    cur_h += iw;
619                }
620                NodeData::Rule {
621                    width,
622                    depth,
623                    height,
624                    index,
625                    ..
626                } => {
627                    let (wd, ht, dpt, idx) = (*width, *height, *depth, *index);
628                    if n.subtype == crate::image::IMAGE_RULE {
629                        self.out_image(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
630                    } else if n.subtype == crate::image::BOX_RESOURCE_RULE {
631                        self.out_box_resource(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
632                    } else {
633                        Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, &b);
634                    }
635                    cur_h += wd;
636                }
637                NodeData::Glue { spec, leader } => {
638                    let (spec, leader, sub) = (*spec, *leader, n.subtype);
639                    let wd = gs.advance(&spec);
640                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
641                        self.hlist_leaders(w, leader, sub, cur_h, left_edge, base_line, wd, &b);
642                    }
643                    cur_h += wd;
644                }
645                NodeData::Kern {
646                    kern,
647                    expansion_factor,
648                } => cur_h += kern + expansion_factor,
649                NodeData::Math { surround, glue } => {
650                    let g = *glue;
651                    cur_h += surround;
652                    if g != Glue::ZERO {
653                        cur_h += gs.advance(&g);
654                    }
655                }
656                NodeData::MarginKern { width, .. } => cur_h += width,
657                NodeData::Whatsit(_) => self.out_what(w, p, cur_h, base_line, &ctx),
658                NodeData::Disc { replace, .. } => {
659                    // A discretionary that was not broken shows its
660                    // replacement text.
661                    let r = *replace;
662                    let mut q = r;
663                    while !q.is_null() {
664                        if let NodeData::Glyph(g) = &self.nodes.get(q).data {
665                            let (font, chr, ef) = (g.font, g.chr, g.expansion_factor);
666                            self.out_glyph(w, font, chr, cur_h, base_line, ef);
667                            cur_h += crate::expansion::expanded_width(
668                                self.font_char_dimen(font, chr, 0),
669                                ef,
670                            );
671                        } else if let NodeData::Kern {
672                            kern,
673                            expansion_factor,
674                        } = &self.nodes.get(q).data
675                        {
676                            cur_h += kern + expansion_factor;
677                        }
678                        q = self.nodes.next(q);
679                    }
680                }
681                _ => {}
682            }
683            p = next;
684        }
685        if !w.doing_leaders {
686            self.ship_end_box_links(w, &ctx);
687        }
688        Self::leave_box(w);
689    }
690
691    /// A rule in an hlist whose left end is at `cur_h` (TeX's `fin_rule`
692    /// in §624); running dimensions come from the enclosing box.
693    fn hlist_rule(
694        w: &mut Walker,
695        cur_h: i32,
696        base_line: i32,
697        wd: i32,
698        ht: i32,
699        dpt: i32,
700        this_box: &crate::nodes::BoxData,
701    ) {
702        let ht = if ht == RUNNING { this_box.height } else { ht };
703        let dpt = if dpt == RUNNING { this_box.depth } else { dpt };
704        let total = ht + dpt;
705        if total > 0 && wd > 0 {
706            Self::out_rule(w, cur_h, base_line + dpt, wd, total);
707        }
708    }
709
710    /// Outputs a vlist box (`vlist_out`).
711    fn vlist_out(&mut self, w: &mut Walker, this_box: NodeRef, cur_h: i32, cur_v: i32) {
712        let Some(b) = self.nodes.box_data(this_box).cloned() else {
713            return;
714        };
715        let mut gs = GlueState {
716            cur_g: 0,
717            cur_glue: 0.0,
718            order: b.glue_order,
719            sign: b.glue_sign,
720            set: b.glue_set,
721        };
722        Self::enter_box(w);
723        let left_edge = cur_h;
724        let ctx = crate::pdfship::BoxCtx {
725            left: left_edge,
726            base: cur_v,
727            width: b.width,
728            height: b.height,
729            depth: b.depth,
730        };
731        let mut cur_v = cur_v - b.height;
732        let top_edge = cur_v;
733        let mut p = b.head;
734        while !p.is_null() {
735            let next = self.nodes.next(p);
736            let n = self.nodes.get(p);
737            match &n.data {
738                NodeData::Box(inner) => {
739                    let (ih, idp, shift, head) =
740                        (inner.height, inner.depth, inner.shift, inner.head);
741                    if head.is_null() {
742                        cur_v += ih + idp;
743                    } else {
744                        cur_v += ih;
745                        self.out_box(w, p, left_edge + shift, cur_v);
746                        cur_v += idp;
747                    }
748                }
749                NodeData::Rule {
750                    width,
751                    depth,
752                    height,
753                    index,
754                    ..
755                } => {
756                    let wd = if *width == RUNNING { b.width } else { *width };
757                    let total = height + depth;
758                    let idx = *index;
759                    cur_v += total;
760                    if n.subtype == crate::image::IMAGE_RULE {
761                        self.out_image(w, idx, left_edge, cur_v, wd, total);
762                    } else if n.subtype == crate::image::BOX_RESOURCE_RULE {
763                        self.out_box_resource(w, idx, left_edge, cur_v, wd, total);
764                    } else if total > 0 && wd > 0 {
765                        Self::out_rule(w, left_edge, cur_v, wd, total);
766                    }
767                }
768                NodeData::Glue { spec, leader } => {
769                    let (spec, leader, sub) = (*spec, *leader, n.subtype);
770                    let ht = gs.advance(&spec);
771                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
772                        cur_v =
773                            self.vlist_leaders(w, leader, sub, cur_v, left_edge, top_edge, ht, &b);
774                    } else {
775                        cur_v += ht;
776                    }
777                }
778                NodeData::Kern { kern, .. } => cur_v += kern,
779                NodeData::Whatsit(_) => self.out_what(w, p, left_edge, cur_v, &ctx),
780                _ => {}
781            }
782            p = next;
783        }
784        Self::leave_box(w);
785    }
786
787    /// Where leaders of the given kind start and how much space follows
788    /// each copy (§626, §635): returns (start, gap) for a space of size
789    /// `size` at `cur` filled with boxes of size `step`.
790    fn leader_start(sub: u16, cur: i32, edge0: i32, size: i32, step: i32) -> (i32, i32) {
791        if sub == glue_sub::C_LEADERS || sub == glue_sub::X_LEADERS {
792            let lq = size / step;
793            let lr = size % step;
794            if sub == glue_sub::C_LEADERS {
795                (cur + lr / 2, 0)
796            } else {
797                let lx = lr / (lq + 1);
798                (cur + (lr - (lq - 1) * lx) / 2, lx)
799            }
800        } else {
801            let mut c = edge0 + step * ((cur - edge0) / step);
802            if c < cur {
803                c += step;
804            }
805            (c, 0)
806        }
807    }
808
809    /// Leaders in an hlist (§625–§628) filling `wd` from `cur_h`.
810    #[allow(clippy::too_many_arguments)]
811    fn hlist_leaders(
812        &mut self,
813        w: &mut Walker,
814        leader: NodeRef,
815        sub: u16,
816        cur_h: i32,
817        left_edge: i32,
818        base_line: i32,
819        wd: i32,
820        this_box: &crate::nodes::BoxData,
821    ) {
822        let lb = match &self.nodes.get(leader).data {
823            NodeData::Rule { height, depth, .. } => {
824                let (ht, dpt) = (*height, *depth);
825                Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, this_box);
826                return;
827            }
828            NodeData::Box(lb) => lb.clone(),
829            _ => return,
830        };
831        let leader_wd = lb.width;
832        if leader_wd <= 0 || wd <= 0 {
833            return;
834        }
835        let rule_wd = wd + 10; // compensates for floating-point rounding
836        let edge = cur_h + rule_wd;
837        let (mut h, lx) = Self::leader_start(sub, cur_h, left_edge, rule_wd, leader_wd);
838        // The box is output even if it is empty, which counts towards the
839        // DVI stack depth (§628).
840        while h + leader_wd <= edge {
841            let outer = w.doing_leaders;
842            w.doing_leaders = true;
843            self.out_box(w, leader, h, base_line + lb.shift);
844            w.doing_leaders = outer;
845            h += leader_wd + lx;
846        }
847    }
848
849    /// Leaders in a vlist (§634–§637) filling `ht` below `cur_v`; returns
850    /// the new `cur_v`.
851    #[allow(clippy::too_many_arguments)]
852    fn vlist_leaders(
853        &mut self,
854        w: &mut Walker,
855        leader: NodeRef,
856        sub: u16,
857        cur_v: i32,
858        left_edge: i32,
859        top_edge: i32,
860        ht: i32,
861        this_box: &crate::nodes::BoxData,
862    ) -> i32 {
863        let lb = match &self.nodes.get(leader).data {
864            NodeData::Rule { width, .. } => {
865                let wd = if *width == RUNNING {
866                    this_box.width
867                } else {
868                    *width
869                };
870                if ht > 0 && wd > 0 {
871                    Self::out_rule(w, left_edge, cur_v + ht, wd, ht);
872                }
873                return cur_v + ht;
874            }
875            NodeData::Box(lb) => lb.clone(),
876            _ => return cur_v + ht,
877        };
878        let leader_ht = lb.height + lb.depth;
879        if leader_ht <= 0 || ht <= 0 {
880            return cur_v + ht;
881        }
882        let rule_ht = ht + 10; // compensates for floating-point rounding
883        let edge = cur_v + rule_ht;
884        let (mut v, lx) = Self::leader_start(sub, cur_v, top_edge, rule_ht, leader_ht);
885        while v + leader_ht <= edge {
886            let outer = w.doing_leaders;
887            w.doing_leaders = true;
888            self.out_box(w, leader, left_edge + lb.shift, v + lb.height);
889            w.doing_leaders = outer;
890            v += leader_ht + lx;
891        }
892        edge - 10
893    }
894
895    /// Executes a whatsit at TeX position (`h`, `v`) (`out_what`).
896    fn out_what(
897        &mut self,
898        w: &mut Walker,
899        p: NodeRef,
900        h: i32,
901        v: i32,
902        ctx: &crate::pdfship::BoxCtx,
903    ) {
904        let NodeData::Whatsit(wh) = &self.nodes.get(p).data else {
905            return;
906        };
907        let (x, y) = w.pos(h, v);
908        match (**wh).clone() {
909            Whatsit::Open { stream, name } if !w.doing_leaders => {
910                self.open_write_stream(stream as i32, &name)
911            }
912            Whatsit::Write { stream, data } if !w.doing_leaders => self.write_out(stream, &data),
913            Whatsit::Close { stream } if !w.doing_leaders => self.close_write_stream(stream as i32),
914            Whatsit::Special { data } => {
915                if let Some(d) = w.dvi.as_mut() {
916                    d.special(w.dvi_h + h, w.dvi_v + v, &data);
917                }
918            }
919            Whatsit::PdfLiteral { mode, data, .. } => {
920                let bytes = match data {
921                    crate::nodes::LateData::Bytes(b) => b,
922                    crate::nodes::LateData::Tokens(t) => {
923                        let toks = self.expand_late_tokens(&t);
924                        self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
925                    }
926                };
927                if let Some(be) = w.backend.as_deref_mut() {
928                    be.literal(mode, &bytes, x, y);
929                }
930            }
931            Whatsit::LateLua { data, .. } => {
932                let code = match data {
933                    crate::nodes::LateData::Bytes(b) => b,
934                    crate::nodes::LateData::Tokens(t) => {
935                        let toks = self.expand_late_tokens(&t);
936                        self.lua_code(&toks)
937                    }
938                };
939                // `pdf.getpos`, `pdf.print` and `pdf.registerannot` work on
940                // this context while the code runs.
941                self.pdf.pos = (x, y);
942                let outer = self.pdf.late.replace(Default::default());
943                self.run_lua(&code);
944                let ctx = std::mem::replace(&mut self.pdf.late, outer).unwrap_or_default();
945                if let Some(be) = w.backend.as_deref_mut() {
946                    for (mode, text) in &ctx.prints {
947                        be.literal(*mode, text, x, y);
948                    }
949                }
950                self.pdf
951                    .page
952                    .annots
953                    .extend(ctx.annots.into_iter().map(|a| (a, None)));
954            }
955            Whatsit::PdfRefobj { objnum } if !w.doing_leaders => self.ship_refobj(w, objnum),
956            Whatsit::PdfAnnot { dims, objnum, data } if !w.doing_leaders => {
957                self.ship_annot(w, ctx, h, v, dims, objnum, &data)
958            }
959            Whatsit::PdfStartLink {
960                dims,
961                objnum,
962                attr,
963                action,
964            } if !w.doing_leaders => {
965                self.ship_start_link(w, ctx, h, v, dims, objnum, &attr, &action)
966            }
967            Whatsit::PdfEndLink if !w.doing_leaders => self.ship_end_link(w, h, v),
968            Whatsit::PdfDest { id, view } if !w.doing_leaders => {
969                self.ship_dest(w, ctx, h, v, &id, &view)
970            }
971            Whatsit::SavePos => {
972                self.last_x_pos = x;
973                self.last_y_pos = y;
974            }
975            Whatsit::PdfSave => {
976                if let Some(be) = w.backend.as_deref_mut() {
977                    be.save(x, y);
978                }
979            }
980            Whatsit::PdfRestore => {
981                if let Some(be) = w.backend.as_deref_mut() {
982                    be.restore(x, y);
983                }
984            }
985            Whatsit::PdfSetmatrix { data } => {
986                if let Some(be) = w.backend.as_deref_mut() {
987                    be.set_matrix(x, y, &data);
988                }
989            }
990            Whatsit::PdfColorstack { stack, cmd, data } => {
991                let out = self.colorstack_op(stack, cmd, data);
992                if let (Some((mode, lit)), Some(be)) = (out, w.backend.as_deref_mut()) {
993                    be.literal(mode, &lit, x, y);
994                }
995            }
996            _ => {}
997        }
998    }
999
1000    /// Applies a colour-stack command; returns the literal to emit.
1001    fn colorstack_op(&mut self, stack: i32, cmd: u8, data: Vec<u8>) -> Option<(u8, Vec<u8>)> {
1002        use crate::pdfext::colorstack_cmd as c;
1003        self.pdf_ensure_colorstacks();
1004        let def = self.pdf.colorstacks.get(stack.max(0) as usize).cloned()?;
1005        let st = self
1006            .colorstack_values
1007            .entry(stack)
1008            .or_insert_with(|| vec![def.init.clone()]);
1009        match cmd {
1010            c::SET => {
1011                if let Some(top) = st.last_mut() {
1012                    *top = data;
1013                } else {
1014                    st.push(data);
1015                }
1016            }
1017            c::PUSH => st.push(data),
1018            c::POP => {
1019                if st.len() > 1 {
1020                    st.pop();
1021                }
1022            }
1023            _ => {}
1024        }
1025        let lit = st.last().cloned().unwrap_or_default();
1026        Some((def.mode, lit))
1027    }
1028
1029    /// Expands a late token list (as `\write` does) and returns the
1030    /// expanded tokens.
1031    fn expand_late_tokens(&mut self, list: &crate::token::TokenList) -> Vec<crate::token::Token> {
1032        use crate::token::Token;
1033        use std::rc::Rc;
1034        self.ins_list(Rc::from(vec![
1035            Token::RIGHT_BRACE,
1036            Token::cs(self.cs.end_write),
1037        ]));
1038        self.begin_token_list(list.clone(), crate::input::TokKind::WriteText);
1039        self.ins_list(Rc::from(vec![Token::LEFT_BRACE]));
1040        let toks = self.scan_toks(false, true);
1041        self.get_token();
1042        toks
1043    }
1044
1045    /// Finishes the output file at the end of the run and reports it.
1046    pub fn finish_backend(&mut self) {
1047        if self.dvi.pages() > 0 {
1048            self.finish_dvi();
1049        } else if self.pdf.output_opened && self.eqtb.int(ip::OUTPUTMODE) <= 0 {
1050            // LuaTeX opened the DVI file for `pdf.obj` and leaves it empty.
1051            let _ = std::fs::write(self.output_path("dvi"), b"");
1052        }
1053        let Some(mut be) = self.backend.take() else {
1054            return;
1055        };
1056        if self.pdf.fatal || self.total_pages == 0 {
1057            be.abandon(self.pdf.fatal);
1058            self.backend = Some(be);
1059            return;
1060        }
1061        if self.total_pages > 0 {
1062            self.pdf_finish_document();
1063            self.flush_pending_objects(be.as_mut());
1064        }
1065        if self.total_pages > 0
1066            && let Some(fin) = be.finish(self)
1067        {
1068            // Observed: printed one after the other like C strings (log
1069            // lines up to 80 characters), right after what came before.
1070            for f in &fin.embedded {
1071                let mut text = b"<".to_vec();
1072                text.extend_from_slice(f.as_bytes());
1073                text.push(b'>');
1074                self.printer.print_lua_bytes(&text);
1075            }
1076            self.pdf.objstm_stats = fin.objstm;
1077            self.print_output_written(&fin.file, self.total_pages, fin.size);
1078        }
1079        self.backend = Some(be);
1080    }
1081
1082    /// The path of output file `<jobname>.<ext>` (in `-output-directory`).
1083    pub(crate) fn output_path(&self, ext: &str) -> std::path::PathBuf {
1084        let job =
1085            String::from_utf8_lossy(self.job_name.as_deref().unwrap_or(b"texput")).into_owned();
1086        let name = format!("{job}.{ext}");
1087        match &self.files.output_dir {
1088            Some(d) => d.join(&name),
1089            None => std::path::PathBuf::from(&name),
1090        }
1091    }
1092
1093    /// Writes the postamble and the DVI file `<jobname>.dvi` (§642).
1094    fn finish_dvi(&mut self) {
1095        let mut dvi = std::mem::take(&mut self.dvi);
1096        let pages = dvi.pages();
1097        let data = dvi.finish(self);
1098        let path = self.output_path("dvi");
1099        let name = path
1100            .file_name()
1101            .map(|n| n.to_string_lossy().into_owned())
1102            .unwrap_or_default();
1103        if std::fs::write(&path, &data).is_err() {
1104            self.printer.print_nl("! I can't write on file `");
1105            self.printer.print_bytes(name.as_bytes());
1106            self.printer.print_str("'.");
1107            return;
1108        }
1109        self.print_output_written(&name, pages, data.len() as u64);
1110    }
1111
1112    /// Prints `Output written on NAME (N pages, B bytes).`.
1113    fn print_output_written(&mut self, name: &str, pages: u32, size: u64) {
1114        self.output_written = Some((name.to_owned(), pages, size));
1115        self.printer.print_nl("Output written on ");
1116        self.printer.print_bytes(name.as_bytes());
1117        self.printer.print_str(" (");
1118        self.printer.print_int(i64::from(pages));
1119        self.printer
1120            .print_str(if pages == 1 { " page" } else { " pages" });
1121        self.printer.print_str(", ");
1122        self.printer.print_int(size as i64);
1123        self.printer.print_str(" bytes).");
1124    }
1125}
1126
1127/// The state of a page being shipped: the outputs and the coordinate
1128/// systems they use.
1129pub(crate) struct Walker {
1130    /// The PDF backend (PDF mode).
1131    backend: Option<Box<dyn Backend>>,
1132    /// The DVI writer (DVI mode).
1133    dvi: Option<crate::dvi::DviWriter>,
1134    h_origin: i32,
1135    v_origin: i32,
1136    page_height: i32,
1137    /// The DVI position of TeX's origin (`\hoffset`, `\voffset`).
1138    dvi_h: i32,
1139    dvi_v: i32,
1140    /// Box nesting depth (`cur_s`): -1 outside the page box.
1141    pub(crate) cur_s: i32,
1142    /// Whether a leader box is being output (`doing_leaders`).
1143    doing_leaders: bool,
1144    /// Whether this is a page (not a box resource): annotations, links
1145    /// and destinations belong to pages.
1146    pub(crate) page: bool,
1147}
1148
1149impl Walker {
1150    /// Page coordinates (from the lower left, y up) of TeX point
1151    /// (`h`, `v`) measured from the origin, v downwards.
1152    pub(crate) fn pos(&self, h: i32, v: i32) -> (i32, i32) {
1153        (self.h_origin + h, self.page_height - (self.v_origin + v))
1154    }
1155}