Skip to main content

retex_engine/
shipout.rs

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