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    /// Calls the page-number callback `name` if one is registered; whether
182    /// it was.
183    fn page_number_callback(&mut self, name: &str) -> bool {
184        let Some(lua) = self.lua.clone() else {
185            return false;
186        };
187        if !lua.has_callback(name) {
188            return false;
189        }
190        if let Err(e) = lua.call_simple(self, name) {
191            self.lua_error(&e);
192        }
193        true
194    }
195
196    /// Ships box `p` out (`ship_out`).
197    pub fn ship_out(&mut self, p: NodeRef) {
198        self.pdf.page_annots.clear();
199        let tracing = self.eqtb.int(ip::TRACINGOUTPUT) > 0;
200        if tracing {
201            self.printer.print_nl("");
202            self.printer.print_ln();
203            self.printer.print_str("Completed box being shipped out");
204        }
205        // The `start_page_number` callback replaces ` [` and the page
206        // numbers, `stop_page_number` the closing `]` (observed).
207        if !self.page_number_callback("start_page_number") {
208            if self.printer.term_offset > self.printer.max_print_line.saturating_sub(9) {
209                self.printer.print_ln();
210            } else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
211                self.printer.print_char(' ' as u32);
212            }
213            self.printer.print_char('[' as u32);
214            let mut j = 9;
215            while j > 0 && self.eqtb.count[j].val == 0 {
216                j -= 1;
217            }
218            for k in 0..=j {
219                self.printer.print_int(self.eqtb.count[k].val.into());
220                if k < j {
221                    self.printer.print_char('.' as u32);
222                }
223            }
224        }
225        self.printer.update_terminal();
226        if tracing {
227            if !self.page_number_callback("stop_page_number") {
228                self.printer.print_char(']' as u32);
229            }
230            self.begin_diagnostic();
231            self.show_box(p);
232            self.end_diagnostic(true);
233        }
234        self.ship_box(p);
235        if !tracing && !self.page_number_callback("stop_page_number") {
236            self.printer.print_char(']' as u32);
237        }
238        self.dead_cycles = 0;
239        self.printer.update_terminal();
240        self.nodes.flush_list(p);
241    }
242
243    /// Ships the contents of box `p`.
244    fn ship_box(&mut self, p: NodeRef) {
245        let Some(b) = self.nodes.box_data(p).cloned() else {
246            return;
247        };
248        let pdf = self.eqtb.int(ip::OUTPUTMODE) > 0;
249        // Page size and origin.
250        let h_origin = self.eqtb.dimen(crate::backend::H_ORIGIN);
251        let v_origin = self.eqtb.dimen(crate::backend::V_ORIGIN);
252        let h_off = self.eqtb.dimen(dp::HOFFSET);
253        let v_off = self.eqtb.dimen(dp::VOFFSET);
254        let mut width = self.eqtb.dimen(dp::PAGEWIDTH);
255        let mut height = self.eqtb.dimen(dp::PAGEHEIGHT);
256        // Observed: without \pagewidth (\pageheight), the page is the box
257        // plus 1in and the offset on each side, whatever the origin.
258        const ONE_INCH: i32 = 4_736_286;
259        if width <= 0 {
260            width = b.width + 2 * (ONE_INCH + h_off);
261        }
262        if height <= 0 {
263            height = b.height + b.depth + 2 * (ONE_INCH + v_off);
264        }
265        let mut backend = if pdf { self.backend.take() } else { None };
266        let forms_depth = self.pending_forms.len();
267        self.pending_forms.push(Vec::new());
268        if let Some(be) = backend.as_mut() {
269            self.pdf.page = Default::default();
270            be.begin_page(self, width, height);
271            if let Some(n) = be.current_page_object() {
272                let page = self.total_pages as i32 + 1;
273                self.pdf.page_refs.insert(page, n);
274                self.pdf.kinds.insert(n, crate::pdfext::ObjKind::Page);
275            }
276            // Observed: written at the page box's reference point.
277            let (x, y) = (h_origin + h_off, height - (v_origin + v_off + b.height));
278            for (mode, lit) in self.page_colorstack_literals() {
279                be.literal(mode, &lit, x, y);
280            }
281        }
282        let dvi = if pdf {
283            None
284        } else {
285            let mut d = std::mem::take(&mut self.dvi);
286            d.begin_page(self, (b.width, b.height, b.depth), (h_off, v_off));
287            Some(d)
288        };
289        let sync = self.synctex_recording();
290        if sync {
291            self.synctex_begin_page((h_origin + h_off, v_origin + v_off));
292        }
293        let mut walker = Walker {
294            backend,
295            dvi,
296            h_origin: h_origin + h_off,
297            v_origin: v_origin + v_off,
298            page_height: height,
299            dvi_h: h_off,
300            dvi_v: v_off,
301            cur_s: -1,
302            doing_leaders: false,
303            page: true,
304            sync,
305        };
306        let cur_h = 0;
307        let cur_v = b.height;
308        if self.nodes.id(p) == id::VLIST {
309            self.vlist_out(&mut walker, p, cur_h, cur_v);
310        } else {
311            self.hlist_out(&mut walker, p, cur_h, cur_v);
312        }
313        if let Some(mut be) = walker.backend.take() {
314            self.flush_pending_objects(be.as_mut());
315            self.collect_page_texts();
316            let items = std::mem::take(&mut self.pdf.page);
317            be.close_page_contents(self, &items);
318            self.pdf.page = items;
319            self.resolve_link_actions();
320            let items = std::mem::take(&mut self.pdf.page);
321            be.page_items(self, &items);
322            // Observed order: the page's own objects, the forms it uses,
323            // its resources.
324            be.end_page(self);
325            let mut be = self.write_pending_forms(be);
326            be.close_page_resources(self);
327            self.flush_pending_objects(be.as_mut());
328            self.backend = Some(be);
329            for n in std::mem::take(&mut self.pdf.page_images) {
330                self.print_image_name(n);
331            }
332        }
333        self.pending_forms.truncate(forms_depth);
334        if let Some(mut d) = walker.dvi.take() {
335            d.end_page();
336            self.dvi = d;
337        }
338        if sync {
339            self.synctex_end_page();
340        }
341        self.total_pages += 1;
342        if let Some(f) = self.progress_hook {
343            f(self);
344        }
345    }
346
347    /// Writes the objects queued while the backend was busy.
348    fn flush_pending_objects(&mut self, be: &mut dyn Backend) {
349        for obj in std::mem::take(&mut self.pdf.pending) {
350            be.write_object(self, &obj);
351        }
352    }
353
354    /// Prints LuaTeX's note about reading font map file `name`
355    /// (`{/path/pdftex.map}`), when it exists.
356    pub(crate) fn print_map_file_note(&mut self, name: &str) {
357        let path = self
358            .kpse
359            .as_ref()
360            .and_then(|k| k.borrow_mut().find_file(name, retex_kpath::FileFormat::Map));
361        if let Some(path) = path {
362            // Printed like a C string (log lines up to 80 characters).
363            let mut text = b"{".to_vec();
364            text.extend_from_slice(path.to_string_lossy().as_bytes());
365            text.push(b'}');
366            self.printer.print_lua_bytes(&text);
367        }
368    }
369
370    /// Outputs a glyph with its reference point at TeX position (`h`,
371    /// `v`).
372    fn out_glyph(&self, w: &mut Walker, font: u32, chr: u32, h: i32, v: i32, ef: i32) {
373        self.out_glyph_at_depth(w, font, chr, h, v, ef, 0);
374    }
375
376    /// [`out_glyph`](Self::out_glyph) inside `depth` virtual characters.
377    #[allow(clippy::too_many_arguments)]
378    fn out_glyph_at_depth(
379        &self,
380        w: &mut Walker,
381        font: u32,
382        chr: u32,
383        h: i32,
384        v: i32,
385        ef: i32,
386        depth: u32,
387    ) {
388        if let Some(vf) = &self.font(font).vf
389            && let Some(cmds) = vf.packets.get(&chr)
390        {
391            if depth < 8 {
392                self.out_packet(w, vf, cmds, h, v, ef, depth);
393            }
394            return;
395        }
396        let (x, y) = w.pos(h, v);
397        if let Some(be) = w.backend.as_deref_mut() {
398            if ef == 0 {
399                be.glyph(self, font, chr, x, y);
400            } else {
401                be.glyph_expanded(self, font, chr, x, y, ef);
402            }
403        }
404        if let Some(d) = w.dvi.as_mut()
405            && self.font(font).chars.contains_key(&chr)
406        {
407            let wd = crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
408            d.glyph(self, font, chr, w.dvi_h + h, w.dvi_v + v, wd);
409        }
410    }
411
412    /// Runs the packet of a virtual character whose reference point is at
413    /// (`h`, `v`): characters of the local fonts and rules are output at
414    /// the positions the packet moves to (in DVI as in PDF, LuaTeX writes
415    /// the local fonts' characters, not the virtual one). Specials in
416    /// packets are dropped, as LuaTeX does.
417    #[allow(clippy::too_many_arguments)]
418    fn out_packet(
419        &self,
420        w: &mut Walker,
421        vf: &crate::fonts::VirtualFont,
422        cmds: &[retex_font::vf::Command],
423        mut h: i32,
424        mut v: i32,
425        ef: i32,
426        depth: u32,
427    ) {
428        use retex_font::vf::Command;
429        let mut f = vf.fonts.first().copied().unwrap_or(0);
430        let mut stack: Vec<(i32, i32)> = Vec::new();
431        for c in cmds {
432            match c {
433                Command::Font(i) => f = vf.fonts.get(*i).copied().unwrap_or(0),
434                Command::Char(c) => {
435                    if f != 0 && self.font(f).chars.contains_key(c) {
436                        self.out_glyph_at_depth(w, f, *c, h, v, ef, depth + 1);
437                        h = h.wrapping_add(crate::expansion::expanded_width(
438                            self.font_char_dimen(f, *c, 0),
439                            ef,
440                        ));
441                    }
442                }
443                Command::Rule { height, width } => {
444                    if *height > 0 && *width > 0 {
445                        Self::out_rule(w, h, v, *width, *height);
446                    }
447                    h = h.wrapping_add(*width);
448                }
449                Command::Right(x) => h = h.wrapping_add(*x),
450                Command::Down(y) => v = v.wrapping_add(*y),
451                Command::Push => stack.push((h, v)),
452                Command::Pop => {
453                    if let Some((sh, sv)) = stack.pop() {
454                        (h, v) = (sh, sv);
455                    }
456                }
457                Command::Special(_) => {}
458            }
459        }
460    }
461
462    /// Outputs a rule with its lower left corner at TeX position (`h`,
463    /// `v`).
464    fn out_rule(w: &mut Walker, h: i32, v: i32, width: i32, height: i32) {
465        let (x, y) = w.pos(h, v);
466        if let Some(be) = w.backend.as_deref_mut() {
467            be.rule(x, y, width, height);
468        }
469        if let Some(d) = w.dvi.as_mut() {
470            d.rule(w.dvi_h + h, w.dvi_v + v, width, height);
471        }
472    }
473
474    /// Draws box resource `index` with its lower-left corner at (`h`, `v`).
475    /// A form not written yet is written after the page or form being
476    /// written (observed object numbering of LuaTeX).
477    fn out_box_resource(
478        &mut self,
479        w: &mut Walker,
480        index: i32,
481        h: i32,
482        v: i32,
483        width: i32,
484        height: i32,
485    ) {
486        let n = index as u32;
487        let (x, y) = w.pos(h, v);
488        let Some(mut be) = w.backend.take() else {
489            return;
490        };
491        if self
492            .box_resource_slot(n)
493            .is_some_and(|i| !self.box_resources[i].written)
494        {
495            match self.pending_forms.last_mut() {
496                Some(p) => {
497                    if !p.contains(&n) {
498                        p.push(n);
499                    }
500                }
501                None => be = self.write_box_resource(be, n),
502            }
503        }
504        be.box_form(self, n, x, y, width, height);
505        w.backend = Some(be);
506    }
507
508    /// Writes box resource `n` as a form: ships its box into the form with
509    /// the box's reference point at (0, depth), then the forms it uses that
510    /// are not written yet, then its resources.
511    pub fn write_box_resource(&mut self, be: Box<dyn Backend>, n: u32) -> Box<dyn Backend> {
512        let Some(slot) = self.box_resource_slot(n) else {
513            return be;
514        };
515        let r = self.box_resources[slot].clone();
516        if r.written {
517            return be;
518        }
519        // Observed: a form is shipped out like a page, so with
520        // \tracingoutput positive the line is shown (but neither the
521        // page number nor the box).
522        if self.eqtb.int(ip::TRACINGOUTPUT) > 0 {
523            self.printer.print_nl("");
524            self.printer.print_ln();
525            self.printer.print_str("Completed box being shipped out");
526        }
527        let mut be = be;
528        let (bw, bh, bd) = r.size;
529        self.pending_forms.push(Vec::new());
530        self.form_objects.push(Vec::new());
531        be.begin_box_form(self, n, (bw, bh, bd), &r.attr, &r.resources);
532        let sync = self.synctex_recording();
533        if sync {
534            self.synctex_begin_form(bh + bd);
535        }
536        let mut walker = Walker {
537            backend: Some(be),
538            dvi: None,
539            h_origin: 0,
540            v_origin: 0,
541            page_height: bh + bd,
542            dvi_h: 0,
543            dvi_v: 0,
544            cur_s: -1,
545            doing_leaders: false,
546            page: false,
547            sync,
548        };
549        if self.nodes.id(r.list) == id::VLIST {
550            self.vlist_out(&mut walker, r.list, 0, bh);
551        } else {
552            self.hlist_out(&mut walker, r.list, 0, bh);
553        }
554        let mut be = walker.backend.take().expect("the walker keeps the backend");
555        be.end_box_form(self);
556        self.box_resources[slot].written = true;
557        for obj in self.form_objects.pop().unwrap_or_default() {
558            be.write_object(self, &obj);
559        }
560        be = self.write_pending_forms(be);
561        // Observed: the forms this one uses are written inside its sheet.
562        if sync {
563            self.synctex_end_form();
564        }
565        be.close_box_form_resources(self);
566        be
567    }
568
569    /// Writes the forms used by the page or form just finished that are
570    /// not written yet.
571    fn write_pending_forms(&mut self, mut be: Box<dyn Backend>) -> Box<dyn Backend> {
572        for n in self.pending_forms.pop().unwrap_or_default() {
573            be = self.write_box_resource(be, n);
574        }
575        be
576    }
577
578    /// Draws image `index` with its lower-left corner at (`h`, `v`).
579    fn out_image(&mut self, w: &mut Walker, index: i32, h: i32, v: i32, width: i32, height: i32) {
580        let (x, y) = w.pos(h, v);
581        if let Some(be) = w.backend.as_deref_mut() {
582            be.image(self, index as u32, x, y, width, height);
583            if self.mark_image_written(index as u32) {
584                self.pdf.page_images.push(index as u32);
585            }
586        }
587    }
588
589    /// Outputs box `p` (`hlist_out` or `vlist_out`) with its reference
590    /// point at (`h`, `v`).
591    fn out_box(&mut self, w: &mut Walker, p: NodeRef, h: i32, v: i32) {
592        // A box that contains itself (made by Lua code) is not followed
593        // forever (LuaTeX crashes).
594        if w.cur_s >= crate::nodes::MAX_NESTING as i32 {
595            return;
596        }
597        if self.nodes.id(p) == id::VLIST {
598            self.vlist_out(w, p, h, v);
599        } else {
600            self.hlist_out(w, p, h, v);
601        }
602    }
603
604    /// Enters a box: `cur_s` goes up and nested boxes get a DVI `push`.
605    fn enter_box(w: &mut Walker) {
606        w.cur_s += 1;
607        if let Some(d) = w.dvi.as_mut() {
608            if w.cur_s > 0 {
609                d.push(w.cur_s);
610            } else {
611                d.note_depth(w.cur_s);
612            }
613        }
614    }
615
616    /// Leaves a box (`dvi_pop`).
617    fn leave_box(w: &mut Walker) {
618        if w.cur_s > 0
619            && let Some(d) = w.dvi.as_mut()
620        {
621            d.pop();
622        }
623        w.cur_s -= 1;
624    }
625
626    /// Outputs an hlist box (`hlist_out`); (`cur_h`, `cur_v`) is the box's
627    /// reference point in TeX coordinates (v downwards).
628    fn hlist_out(&mut self, w: &mut Walker, this_box: NodeRef, mut cur_h: i32, cur_v: i32) {
629        let Some(b) = self.nodes.box_data(this_box).cloned() else {
630            return;
631        };
632        let mut gs = GlueState {
633            cur_g: 0,
634            cur_glue: 0.0,
635            order: b.glue_order,
636            sign: b.glue_sign,
637            set: b.glue_set,
638        };
639        Self::enter_box(w);
640        let base_line = cur_v;
641        let left_edge = cur_h;
642        let ctx = crate::pdfship::BoxCtx {
643            left: left_edge,
644            base: base_line,
645            width: b.width,
646            height: b.height,
647            depth: b.depth,
648        };
649        if !w.doing_leaders {
650            self.ship_continue_links(w, &ctx);
651        }
652        if w.sync {
653            let f = self.sync_of(this_box);
654            self.synctex
655                .box_open(true, f, cur_h, cur_v, [b.width, b.height, b.depth]);
656        }
657        let mut p = b.head;
658        while !p.is_null() {
659            let next = self.nodes.next(p);
660            if w.sync {
661                self.sync_hlist_node(p, cur_h, base_line);
662            }
663            let n = self.nodes.get(p);
664            let n_id = n.id;
665            match &n.data {
666                NodeData::Glyph(g) => {
667                    let (font, chr, xo, yo, ef) =
668                        (g.font, g.chr, g.xoffset, g.yoffset, g.expansion_factor);
669                    if !self.pdf.map_note_done
670                        && w.backend.is_some()
671                        && self.fonts.char_metrics(font, chr).is_some()
672                    {
673                        // Observed: LuaTeX reads the map file when the
674                        // first character is output; a character missing
675                        // from its font (in \nullfont, say) is not.
676                        self.pdf.map_note_done = true;
677                        self.print_map_file_note("pdftex.map");
678                    }
679                    self.check_lost_char(p);
680                    self.out_glyph(w, font, chr, cur_h + xo, base_line - yo, ef);
681                    cur_h +=
682                        crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
683                }
684                NodeData::Box(inner) => {
685                    let (iw, shift, head) = (inner.width, inner.shift, inner.head);
686                    if !head.is_null() {
687                        self.out_box(w, p, cur_h, base_line + shift);
688                    } else if w.sync {
689                        let size = [iw, inner.height, inner.depth];
690                        let f = self.sync_of(p);
691                        self.synctex
692                            .void_box(n_id == id::HLIST, f, cur_h, base_line, size);
693                    }
694                    cur_h += iw;
695                }
696                NodeData::Rule {
697                    width,
698                    depth,
699                    height,
700                    index,
701                    ..
702                } => {
703                    let (wd, ht, dpt, idx) = (*width, *height, *depth, *index);
704                    if n.subtype == crate::image::IMAGE_RULE {
705                        self.out_image(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
706                    } else if n.subtype == crate::image::BOX_RESOURCE_RULE {
707                        self.out_box_resource(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
708                    } else {
709                        Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, &b);
710                    }
711                    cur_h += wd;
712                    if w.sync {
713                        let f = self.sync_of(p);
714                        self.synctex.rule(f, cur_h, base_line, [wd, ht, dpt]);
715                    }
716                }
717                NodeData::Glue { spec, leader } => {
718                    let (spec, leader, sub) = (*spec, *leader, n.subtype);
719                    let wd = gs.advance(&spec);
720                    let box_leaders = sub >= glue_sub::A_LEADERS
721                        && !leader.is_null()
722                        && self.nodes.id(leader) != id::RULE;
723                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
724                        self.hlist_leaders(w, leader, sub, cur_h, left_edge, base_line, wd, &b);
725                    }
726                    cur_h += wd;
727                    if w.sync && !box_leaders {
728                        let f = self.sync_of(p);
729                        self.synctex.glue(f, cur_h, base_line);
730                    }
731                }
732                NodeData::Kern {
733                    kern,
734                    expansion_factor,
735                } => {
736                    let wd = kern + expansion_factor;
737                    if w.sync {
738                        // Observed: the width recorded is the kern's,
739                        // without font expansion.
740                        let f = self.sync_of(p);
741                        self.synctex.kern(f, *kern);
742                    }
743                    cur_h += wd;
744                }
745                NodeData::Math { surround, glue } => {
746                    let g = *glue;
747                    if w.sync {
748                        let f = self.sync_of(p);
749                        self.synctex.math(f, cur_h, base_line);
750                    }
751                    cur_h += surround;
752                    if g != Glue::ZERO {
753                        cur_h += gs.advance(&g);
754                    }
755                }
756                NodeData::MarginKern { width, .. } => cur_h += width,
757                NodeData::Whatsit(_) => self.out_what(w, p, cur_h, base_line, &ctx),
758                NodeData::Disc { replace, .. } => {
759                    // A discretionary that was not broken shows its
760                    // replacement text.
761                    let r = *replace;
762                    let mut q = r;
763                    while !q.is_null() {
764                        if let NodeData::Glyph(g) = &self.nodes.get(q).data {
765                            let (font, chr, ef) = (g.font, g.chr, g.expansion_factor);
766                            self.out_glyph(w, font, chr, cur_h, base_line, ef);
767                            if w.sync {
768                                self.synctex.glyph();
769                            }
770                            cur_h += crate::expansion::expanded_width(
771                                self.font_char_dimen(font, chr, 0),
772                                ef,
773                            );
774                        } else if let NodeData::Kern {
775                            kern,
776                            expansion_factor,
777                        } = &self.nodes.get(q).data
778                        {
779                            let wd = kern + expansion_factor;
780                            if w.sync {
781                                self.synctex.end_run(cur_h, base_line);
782                                let f = self.sync_of(q);
783                                self.synctex.kern(f, *kern);
784                            }
785                            cur_h += wd;
786                        }
787                        q = self.nodes.next(q);
788                    }
789                }
790                _ => {}
791            }
792            p = next;
793        }
794        if !w.doing_leaders {
795            self.ship_end_box_links(w, &ctx);
796        }
797        if w.sync {
798            self.synctex.end_run(cur_h, base_line);
799            let f = self.sync_of(this_box);
800            self.synctex.box_close(true, f, cur_h, base_line);
801        }
802        Self::leave_box(w);
803    }
804
805    /// SyncTeX before node `p` of an hlist: a glyph continues a run of
806    /// glyphs, the nodes that do not end one leave it, others end it.
807    #[inline]
808    fn sync_hlist_node(&mut self, p: NodeRef, cur_h: i32, base_line: i32) {
809        match self.nodes.id(p) {
810            id::GLYPH => self.synctex.glyph(),
811            id::MARK | id::INS | id::ADJUST | id::BOUNDARY | id::LOCAL_PAR => {}
812            _ => self.synctex.end_run(cur_h, base_line),
813        }
814    }
815
816    /// A rule in an hlist whose left end is at `cur_h` (TeX's `fin_rule`
817    /// in §624); running dimensions come from the enclosing box.
818    fn hlist_rule(
819        w: &mut Walker,
820        cur_h: i32,
821        base_line: i32,
822        wd: i32,
823        ht: i32,
824        dpt: i32,
825        this_box: &crate::nodes::BoxData,
826    ) {
827        let ht = if ht == RUNNING { this_box.height } else { ht };
828        let dpt = if dpt == RUNNING { this_box.depth } else { dpt };
829        let total = ht + dpt;
830        if total > 0 && wd > 0 {
831            Self::out_rule(w, cur_h, base_line + dpt, wd, total);
832        }
833    }
834
835    /// Outputs a vlist box (`vlist_out`).
836    fn vlist_out(&mut self, w: &mut Walker, this_box: NodeRef, cur_h: i32, cur_v: i32) {
837        let Some(b) = self.nodes.box_data(this_box).cloned() else {
838            return;
839        };
840        let mut gs = GlueState {
841            cur_g: 0,
842            cur_glue: 0.0,
843            order: b.glue_order,
844            sign: b.glue_sign,
845            set: b.glue_set,
846        };
847        Self::enter_box(w);
848        let left_edge = cur_h;
849        let ctx = crate::pdfship::BoxCtx {
850            left: left_edge,
851            base: cur_v,
852            width: b.width,
853            height: b.height,
854            depth: b.depth,
855        };
856        let mut cur_v = cur_v - b.height;
857        let top_edge = cur_v;
858        if w.sync {
859            let f = self.sync_of(this_box);
860            self.synctex
861                .box_open(false, f, cur_h, cur_v, [b.width, b.height, b.depth]);
862        }
863        let mut p = b.head;
864        while !p.is_null() {
865            let next = self.nodes.next(p);
866            let n = self.nodes.get(p);
867            match &n.data {
868                NodeData::Box(inner) => {
869                    let (ih, idp, shift, head) =
870                        (inner.height, inner.depth, inner.shift, inner.head);
871                    if head.is_null() {
872                        cur_v += ih + idp;
873                        if w.sync {
874                            let size = [inner.width, ih, idp];
875                            let hlist = n.id == id::HLIST;
876                            let f = self.sync_of(p);
877                            self.synctex.void_box(hlist, f, left_edge, cur_v, size);
878                        }
879                    } else {
880                        cur_v += ih;
881                        self.out_box(w, p, left_edge + shift, cur_v);
882                        cur_v += idp;
883                    }
884                }
885                NodeData::Rule {
886                    width,
887                    depth,
888                    height,
889                    index,
890                    ..
891                } => {
892                    let wd = if *width == RUNNING { b.width } else { *width };
893                    let total = height + depth;
894                    let idx = *index;
895                    cur_v += total;
896                    if n.subtype == crate::image::IMAGE_RULE {
897                        self.out_image(w, idx, left_edge, cur_v, wd, total);
898                    } else if n.subtype == crate::image::BOX_RESOURCE_RULE {
899                        self.out_box_resource(w, idx, left_edge, cur_v, wd, total);
900                    } else if total > 0 && wd > 0 {
901                        Self::out_rule(w, left_edge, cur_v, wd, total);
902                    }
903                }
904                NodeData::Glue { spec, leader } => {
905                    let (spec, leader, sub) = (*spec, *leader, n.subtype);
906                    let ht = gs.advance(&spec);
907                    if sub >= glue_sub::A_LEADERS && !leader.is_null() {
908                        cur_v =
909                            self.vlist_leaders(w, leader, sub, cur_v, left_edge, top_edge, ht, &b);
910                    } else {
911                        cur_v += ht;
912                    }
913                }
914                NodeData::Kern { kern, .. } => cur_v += kern,
915                NodeData::Whatsit(_) => self.out_what(w, p, left_edge, cur_v, &ctx),
916                _ => {}
917            }
918            p = next;
919        }
920        if w.sync {
921            let f = self.sync_of(this_box);
922            self.synctex.box_close(false, f, left_edge, cur_v);
923        }
924        Self::leave_box(w);
925    }
926
927    /// Where leaders of the given kind start and how much space follows
928    /// each copy (§626, §635): returns (start, gap) for a space of size
929    /// `size` at `cur` filled with boxes of size `step`.
930    fn leader_start(sub: u16, cur: i32, edge0: i32, size: i32, step: i32) -> (i32, i32) {
931        if sub == glue_sub::C_LEADERS || sub == glue_sub::X_LEADERS {
932            let lq = size / step;
933            let lr = size % step;
934            if sub == glue_sub::C_LEADERS {
935                (cur + lr / 2, 0)
936            } else {
937                let lx = lr / (lq + 1);
938                (cur + (lr - (lq - 1) * lx) / 2, lx)
939            }
940        } else {
941            let mut c = edge0 + step * ((cur - edge0) / step);
942            if c < cur {
943                c += step;
944            }
945            (c, 0)
946        }
947    }
948
949    /// Leaders in an hlist (§625–§628) filling `wd` from `cur_h`.
950    #[allow(clippy::too_many_arguments)]
951    fn hlist_leaders(
952        &mut self,
953        w: &mut Walker,
954        leader: NodeRef,
955        sub: u16,
956        cur_h: i32,
957        left_edge: i32,
958        base_line: i32,
959        wd: i32,
960        this_box: &crate::nodes::BoxData,
961    ) {
962        let lb = match &self.nodes.get(leader).data {
963            NodeData::Rule { height, depth, .. } => {
964                let (ht, dpt) = (*height, *depth);
965                Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, this_box);
966                return;
967            }
968            NodeData::Box(lb) => lb.clone(),
969            _ => return,
970        };
971        let leader_wd = lb.width;
972        if leader_wd <= 0 || wd <= 0 {
973            return;
974        }
975        let rule_wd = wd + 10; // compensates for floating-point rounding
976        let edge = cur_h + rule_wd;
977        let (mut h, lx) = Self::leader_start(sub, cur_h, left_edge, rule_wd, leader_wd);
978        // The box is output even if it is empty, which counts towards the
979        // DVI stack depth (§628).
980        while h + leader_wd <= edge {
981            let outer = w.doing_leaders;
982            w.doing_leaders = true;
983            self.out_box(w, leader, h, base_line + lb.shift);
984            w.doing_leaders = outer;
985            h += leader_wd + lx;
986        }
987    }
988
989    /// Leaders in a vlist (§634–§637) filling `ht` below `cur_v`; returns
990    /// the new `cur_v`.
991    #[allow(clippy::too_many_arguments)]
992    fn vlist_leaders(
993        &mut self,
994        w: &mut Walker,
995        leader: NodeRef,
996        sub: u16,
997        cur_v: i32,
998        left_edge: i32,
999        top_edge: i32,
1000        ht: i32,
1001        this_box: &crate::nodes::BoxData,
1002    ) -> i32 {
1003        let lb = match &self.nodes.get(leader).data {
1004            NodeData::Rule { width, .. } => {
1005                let wd = if *width == RUNNING {
1006                    this_box.width
1007                } else {
1008                    *width
1009                };
1010                if ht > 0 && wd > 0 {
1011                    Self::out_rule(w, left_edge, cur_v + ht, wd, ht);
1012                }
1013                return cur_v + ht;
1014            }
1015            NodeData::Box(lb) => lb.clone(),
1016            _ => return cur_v + ht,
1017        };
1018        let leader_ht = lb.height + lb.depth;
1019        if leader_ht <= 0 || ht <= 0 {
1020            return cur_v + ht;
1021        }
1022        let rule_ht = ht + 10; // compensates for floating-point rounding
1023        let edge = cur_v + rule_ht;
1024        let (mut v, lx) = Self::leader_start(sub, cur_v, top_edge, rule_ht, leader_ht);
1025        while v + leader_ht <= edge {
1026            let outer = w.doing_leaders;
1027            w.doing_leaders = true;
1028            self.out_box(w, leader, left_edge + lb.shift, v + lb.height);
1029            w.doing_leaders = outer;
1030            v += leader_ht + lx;
1031        }
1032        edge - 10
1033    }
1034
1035    /// Executes a whatsit at TeX position (`h`, `v`) (`out_what`).
1036    fn out_what(
1037        &mut self,
1038        w: &mut Walker,
1039        p: NodeRef,
1040        h: i32,
1041        v: i32,
1042        ctx: &crate::pdfship::BoxCtx,
1043    ) {
1044        let NodeData::Whatsit(wh) = &self.nodes.get(p).data else {
1045            return;
1046        };
1047        let (x, y) = w.pos(h, v);
1048        match (**wh).clone() {
1049            Whatsit::Open { stream, name } if !w.doing_leaders => {
1050                self.open_write_stream(stream as i32, &name)
1051            }
1052            Whatsit::Write { stream, data } if !w.doing_leaders => self.write_out(stream, &data),
1053            Whatsit::Close { stream } if !w.doing_leaders => self.close_write_stream(stream as i32),
1054            Whatsit::Special { data } => {
1055                if let Some(d) = w.dvi.as_mut() {
1056                    d.special(w.dvi_h + h, w.dvi_v + v, &data);
1057                } else if let Some(be) = w.backend.as_deref_mut() {
1058                    // Observed in PDF mode: `pdf:` specials (any case) are
1059                    // literals, with an optional mode keyword; every other
1060                    // special is dropped with a warning.
1061                    if data.len() >= 4 && data[..4].eq_ignore_ascii_case(b"pdf:") {
1062                        let rest = &data[4..];
1063                        let (mode, text) = [
1064                            (&b"direct:"[..], 2),
1065                            (b"page:", 1),
1066                            (b"raw:", 3),
1067                            (b"text:", 4),
1068                            (b"origin:", 0),
1069                        ]
1070                        .iter()
1071                        .find_map(|(k, m)| rest.strip_prefix(*k).map(|t| (*m, t)))
1072                        .unwrap_or((crate::pdfext::literal_mode::ORIGIN, rest));
1073                        be.literal(mode, text, x, y);
1074                    } else {
1075                        self.printer.print_nl("Non-PDF special ignored!");
1076                    }
1077                }
1078            }
1079            Whatsit::PdfLiteral { mode, data, .. } => {
1080                let bytes = match data {
1081                    crate::nodes::LateData::Bytes(b)
1082                    | crate::nodes::LateData::Lua { data: b, .. } => b,
1083                    crate::nodes::LateData::Tokens(t) => {
1084                        let toks = self.expand_late_tokens(&t);
1085                        self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
1086                    }
1087                };
1088                if let Some(be) = w.backend.as_deref_mut() {
1089                    be.literal(mode, &bytes, x, y);
1090                }
1091            }
1092            Whatsit::LateLua { data, .. } => {
1093                let code = match data {
1094                    crate::nodes::LateData::Bytes(b)
1095                    | crate::nodes::LateData::Lua { data: b, .. } => b,
1096                    crate::nodes::LateData::Tokens(t) => {
1097                        let toks = self.expand_late_tokens(&t);
1098                        self.lua_code(&toks)
1099                    }
1100                };
1101                // `pdf.getpos`, `pdf.print` and `pdf.registerannot` work on
1102                // this context while the code runs.
1103                self.pdf.pos = (x, y);
1104                let outer = self.pdf.late.replace(Default::default());
1105                self.run_lua(&code);
1106                let ctx = std::mem::replace(&mut self.pdf.late, outer).unwrap_or_default();
1107                if let Some(be) = w.backend.as_deref_mut() {
1108                    for (mode, text) in &ctx.prints {
1109                        be.literal(*mode, text, x, y);
1110                    }
1111                }
1112                self.pdf
1113                    .page
1114                    .annots
1115                    .extend(ctx.annots.into_iter().map(|a| (a, None)));
1116            }
1117            Whatsit::PdfRefobj { objnum } if !w.doing_leaders => self.ship_refobj(w, objnum),
1118            Whatsit::PdfAnnot { dims, objnum, data } if !w.doing_leaders => {
1119                self.ship_annot(w, ctx, h, v, dims, objnum, &data)
1120            }
1121            Whatsit::PdfStartLink {
1122                dims,
1123                objnum,
1124                attr,
1125                action,
1126            } if !w.doing_leaders => {
1127                self.ship_start_link(w, ctx, h, v, dims, objnum, &attr, &action)
1128            }
1129            Whatsit::PdfEndLink if !w.doing_leaders => self.ship_end_link(w, h, v),
1130            Whatsit::PdfDest {
1131                id,
1132                view,
1133                structure,
1134            } if !w.doing_leaders => self.ship_dest(w, ctx, h, v, &id, &view, structure),
1135            Whatsit::SavePos => {
1136                self.last_x_pos = x;
1137                self.last_y_pos = y;
1138            }
1139            Whatsit::PdfSave => {
1140                if let Some(be) = w.backend.as_deref_mut() {
1141                    be.save(x, y);
1142                }
1143            }
1144            Whatsit::PdfRestore => {
1145                if let Some(be) = w.backend.as_deref_mut() {
1146                    be.restore(x, y);
1147                }
1148            }
1149            Whatsit::PdfSetmatrix { data } => {
1150                if let Some(be) = w.backend.as_deref_mut() {
1151                    be.set_matrix(x, y, &data);
1152                }
1153            }
1154            Whatsit::PdfColorstack { stack, cmd, data } => {
1155                let out = self.colorstack_op(stack, cmd, data);
1156                if let (Some((mode, lit)), Some(be)) = (out, w.backend.as_deref_mut()) {
1157                    be.literal(mode, &lit, x, y);
1158                }
1159            }
1160            _ => {}
1161        }
1162    }
1163
1164    /// Applies a colour-stack command; returns the literal to emit.
1165    fn colorstack_op(&mut self, stack: i32, cmd: u8, data: Vec<u8>) -> Option<(u8, Vec<u8>)> {
1166        use crate::pdfext::colorstack_cmd as c;
1167        self.pdf_ensure_colorstacks();
1168        let def = self.pdf.colorstacks.get(stack.max(0) as usize).cloned()?;
1169        let st = self
1170            .colorstack_values
1171            .entry(stack)
1172            .or_insert_with(|| vec![def.init.clone()]);
1173        match cmd {
1174            c::SET => {
1175                if let Some(top) = st.last_mut() {
1176                    *top = data;
1177                } else {
1178                    st.push(data);
1179                }
1180            }
1181            c::PUSH => st.push(data),
1182            c::POP => {
1183                if st.len() > 1 {
1184                    st.pop();
1185                }
1186            }
1187            _ => {}
1188        }
1189        let lit = st.last().cloned().unwrap_or_default();
1190        Some((def.mode, lit))
1191    }
1192
1193    /// Expands a late token list (as `\write` does) and returns the
1194    /// expanded tokens.
1195    fn expand_late_tokens(&mut self, list: &crate::token::TokenList) -> Vec<crate::token::Token> {
1196        use crate::token::Token;
1197        use std::rc::Rc;
1198        self.ins_list(Rc::from(vec![
1199            Token::RIGHT_BRACE,
1200            Token::cs(self.cs.end_write),
1201        ]));
1202        self.begin_token_list(list.clone(), crate::input::TokKind::WriteText);
1203        self.ins_list(Rc::from(vec![Token::LEFT_BRACE]));
1204        let toks = self.scan_toks(false, true);
1205        self.get_token();
1206        toks
1207    }
1208
1209    /// Finishes the output file at the end of the run and reports it.
1210    pub fn finish_backend(&mut self) {
1211        if self.dvi.pages() > 0 {
1212            self.finish_dvi();
1213        } else if self.pdf.output_opened && self.eqtb.int(ip::OUTPUTMODE) <= 0 {
1214            // LuaTeX opened the DVI file for `pdf.obj` and leaves it empty.
1215            let _ = std::fs::write(self.output_path("dvi"), b"");
1216        }
1217        let Some(mut be) = self.backend.take() else {
1218            return;
1219        };
1220        if self.pdf.fatal || self.total_pages == 0 {
1221            be.abandon(self.pdf.fatal);
1222            self.backend = Some(be);
1223            return;
1224        }
1225        if self.eqtb.int(ip::DRAFTMODE) > 0 && self.eqtb.int(ip::OUTPUTMODE) > 0 {
1226            // `-draftmode`: the document is typeset but no PDF is written
1227            // (the backend wrote to a scratch file, removed here). LuaTeX
1228            // prints this warning; unlike LuaTeX, which empties an
1229            // existing PDF file, retex leaves it as it is.
1230            be.abandon(true);
1231            self.backend = Some(be);
1232            self.printer
1233                .print_nl("warning  (pdf backend): draftmode enabled, not changing output pdf");
1234            self.printer.print_ln();
1235            return;
1236        }
1237        if self.total_pages > 0 {
1238            self.pdf_finish_document();
1239            self.flush_pending_objects(be.as_mut());
1240        }
1241        if self.total_pages > 0
1242            && let Some(fin) = be.finish(self)
1243        {
1244            // Observed: printed one after the other like C strings (log
1245            // lines up to 80 characters), right after what came before.
1246            for f in &fin.embedded {
1247                let mut text = b"<".to_vec();
1248                text.extend_from_slice(f.as_bytes());
1249                text.push(b'>');
1250                self.printer.print_lua_bytes(&text);
1251            }
1252            self.pdf.objstm_stats = fin.objstm;
1253            self.print_output_written(&fin.file, self.total_pages, fin.size);
1254        }
1255        self.backend = Some(be);
1256    }
1257
1258    /// The path of output file `<jobname>.<ext>` (in `-output-directory`).
1259    pub(crate) fn output_path(&self, ext: &str) -> std::path::PathBuf {
1260        let job =
1261            String::from_utf8_lossy(self.job_name.as_deref().unwrap_or(b"texput")).into_owned();
1262        let name = format!("{job}.{ext}");
1263        match &self.files.output_dir {
1264            Some(d) => d.join(&name),
1265            None => std::path::PathBuf::from(&name),
1266        }
1267    }
1268
1269    /// Writes the postamble and the DVI file `<jobname>.dvi` (§642).
1270    fn finish_dvi(&mut self) {
1271        let mut dvi = std::mem::take(&mut self.dvi);
1272        let pages = dvi.pages();
1273        let data = dvi.finish(self);
1274        let path = self.output_path("dvi");
1275        let name = path
1276            .file_name()
1277            .map(|n| n.to_string_lossy().into_owned())
1278            .unwrap_or_default();
1279        if std::fs::write(&path, &data).is_err() {
1280            self.printer.print_nl("! I can't write on file `");
1281            self.printer.print_bytes(name.as_bytes());
1282            self.printer.print_str("'.");
1283            return;
1284        }
1285        self.print_output_written(&name, pages, data.len() as u64);
1286    }
1287
1288    /// Prints `Output written on NAME (N pages, B bytes).`.
1289    fn print_output_written(&mut self, name: &str, pages: u32, size: u64) {
1290        self.output_written = Some((name.to_owned(), pages, size));
1291        self.printer.print_nl("Output written on ");
1292        self.printer.print_bytes(name.as_bytes());
1293        self.printer.print_str(" (");
1294        self.printer.print_int(i64::from(pages));
1295        self.printer
1296            .print_str(if pages == 1 { " page" } else { " pages" });
1297        self.printer.print_str(", ");
1298        self.printer.print_int(size as i64);
1299        self.printer.print_str(" bytes).");
1300    }
1301}
1302
1303/// The state of a page being shipped: the outputs and the coordinate
1304/// systems they use.
1305pub(crate) struct Walker {
1306    /// The PDF backend (PDF mode).
1307    backend: Option<Box<dyn Backend>>,
1308    /// The DVI writer (DVI mode).
1309    dvi: Option<crate::dvi::DviWriter>,
1310    h_origin: i32,
1311    v_origin: i32,
1312    page_height: i32,
1313    /// The DVI position of TeX's origin (`\hoffset`, `\voffset`).
1314    dvi_h: i32,
1315    dvi_v: i32,
1316    /// Box nesting depth (`cur_s`): -1 outside the page box.
1317    pub(crate) cur_s: i32,
1318    /// Whether a leader box is being output (`doing_leaders`).
1319    doing_leaders: bool,
1320    /// Whether this is a page (not a box resource): annotations, links
1321    /// and destinations belong to pages.
1322    pub(crate) page: bool,
1323    /// Whether SyncTeX records this sheet.
1324    sync: bool,
1325}
1326
1327impl Walker {
1328    /// Page coordinates (from the lower left, y up) of TeX point
1329    /// (`h`, `v`) measured from the origin, v downwards.
1330    pub(crate) fn pos(&self, h: i32, v: i32) -> (i32, i32) {
1331        (self.h_origin + h, self.page_height - (self.v_origin + v))
1332    }
1333}