Skip to main content

retex_engine/
pdfext.rs

1//! `\pdfextension` (LuaTeX manual §3.2.2): PDF literals, matrices,
2//! colour stacks, objects, annotations, links, destinations, outlines and
3//! document-level dictionaries.
4//!
5//! Commands that act at shipout become whatsit nodes in the current list;
6//! document-level data is collected in [`PdfState`] for the backend.
7//!
8//! # Object numbers
9//!
10//! The engine and the PDF writer share one object counter
11//! ([`PdfState::counter`]), so the numbers TeX and Lua see
12//! (`\pdffeedback lastobj`, `pdf.getpageref`, …) are those in the file.
13//! Observed in LuaTeX 1.22:
14//!
15//! * `\pdfextension annot` and `startlink` take their number when they are
16//!   scanned; continuation rectangles of a link get theirs at shipout.
17//! * A named or numbered destination gets its number the first time it is
18//!   referred to (by an outline or open action when it is scanned, by a
19//!   link when the page is written) or met at shipout, whichever comes
20//!   first. Pages likewise (`goto page`, `\pdffeedback pageref`).
21//! * `\pdfextension outline` allocates the action (written at once), the
22//!   numbers of the destination it refers to, the entry, and the title
23//!   (written at once as `(title)` and a newline).
24//! * Destinations that were referred to but never defined are written at
25//!   the end as `[ last-page 0 R /Fit ]`, in the reverse order of their
26//!   allocation, each with a warning.
27
28use std::cell::Cell;
29use std::collections::HashMap;
30use std::rc::Rc;
31
32use crate::Engine;
33use crate::nodes::{LateData, NodeData, RUNNING, Whatsit, id, whatsit_sub};
34use crate::token::TokenList;
35
36/// Literal modes (`mode` of `pdf_literal` whatsits).
37#[allow(missing_docs)]
38pub mod literal_mode {
39    pub const ORIGIN: u8 = 0;
40    pub const PAGE: u8 = 1;
41    pub const DIRECT: u8 = 2;
42    pub const RAW: u8 = 3;
43}
44
45/// Colour-stack commands.
46#[allow(missing_docs)]
47pub mod colorstack_cmd {
48    pub const SET: u8 = 0;
49    pub const PUSH: u8 = 1;
50    pub const POP: u8 = 2;
51    pub const CURRENT: u8 = 3;
52}
53
54/// A PDF object created with `\pdfextension obj`.
55#[derive(Clone, Debug, Default)]
56pub struct PdfObject {
57    /// The PDF object number.
58    pub num: i32,
59    /// The object content (dictionary text, or stream data).
60    pub data: Vec<u8>,
61    /// Whether this is a stream object.
62    pub stream: bool,
63    /// Stream dictionary entries (`attr {…}`).
64    pub attr: Vec<u8>,
65    /// Whether `data` names a file whose content is the object.
66    pub file: bool,
67    /// Whether the object must not go into an object stream.
68    pub uncompressed: bool,
69    /// Whether the content has been given (not just reserved).
70    pub defined: bool,
71    /// Whether it has been written (immediately or at shipout).
72    pub written: bool,
73}
74
75/// An outline entry (`\pdfextension outline`).
76#[derive(Clone, Debug, Default)]
77pub struct Outline {
78    /// Extra dictionary entries.
79    pub attr: Vec<u8>,
80    /// The entry's object.
81    pub objnum: i32,
82    /// The title object.
83    pub title: i32,
84    /// The action object.
85    pub action: i32,
86    /// `count` (number of direct children; negative means closed).
87    pub count: i32,
88}
89
90/// Colour-stack definition.
91#[derive(Clone, Debug)]
92pub struct ColorStack {
93    /// Whether the stack's current value is written at the start of every
94    /// page (`page` keyword).
95    pub page: bool,
96    /// The literal mode of its literals (observed: `direct` for stack 0,
97    /// whose literals go inside a text object without ending it;
98    /// `origin` for stacks declared without `direct`).
99    pub mode: u8,
100    /// The initial value.
101    pub init: Vec<u8>,
102}
103
104/// Kinds of PDF object numbers, as `pdf.getobjtype` names them.
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum ObjKind {
107    /// `\pdfextension obj`, `pdf.obj`, `pdf.reserveobj()`.
108    Obj,
109    /// `\pdfextension annot`, `pdf.reserveobj("annot")`.
110    Annot,
111    /// Links (`\pdfextension startlink`); LuaTeX reports `others`.
112    Link,
113    /// Page objects (`pdf.getpageref`, `\pdffeedback pageref`).
114    Page,
115    /// Font objects (`pdf.getfontobjnum`, `\pdffeedback fontobjnum`).
116    Font,
117    /// Image XObjects (`\saveimageresource`).
118    Ximage,
119    /// Box resources (`\saveboxresource`).
120    Xform,
121    /// Destinations, outline entries, actions, threads.
122    Other,
123}
124
125impl ObjKind {
126    /// The name returned by `pdf.getobjtype`.
127    pub fn name(self) -> &'static str {
128        match self {
129            ObjKind::Obj => "obj",
130            ObjKind::Annot => "annot",
131            ObjKind::Link | ObjKind::Other => "others",
132            ObjKind::Page => "page",
133            ObjKind::Font => "font",
134            ObjKind::Ximage => "ximage",
135            ObjKind::Xform => "xform",
136        }
137    }
138}
139
140/// Dictionary texts set from Lua (`pdf.setcatalog` and friends). They are
141/// independent of the token lists set at the TeX end and are written after
142/// them; `None` means never set (the getters return `nil`).
143#[derive(Clone, Debug, Default)]
144pub struct LuaPdfTexts {
145    /// `pdf.setcatalog`.
146    pub catalog: Option<Vec<u8>>,
147    /// `pdf.setinfo`.
148    pub info: Option<Vec<u8>>,
149    /// `pdf.setnames`.
150    pub names: Option<Vec<u8>>,
151    /// `pdf.settrailer` (observed: LuaTeX 1.22 does not write it).
152    pub trailer: Option<Vec<u8>>,
153    /// `pdf.settrailerid`.
154    pub trailer_id: Option<Vec<u8>>,
155    /// `pdf.setpageattributes`.
156    pub page_attributes: Option<Vec<u8>>,
157    /// `pdf.setpageresources`.
158    pub page_resources: Option<Vec<u8>>,
159    /// `pdf.setpagesattributes`.
160    pub pages_attributes: Option<Vec<u8>>,
161    /// `pdf.setxformattributes`.
162    pub xform_attributes: Option<Vec<u8>>,
163    /// `pdf.setxformresources`.
164    pub xform_resources: Option<Vec<u8>>,
165}
166
167/// State of the `\latelua` call being executed at shipout.
168#[derive(Clone, Debug, Default)]
169pub struct LateLuaContext {
170    /// Literals queued by `pdf.print`: (literal mode, text).
171    pub prints: Vec<(u8, Vec<u8>)>,
172    /// Annotations added with `pdf.registerannot`.
173    pub annots: Vec<i32>,
174}
175
176/// Identifies a destination (`name {…}` or `num n`).
177#[derive(Clone, Debug, PartialEq, Eq, Hash)]
178pub enum DestId {
179    /// `name {…}`.
180    Name(Vec<u8>),
181    /// `num n`.
182    Num(i32),
183}
184
185impl DestId {
186    /// How warnings name the identifier: `name 'x'` or `num '3'`.
187    fn describe(&self) -> String {
188        match self {
189            DestId::Name(n) => format!("name '{}'", String::from_utf8_lossy(n)),
190            DestId::Num(n) => format!("num '{n}'"),
191        }
192    }
193}
194
195/// The destination types of `\pdfextension dest`.
196#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum DestKind {
198    /// `xyz`.
199    Xyz,
200    /// `fit`.
201    Fit,
202    /// `fith`.
203    FitH,
204    /// `fitv`.
205    FitV,
206    /// `fitb`.
207    FitB,
208    /// `fitbh`.
209    FitBH,
210    /// `fitbv`.
211    FitBV,
212    /// `fitr`.
213    FitR,
214}
215
216/// The view of `\pdfextension dest`.
217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub struct DestView {
219    /// The type.
220    pub kind: DestKind,
221    /// `zoom` for `xyz` (0 when not given).
222    pub zoom: i32,
223    /// `width`, `height`, `depth` for `fitr` ([`RUNNING`] when not given).
224    pub dims: [i32; 3],
225}
226
227/// What an action refers to.
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub enum ActionTarget {
230    /// `name {…}`.
231    Name(Vec<u8>),
232    /// `num n`.
233    Num(i32),
234    /// `page n {view}`.
235    Page(i32, Vec<u8>),
236}
237
238/// An action specification (`user`, `goto`, `thread`).
239#[derive(Clone, Debug, PartialEq, Eq)]
240pub enum PdfAction {
241    /// `user {…}`.
242    User(Vec<u8>),
243    /// `goto [file {…}] [struct …] …`.
244    Goto {
245        /// `file {…}`.
246        file: Option<Vec<u8>>,
247        /// `struct name {…}` or `struct num n`: the structure destination
248        /// (`/SD`).
249        structure: Option<ActionTarget>,
250        /// The target.
251        target: ActionTarget,
252        /// `newwindow` (true) or `nonewwindow` (false).
253        new_window: Option<bool>,
254    },
255    /// `thread [file {…}] …`.
256    Thread {
257        /// `file {…}`.
258        file: Option<Vec<u8>>,
259        /// The target.
260        target: ActionTarget,
261    },
262}
263
264/// A destination's view in page coordinates (sp), for the backend.
265#[derive(Clone, Copy, Debug, PartialEq, Eq)]
266pub enum DestPos {
267    /// `/XYZ x y zoom` (zoom in thousandths, 0 for `null`).
268    Xyz {
269        /// Left.
270        x: i32,
271        /// Top.
272        y: i32,
273        /// Zoom times 1000.
274        zoom: i32,
275    },
276    /// `/Fit`.
277    Fit,
278    /// `/FitB`.
279    FitB,
280    /// `/FitH y`.
281    FitH(i32),
282    /// `/FitBH y`.
283    FitBH(i32),
284    /// `/FitV x`.
285    FitV(i32),
286    /// `/FitBV x`.
287    FitBV(i32),
288    /// `/FitR x1 y1 x2 y2`.
289    FitR([i32; 4]),
290}
291
292/// One rectangle of a link, as written.
293#[derive(Clone, Debug)]
294pub struct LinkPiece {
295    /// The annotation's object number.
296    pub objnum: i32,
297    /// The rectangle (sp, page coordinates).
298    pub rect: [i32; 4],
299    /// `attr {…}`.
300    pub attr: Vec<u8>,
301    /// The action text: a dictionary, or the user text.
302    pub action: Vec<u8>,
303    /// Whether `action` is user text.
304    pub user: bool,
305    /// The action, turned into `action` when the page is written (LuaTeX
306    /// allocates the destinations links refer to then).
307    pub source: PdfAction,
308}
309
310/// A link whose `\pdfextension endlink` has not been shipped yet.
311#[derive(Clone, Debug)]
312pub struct ActiveLink {
313    /// Box nesting level (`cur_s`) of the box that holds the startlink;
314    /// every later hlist at that level gets a rectangle of its own.
315    pub level: i32,
316    /// The rectangle being built on the current page, if any.
317    pub piece: Option<usize>,
318    /// Whether that rectangle is the first one, whose right edge is still
319    /// open.
320    pub first_open: bool,
321    /// `\pdfvariable linkmargin` at the start of the link.
322    pub margin: i32,
323    /// Whether the current rectangle continues the link from an earlier
324    /// box (it then ends at the endlink without margin).
325    pub continued: bool,
326    /// `attr {…}`.
327    pub attr: Vec<u8>,
328    /// The action.
329    pub action: PdfAction,
330}
331
332/// An annotation's rectangle (sp, page coordinates) and text.
333pub type AnnotBody = ([i32; 4], Vec<u8>);
334
335/// The interactive items of the page being shipped, handed to the
336/// backend when the page ends.
337#[derive(Clone, Debug, Default)]
338pub struct PageItems {
339    /// Annotations: object number and, unless written by other means
340    /// (`pdf.registerannot`), rectangle and text.
341    pub annots: Vec<(i32, Option<AnnotBody>)>,
342    /// Link rectangles, in the order they were started.
343    pub links: Vec<LinkPiece>,
344    /// Destinations: object number, whether named, view, and for a
345    /// structure destination the structure element's object.
346    pub dests: Vec<(i32, bool, DestPos, Option<i32>)>,
347    /// Objects to write with the page (`\pdfextension refobj`).
348    pub objects: Vec<PdfObject>,
349    /// Page attributes: the TeX and the Lua text.
350    pub attr: (Vec<u8>, Vec<u8>),
351    /// Page resources: the TeX and the Lua text.
352    pub resources: (Vec<u8>, Vec<u8>),
353}
354
355/// Document-level items computed at the end of the run for the backend.
356#[derive(Clone, Debug, Default)]
357pub struct DocItems {
358    /// Destinations referred to but never defined, in writing order.
359    pub missing_dests: Vec<i32>,
360    /// Threads referred to but never defined: object and title.
361    pub missing_threads: Vec<(i32, Vec<u8>)>,
362    /// Named destinations sorted by name.
363    pub dest_names: Vec<(Vec<u8>, i32)>,
364    /// `\pdfvariable pagesattr` at the end of the run.
365    pub pages_attr: Vec<u8>,
366    /// `\pdfvariable trailerid` at the end of the run.
367    pub trailer_id: Vec<u8>,
368}
369
370/// A destination known by its identifier.
371#[derive(Clone, Copy, Debug, Default)]
372pub struct DestEntry {
373    /// The object number.
374    pub objnum: i32,
375    /// Whether `\pdfextension dest` was shipped for it.
376    pub defined: bool,
377}
378
379/// Document-level PDF state.
380#[derive(Clone, Debug, Default)]
381pub struct PdfState {
382    /// Images written while shipping the current page, in order of first
383    /// use: their file names are printed at the end of the page.
384    pub page_images: Vec<u32>,
385    /// Objects by PDF object number.
386    pub objects: std::collections::BTreeMap<i32, PdfObject>,
387    /// `\pdfextension catalog` text.
388    pub catalog: Vec<u8>,
389    /// The catalog's open action (object number).
390    pub open_action: Option<i32>,
391    /// `\pdfextension info` text.
392    pub info: Vec<u8>,
393    /// `\pdfextension names` text.
394    pub names: Vec<u8>,
395    /// `\pdfextension trailer` text.
396    pub trailer: Vec<u8>,
397    /// Outline entries in order.
398    pub outlines: Vec<Outline>,
399    /// `\pdfextension glyphtounicode` pairs.
400    pub glyph_to_unicode: Vec<(Vec<u8>, Vec<u8>)>,
401    /// Colour stacks; stack 0 is the predefined colour stack.
402    pub colorstacks: Vec<ColorStack>,
403    /// `\pdffeedback lastobj`.
404    pub last_obj: i32,
405    /// `\pdffeedback lastannot`.
406    pub last_annot: i32,
407    /// `\pdffeedback lastlink`.
408    pub last_link: i32,
409    /// The highest object number allocated so far, shared with the PDF
410    /// writer.
411    pub counter: Rc<Cell<u32>>,
412    /// The kind of each allocated object number.
413    pub kinds: HashMap<i32, ObjKind>,
414    /// Page object numbers by page number (forward references included).
415    pub page_refs: HashMap<i32, i32>,
416    /// PDF fonts (resource name number, object number) by font, see
417    /// [`Engine::pdf_font`].
418    pub fonts: HashMap<u32, (u32, i32)>,
419    /// `\pdfextension fontattr` and `pdf.setfontattributes` texts by font.
420    pub font_attrs: HashMap<u32, Vec<u8>>,
421    /// Map files and map lines (`pdf.mapfile`, `pdf.mapline`,
422    /// `\pdfextension mapfile`/`mapline`), in order.
423    pub map_entries: Vec<Vec<u8>>,
424    /// Texts set from Lua.
425    pub lua: LuaPdfTexts,
426    /// `\pdffeedback retval`.
427    pub retval: i32,
428    /// The position (PDF coordinates, sp) of the last `\latelua`, for
429    /// `pdf.getpos`.
430    pub pos: (i32, i32),
431    /// The `\latelua` being executed, if any.
432    pub late: Option<LateLuaContext>,
433    /// Annotations registered from Lua for the page being shipped.
434    pub page_annots: Vec<i32>,
435    /// Whether `pdf.obj` opened the output file (in DVI mode LuaTeX then
436    /// leaves an empty DVI file when no page is shipped out).
437    pub output_opened: bool,
438    /// Destinations by identifier.
439    pub dests: HashMap<DestId, DestEntry>,
440    /// Destination identifiers in the order their numbers were allocated.
441    pub dest_order: Vec<DestId>,
442    /// Structure destinations by identifier (`\pdfextension dest struct`).
443    pub struct_dests: HashMap<DestId, DestEntry>,
444    /// Threads referred to by actions: identifier and object number.
445    pub threads: Vec<(DestId, i32)>,
446    /// The interactive items of the page being shipped.
447    pub page: PageItems,
448    /// Objects to write as soon as the backend is available again (they
449    /// were written during shipout, while the backend is busy).
450    pub pending: Vec<PdfObject>,
451    /// Document-level items for the backend, computed at the end.
452    pub doc: DocItems,
453    /// A fatal backend error occurred: no PDF is written.
454    pub fatal: bool,
455    /// Objects written into object streams and the number of streams.
456    pub objstm_stats: (u32, u32),
457    /// Whether a stream (form, image or stream object) was written
458    /// immediately: with no pages, LuaTeX then fails at the end
459    /// ("already written content discarded").
460    pub stream_written: bool,
461    /// Words of extra memory LuaTeX reports for objects (see
462    /// [`Engine::pdf_define_object`]).
463    pub obj_words: usize,
464    /// Whether the font map note (`{…/pdftex.map}`) was printed.
465    pub map_note_done: bool,
466    /// Links started but not ended yet, innermost last.
467    pub active_links: Vec<ActiveLink>,
468}
469
470impl PdfState {
471    /// The highest object number allocated so far.
472    pub fn max_obj(&self) -> i32 {
473        self.counter.get() as i32
474    }
475
476    /// The number of named destinations (for the PDF statistics).
477    pub fn named_dests(&self) -> usize {
478        self.dests
479            .keys()
480            .filter(|k| matches!(k, DestId::Name(_)))
481            .count()
482    }
483}
484
485impl Engine {
486    /// Scans a balanced text (expanded) and returns its characters.
487    fn scan_pdf_string(&mut self) -> Vec<u8> {
488        let toks = self.scan_toks_expanded();
489        self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
490    }
491
492    /// Allocates a PDF object number of kind `kind`.
493    pub fn pdf_alloc_obj(&mut self, kind: ObjKind) -> i32 {
494        let n = self.pdf.counter.get() + 1;
495        self.pdf.counter.set(n);
496        let n = n as i32;
497        self.pdf.kinds.insert(n, kind);
498        n
499    }
500
501    /// The kind of object number `n`, if it was allocated.
502    pub fn pdf_obj_kind(&self, n: i32) -> Option<ObjKind> {
503        self.pdf.kinds.get(&n).copied()
504    }
505
506    /// The object number of page `page` (counting from 1), allocated on
507    /// first request (`pdf.getpageref`, `\pdffeedback pageref`).
508    pub fn pdf_page_ref(&mut self, page: i32) -> i32 {
509        if let Some(&n) = self.pdf.page_refs.get(&page) {
510            return n;
511        }
512        let n = self.pdf_alloc_obj(ObjKind::Page);
513        self.pdf.page_refs.insert(page, n);
514        n
515    }
516
517    /// The object number of destination `id`, allocated on first use.
518    pub fn pdf_dest_obj(&mut self, id: &DestId) -> i32 {
519        if let Some(d) = self.pdf.dests.get(id) {
520            return d.objnum;
521        }
522        let n = self.pdf_alloc_obj(ObjKind::Other);
523        self.pdf.dests.insert(
524            id.clone(),
525            DestEntry {
526                objnum: n,
527                defined: false,
528            },
529        );
530        self.pdf.dest_order.push(id.clone());
531        n
532    }
533
534    /// The object number of structure destination `id`, allocated on
535    /// first use.
536    pub fn pdf_struct_dest_obj(&mut self, id: &DestId) -> i32 {
537        if let Some(d) = self.pdf.struct_dests.get(id) {
538            return d.objnum;
539        }
540        let n = self.pdf_alloc_obj(ObjKind::Other);
541        self.pdf.struct_dests.insert(
542            id.clone(),
543            DestEntry {
544                objnum: n,
545                defined: false,
546            },
547        );
548        n
549    }
550
551    /// The object number of thread `id`, allocated on first use.
552    fn pdf_thread_obj(&mut self, id: DestId) -> i32 {
553        if let Some((_, n)) = self.pdf.threads.iter().find(|(t, _)| *t == id) {
554            return *n;
555        }
556        let n = self.pdf_alloc_obj(ObjKind::Other);
557        self.pdf.threads.push((id, n));
558        n
559    }
560
561    /// The PDF font of font `f`: the number in its resource name `/F<n>`
562    /// and its object number. Fonts with the same name share one PDF font.
563    /// It is created when first asked for (by `\pdffeedback fontname` or
564    /// `fontobjnum`, or their Lua counterparts), named after the font
565    /// asked for, and gets an object number then (observed: asking for
566    /// `cmr10 at 12pt` before `cmr10` names both after the former).
567    pub fn pdf_font(&mut self, f: u32) -> (u32, i32) {
568        if let Some(&r) = self.pdf.fonts.get(&f) {
569            return r;
570        }
571        let name = self.font(f).name.clone();
572        let shared = self
573            .pdf
574            .fonts
575            .iter()
576            .filter(|(g, _)| self.font_exists(**g as i32) && self.font(**g).name == name)
577            .map(|(_, r)| *r)
578            .min();
579        let r = match shared {
580            Some(r) => r,
581            None => (f, self.pdf_alloc_obj(ObjKind::Font)),
582        };
583        self.pdf.fonts.insert(f, r);
584        r
585    }
586
587    /// Makes sure the predefined colour stack 0 exists.
588    pub fn pdf_ensure_colorstacks(&mut self) {
589        if self.pdf.colorstacks.is_empty() {
590            self.pdf.colorstacks.push(ColorStack {
591                page: true,
592                mode: literal_mode::DIRECT,
593                init: b"0 g 0 G".to_vec(),
594            });
595        }
596    }
597
598    /// Defines a new colour stack and returns its number; stack 0 is the
599    /// predefined one (`\pdffeedback colorstackinit`, `pdf.newcolorstack`).
600    pub fn pdf_new_colorstack(&mut self, init: Vec<u8>, page: bool, mode: u8) -> i32 {
601        self.pdf_ensure_colorstacks();
602        self.pdf.colorstacks.push(ColorStack { page, mode, init });
603        (self.pdf.colorstacks.len() - 1) as i32
604    }
605
606    /// LuaTeX's checks of the object of `\pdfextension refobj` in PDF
607    /// mode (observed): it must be a defined `obj` object.
608    fn pdf_check_refobj(&mut self, n: i32) {
609        if n == 0 {
610            // Observed: object 0 counts as a font.
611            self.pdf_fatal("referenced object has wrong type font; should be obj");
612        }
613        match self.pdf_obj_kind(n) {
614            None => self.pdf_fatal("cannot find referenced object"),
615            Some(ObjKind::Obj) => {
616                if !self.pdf.objects.get(&n).is_some_and(|o| o.defined) {
617                    self.pdf_fatal("invalid object");
618                }
619            }
620            Some(k) => {
621                let msg = format!(
622                    "referenced object has wrong type {}; should be obj",
623                    k.name()
624                );
625                self.pdf_fatal(&msg);
626            }
627        }
628    }
629
630    /// Appends a `pdf_refobj` whatsit for object `n` to the current list
631    /// (`\pdfextension refobj`, `pdf.refobj` in `\directlua`).
632    pub fn pdf_append_refobj(&mut self, n: i32) {
633        self.append_whatsit(whatsit_sub::PDF_REFOBJ, Whatsit::PdfRefobj { objnum: n });
634    }
635
636    /// Stores object `obj`, makes it `\pdffeedback lastobj`, and writes it
637    /// now if `immediate`.
638    ///
639    /// Observed: LuaTeX's "words of extra memory" grow by 5 for each
640    /// object from `\pdfextension obj` (immediate or not) and from
641    /// `pdf.obj`, but not from `pdf.immediateobj` or a reservation.
642    pub fn pdf_define_object(&mut self, obj: PdfObject, immediate: bool) {
643        if !immediate {
644            self.pdf.obj_words += 5;
645        }
646        let n = obj.num;
647        self.pdf.last_obj = n;
648        self.pdf.stream_written |= immediate && obj.stream;
649        self.pdf.objects.insert(n, obj);
650        if immediate {
651            self.pdf_write_object(n);
652        }
653    }
654
655    /// Appends a whatsit to the current list.
656    fn append_whatsit(&mut self, subtype: u16, w: Whatsit) {
657        // Observed: like every node, it carries the current attributes.
658        let p = self.new_node(id::WHATSIT, subtype, NodeData::Whatsit(Box::new(w)));
659        self.tail_append(p);
660    }
661
662    /// Scans `width`, `height`, `depth` keywords (`scan_rule_spec` for
663    /// annotations); unspecified dimensions are running.
664    fn scan_pdf_rule_spec(&mut self) -> [i32; 3] {
665        let mut d = [RUNNING; 3];
666        loop {
667            let i = if self.scan_keyword("width") {
668                0
669            } else if self.scan_keyword("height") {
670                1
671            } else if self.scan_keyword("depth") {
672                2
673            } else {
674                return d;
675            };
676            self.scan_normal_dimen();
677            d[i] = self.cur_val;
678        }
679    }
680
681    /// Reports a fatal backend error the way LuaTeX does and ends the run
682    /// without writing the PDF file.
683    pub fn pdf_fatal(&mut self, msg: &str) -> ! {
684        self.pdf_fatal_in("pdf backend", msg)
685    }
686
687    /// [`Engine::pdf_fatal`] with another category.
688    pub fn pdf_fatal_in(&mut self, category: &str, msg: &str) -> ! {
689        self.normalize_selector();
690        self.printer.print_nl("! error:  (");
691        self.printer.print_str(category);
692        self.printer.print_str("): ");
693        self.printer.print_str(msg);
694        self.printer
695            .print_nl("!  ==> Fatal error occurred, no output PDF file produced!");
696        self.printer.update_terminal();
697        // Observed: LuaTeX stops right there; the log ends without a
698        // newline and the PDF file is gone.
699        self.pdf.fatal = true;
700        self.err.history = crate::error::History::FatalErrorStop;
701        let _ = std::fs::remove_file(self.output_path("pdf"));
702        self.printer.close_log();
703        std::panic::resume_unwind(Box::new(crate::error::JumpOut));
704    }
705
706    /// Prints a backend warning on a line of its own
707    /// (`warning  (pdf backend): …`).
708    pub fn pdf_warning(&mut self, msg: &str) {
709        self.printer.print_nl("warning  (pdf backend): ");
710        self.printer.print_str(msg);
711        self.printer.print_ln();
712    }
713
714    /// Scans an action specification (`user {…}` | `goto …` | `thread …`).
715    fn scan_pdf_action(&mut self) -> Option<PdfAction> {
716        if self.scan_keyword("user") {
717            return Some(PdfAction::User(self.scan_pdf_string()));
718        }
719        let goto = if self.scan_keyword("goto") {
720            true
721        } else if self.scan_keyword("thread") {
722            false
723        } else {
724            self.pdf_fatal("action type missing");
725        };
726        let file = if self.scan_keyword("file") {
727            Some(self.scan_pdf_string())
728        } else {
729            None
730        };
731        // LuaTeX 1.22: `goto struct name {…}` or `struct num n` names the
732        // structure destination of the link.
733        let structure = if goto && self.scan_keyword("struct") {
734            if self.scan_keyword("name") {
735                Some(ActionTarget::Name(self.scan_pdf_string()))
736            } else if self.scan_keyword("num") {
737                self.scan_int();
738                Some(ActionTarget::Num(self.cur_val))
739            } else {
740                self.pdf_fatal("identifier type missing");
741            }
742        } else {
743            None
744        };
745        let target = if goto && self.scan_keyword("page") {
746            self.scan_int();
747            let page = self.cur_val;
748            ActionTarget::Page(page, self.scan_pdf_string())
749        } else if self.scan_keyword("name") {
750            ActionTarget::Name(self.scan_pdf_string())
751        } else if self.scan_keyword("num") {
752            if goto && file.is_some() {
753                self.pdf_fatal("'goto' option cannot be used with both 'file' and 'num'");
754            }
755            self.scan_int();
756            ActionTarget::Num(self.cur_val)
757        } else {
758            self.pdf_fatal("identifier type missing");
759        };
760        let new_window = if self.scan_keyword("newwindow") {
761            Some(true)
762        } else if self.scan_keyword("nonewwindow") {
763            Some(false)
764        } else {
765            None
766        };
767        if new_window.is_some() && !(goto && file.is_some()) {
768            self.pdf_fatal(
769                "'newwindow' or 'nonewwindow' must be used with 'goto' and 'file' option",
770            );
771        }
772        Some(if goto {
773            PdfAction::Goto {
774                file,
775                structure,
776                target,
777                new_window,
778            }
779        } else {
780            PdfAction::Thread { file, target }
781        })
782    }
783
784    /// The PDF text of an action (a dictionary, or the user text),
785    /// allocating the objects of the destinations, pages and threads it
786    /// refers to.
787    pub fn pdf_action_text(&mut self, action: &PdfAction) -> Vec<u8> {
788        fn string(out: &mut Vec<u8>, s: &[u8]) {
789            out.push(b'(');
790            out.extend_from_slice(s);
791            out.push(b')');
792        }
793        let mut d = b"<< ".to_vec();
794        match action {
795            PdfAction::User(u) => return u.clone(),
796            PdfAction::Goto {
797                file: None,
798                target,
799                structure,
800                ..
801            } => {
802                d.extend_from_slice(b"/S /GoTo /D ");
803                match target {
804                    ActionTarget::Name(n) => {
805                        self.pdf_dest_obj(&DestId::Name(n.clone()));
806                        string(&mut d, n);
807                    }
808                    ActionTarget::Num(n) => {
809                        let o = self.pdf_dest_obj(&DestId::Num(*n));
810                        d.extend_from_slice(format!("{o} 0 R").as_bytes());
811                    }
812                    ActionTarget::Page(p, spec) => {
813                        let o = self.pdf_page_ref((*p).max(1));
814                        d.extend_from_slice(format!("[ {o} 0 R ").as_bytes());
815                        d.extend_from_slice(spec);
816                        d.extend_from_slice(b" ]");
817                    }
818                }
819                // Observed: ` /SD n 0 R` after the destination, numbered
820                // after it.
821                let sd = structure.as_ref().map(|s| {
822                    let id = match s {
823                        ActionTarget::Name(n) => DestId::Name(n.clone()),
824                        ActionTarget::Num(n) => DestId::Num(*n),
825                        ActionTarget::Page(..) => DestId::Num(0),
826                    };
827                    self.pdf_struct_dest_obj(&id)
828                });
829                if let Some(o) = sd {
830                    d.extend_from_slice(format!(" /SD {o} 0 R").as_bytes());
831                }
832            }
833            PdfAction::Goto {
834                file: Some(f),
835                target,
836                new_window,
837                ..
838            } => {
839                d.extend_from_slice(b"/F ");
840                string(&mut d, f);
841                d.push(b' ');
842                match new_window {
843                    Some(true) => d.extend_from_slice(b" /NewWindow true"),
844                    Some(false) => d.extend_from_slice(b" /NewWindow false"),
845                    None => {}
846                }
847                match target {
848                    ActionTarget::Name(n) => {
849                        d.extend_from_slice(b" /S /GoToR /D ");
850                        string(&mut d, n);
851                    }
852                    ActionTarget::Page(p, spec) => {
853                        // Observed: /GoTo (not /GoToR), page counted from 0.
854                        d.extend_from_slice(format!(" /S /GoTo /D [{} ", p - 1).as_bytes());
855                        d.extend_from_slice(spec);
856                        d.extend_from_slice(b" ]");
857                    }
858                    ActionTarget::Num(_) => {}
859                }
860            }
861            PdfAction::Thread { file, target } => {
862                if let Some(f) = file {
863                    // Observed: the identifier is dropped.
864                    d.extend_from_slice(b"/F ");
865                    string(&mut d, f);
866                    d.extend_from_slice(b"  /S /Thread");
867                } else {
868                    d.extend_from_slice(b"/S /Thread /D ");
869                    match target {
870                        ActionTarget::Name(n) => {
871                            self.pdf_thread_obj(DestId::Name(n.clone()));
872                            string(&mut d, n);
873                        }
874                        ActionTarget::Num(n) => {
875                            let o = self.pdf_thread_obj(DestId::Num(*n));
876                            d.extend_from_slice(format!("{o} 0 R").as_bytes());
877                        }
878                        ActionTarget::Page(..) => {}
879                    }
880                }
881            }
882        }
883        d.extend_from_slice(b" >>");
884        d
885    }
886
887    /// Writes `body` as object `n` now (actions and titles of outlines,
888    /// the open action).
889    fn pdf_write_raw(&mut self, n: i32, body: Vec<u8>) {
890        let obj = PdfObject {
891            num: n,
892            data: body,
893            defined: true,
894            ..Default::default()
895        };
896        self.pdf_write_object_data(&obj);
897    }
898
899    /// Writes an action object for `action` now and returns its number;
900    /// user actions are written between newlines.
901    fn pdf_write_action(&mut self, action: &PdfAction) -> i32 {
902        let n = self.pdf_alloc_obj(ObjKind::Other);
903        if let Some(mut be) = self.backend.take() {
904            be.begin_object(self);
905            self.backend = Some(be);
906        }
907        let body = match action {
908            PdfAction::User(u) => {
909                let mut b = b"\n".to_vec();
910                b.extend_from_slice(u);
911                b.push(b'\n');
912                b
913            }
914            _ => self.pdf_action_text(action),
915        };
916        self.pdf_write_raw(n, body);
917        n
918    }
919
920    /// Executes `\pdfextension` (`immediate` when prefixed by
921    /// `\immediate`).
922    pub fn pdf_extension(&mut self, immediate: bool) {
923        if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) <= 0 {
924            // Observed: in DVI mode LuaTeX ignores `\pdfextension` without
925            // reading any keyword.
926            return;
927        }
928        if self.scan_keyword("literal") {
929            let late = self.scan_keyword("shipout");
930            let mode = if self.scan_keyword("direct") {
931                literal_mode::DIRECT
932            } else if self.scan_keyword("page") {
933                literal_mode::PAGE
934            } else if self.scan_keyword("raw") {
935                literal_mode::RAW
936            } else {
937                literal_mode::ORIGIN
938            };
939            let data = if late {
940                LateData::Tokens(self.scan_general_text())
941            } else {
942                LateData::Bytes(self.scan_pdf_string())
943            };
944            self.append_whatsit(
945                if late {
946                    whatsit_sub::PDF_LATE_LITERAL
947                } else {
948                    whatsit_sub::PDF_LITERAL
949                },
950                Whatsit::PdfLiteral { mode, data, late },
951            );
952        } else if self.scan_keyword("lateliteral") {
953            let data = LateData::Tokens(self.scan_general_text());
954            self.append_whatsit(
955                whatsit_sub::PDF_LATE_LITERAL,
956                Whatsit::PdfLiteral {
957                    mode: literal_mode::ORIGIN,
958                    data,
959                    late: true,
960                },
961            );
962        } else if self.scan_keyword("save") {
963            self.append_whatsit(whatsit_sub::PDF_SAVE, Whatsit::PdfSave);
964        } else if self.scan_keyword("restore") {
965            self.append_whatsit(whatsit_sub::PDF_RESTORE, Whatsit::PdfRestore);
966        } else if self.scan_keyword("setmatrix") {
967            let data = self.scan_pdf_string();
968            self.append_whatsit(whatsit_sub::PDF_SETMATRIX, Whatsit::PdfSetmatrix { data });
969        } else if self.scan_keyword("colorstack") {
970            self.scan_int();
971            let stack = self.cur_val;
972            let (cmd, data) = if self.scan_keyword("set") {
973                (colorstack_cmd::SET, self.scan_pdf_string())
974            } else if self.scan_keyword("push") {
975                (colorstack_cmd::PUSH, self.scan_pdf_string())
976            } else if self.scan_keyword("pop") {
977                (colorstack_cmd::POP, Vec::new())
978            } else if self.scan_keyword("current") {
979                (colorstack_cmd::CURRENT, Vec::new())
980            } else {
981                self.print_err("Color stack action is missing");
982                self.help(&[
983                    "The expected actions for \\pdfextension colorstack:",
984                    "    set, push, pop, current",
985                ]);
986                self.error();
987                return;
988            };
989            self.append_whatsit(
990                whatsit_sub::PDF_COLORSTACK,
991                Whatsit::PdfColorstack { stack, cmd, data },
992            );
993        } else if self.scan_keyword("obj") {
994            self.pdf_obj(immediate);
995        } else if self.scan_keyword("refobj") {
996            self.scan_int();
997            let n = self.cur_val;
998            if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
999                self.pdf_check_refobj(n);
1000            }
1001            self.pdf_append_refobj(n);
1002        } else if self.scan_keyword("annot") {
1003            let objnum = if self.scan_keyword("reserveobjnum") {
1004                let n = self.pdf_alloc_obj(ObjKind::Annot);
1005                self.pdf.last_annot = n;
1006                self.scan_optional_space();
1007                return;
1008            } else if self.scan_keyword("useobjnum") {
1009                self.scan_int();
1010                self.cur_val
1011            } else {
1012                self.pdf_alloc_obj(ObjKind::Annot)
1013            };
1014            self.pdf.last_annot = objnum;
1015            let dims = self.scan_pdf_rule_spec();
1016            let data = self.scan_pdf_string();
1017            self.append_whatsit(
1018                whatsit_sub::PDF_ANNOT,
1019                Whatsit::PdfAnnot { dims, objnum, data },
1020            );
1021        } else if self.scan_keyword("startlink") {
1022            if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
1023                self.pdf_fatal("startlink cannot be used in vertical mode");
1024            }
1025            let dims = self.scan_pdf_rule_spec();
1026            let mut attr = Vec::new();
1027            if self.scan_keyword("attr") {
1028                attr = self.scan_pdf_string();
1029            }
1030            let Some(action) = self.scan_pdf_action() else {
1031                return;
1032            };
1033            let objnum = self.pdf_alloc_obj(ObjKind::Link);
1034            self.pdf.last_link = objnum;
1035            self.append_whatsit(
1036                whatsit_sub::PDF_START_LINK,
1037                Whatsit::PdfStartLink {
1038                    dims,
1039                    objnum,
1040                    attr,
1041                    action,
1042                },
1043            );
1044        } else if self.scan_keyword("endlink") {
1045            if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
1046                self.pdf_fatal("\\pdfextension endlink cannot be used in vertical mode");
1047            }
1048            self.append_whatsit(whatsit_sub::PDF_END_LINK, Whatsit::PdfEndLink);
1049        } else if self.scan_keyword("dest") {
1050            self.pdf_dest_cmd();
1051        } else if self.scan_keyword("outline") {
1052            self.pdf_outline_cmd();
1053        } else if self.scan_keyword("glyphtounicode") {
1054            let a = self.scan_pdf_string();
1055            let b = self.scan_pdf_string();
1056            self.pdf.glyph_to_unicode.push((a, b));
1057        } else if self.scan_keyword("catalog") {
1058            let s = self.scan_pdf_string();
1059            self.pdf.catalog.extend_from_slice(&s);
1060            if self.scan_keyword("openaction") {
1061                if self.pdf.open_action.is_some() {
1062                    self.pdf_fatal("duplicate of openaction");
1063                }
1064                if let Some(a) = self.scan_pdf_action() {
1065                    let n = self.pdf_write_action(&a);
1066                    self.pdf.open_action = Some(n);
1067                }
1068            }
1069        } else if self.scan_keyword("info") {
1070            let s = self.scan_pdf_string();
1071            self.pdf.info.extend_from_slice(&s);
1072        } else if self.scan_keyword("names") {
1073            let s = self.scan_pdf_string();
1074            self.pdf.names.extend_from_slice(&s);
1075        } else if self.scan_keyword("trailer") {
1076            let s = self.scan_pdf_string();
1077            self.pdf.trailer.extend_from_slice(&s);
1078        } else if self.scan_keyword("fontattr") {
1079            self.scan_font_ident();
1080            let f = self.cur_val as u32;
1081            let s = self.scan_pdf_string();
1082            self.pdf.font_attrs.insert(f, s);
1083        } else if self.scan_keyword("mapfile") {
1084            let s = self.scan_pdf_string();
1085            // Observed: the map file is read at once (`{path}` in the log),
1086            // after the default one for `+` and `=`, and the default one is
1087            // then not announced at the first page. (Reading a file twice
1088            // warns about each duplicate entry; that is not reproduced.)
1089            if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
1090                let text = String::from_utf8_lossy(&s).trim().to_owned();
1091                let prefix = text.chars().next().filter(|c| matches!(c, '+' | '-' | '='));
1092                let name = text.trim_start_matches(['+', '-', '=']);
1093                if prefix != Some('-') {
1094                    if prefix.is_some() && !self.pdf.map_note_done {
1095                        self.print_map_file_note("pdftex.map");
1096                    }
1097                    self.print_map_file_note(name);
1098                    self.pdf.map_note_done = true;
1099                }
1100            }
1101            self.pdf.map_entries.push(s);
1102        } else if self.scan_keyword("mapline") {
1103            let s = self.scan_pdf_string();
1104            self.pdf.map_entries.push(s);
1105        } else if self.scan_keyword("includechars") {
1106            let _ = self.scan_pdf_string();
1107        } else if self.scan_keyword("linkstate") {
1108            self.scan_int();
1109            let state = self.cur_val;
1110            self.append_whatsit(
1111                crate::nodes::whatsit_sub::PDF_LINK_STATE,
1112                Whatsit::PdfLinkState { state },
1113            );
1114        } else if self.scan_keyword("startthread") || self.scan_keyword("thread") {
1115            // Article threads are not written yet; the syntax is accepted.
1116            let _ = self.scan_pdf_rule_spec();
1117            if self.scan_keyword("attr") {
1118                let _ = self.scan_pdf_string();
1119            }
1120            if self.scan_keyword("num") {
1121                self.scan_int();
1122            } else if self.scan_keyword("name") {
1123                let _ = self.scan_pdf_string();
1124            }
1125        } else if self.scan_keyword("endthread") {
1126        } else {
1127            // LuaTeX 1.22: lower case, no help text.
1128            self.print_err("unexpected use of \\pdfextension");
1129            self.help(&[]);
1130            self.error();
1131        }
1132    }
1133
1134    /// `\pdfextension dest …`.
1135    fn pdf_dest_cmd(&mut self) {
1136        let structure = if self.scan_keyword("struct") {
1137            self.scan_int();
1138            Some(self.cur_val)
1139        } else {
1140            None
1141        };
1142        let id = if self.scan_keyword("num") {
1143            self.scan_int();
1144            DestId::Num(self.cur_val)
1145        } else if self.scan_keyword("name") {
1146            DestId::Name(self.scan_pdf_string())
1147        } else {
1148            self.pdf_fatal("identifier type missing");
1149        };
1150        let mut view = DestView {
1151            kind: DestKind::Xyz,
1152            zoom: 0,
1153            dims: [RUNNING; 3],
1154        };
1155        if self.scan_keyword("xyz") {
1156            if self.scan_keyword("zoom") {
1157                self.scan_int();
1158                view.zoom = self.cur_val;
1159            }
1160        } else if self.scan_keyword("fitbh") {
1161            view.kind = DestKind::FitBH;
1162        } else if self.scan_keyword("fitbv") {
1163            view.kind = DestKind::FitBV;
1164        } else if self.scan_keyword("fitb") {
1165            view.kind = DestKind::FitB;
1166        } else if self.scan_keyword("fith") {
1167            view.kind = DestKind::FitH;
1168        } else if self.scan_keyword("fitv") {
1169            view.kind = DestKind::FitV;
1170        } else if self.scan_keyword("fitr") {
1171            view.kind = DestKind::FitR;
1172            view.dims = self.scan_pdf_rule_spec();
1173        } else if self.scan_keyword("fit") {
1174            view.kind = DestKind::Fit;
1175        } else {
1176            self.pdf_fatal("destination type missing");
1177        }
1178        self.append_whatsit(
1179            whatsit_sub::PDF_DEST,
1180            Whatsit::PdfDest {
1181                id,
1182                view,
1183                structure,
1184            },
1185        );
1186    }
1187
1188    /// `\pdfextension outline …`.
1189    fn pdf_outline_cmd(&mut self) {
1190        let mut o = Outline::default();
1191        if self.scan_keyword("attr") {
1192            o.attr = self.scan_pdf_string();
1193        }
1194        if self.scan_keyword("useobjnum") {
1195            self.scan_int();
1196            o.action = self.cur_val;
1197        } else {
1198            let Some(a) = self.scan_pdf_action() else {
1199                return;
1200            };
1201            o.action = self.pdf_write_action(&a);
1202        }
1203        if self.scan_keyword("count") {
1204            self.scan_int();
1205            o.count = self.cur_val;
1206        }
1207        let title = self.scan_pdf_string();
1208        o.objnum = self.pdf_alloc_obj(ObjKind::Other);
1209        o.title = self.pdf_alloc_obj(ObjKind::Other);
1210        let mut body = b"(".to_vec();
1211        body.extend_from_slice(&title);
1212        body.extend_from_slice(b")\n");
1213        self.pdf_write_raw(o.title, body);
1214        self.pdf.outlines.push(o);
1215    }
1216
1217    /// `\pdfextension obj …`.
1218    fn pdf_obj(&mut self, immediate: bool) {
1219        if self.scan_keyword("reserveobjnum") {
1220            let n = self.pdf_alloc_obj(ObjKind::Obj);
1221            self.pdf.objects.insert(
1222                n,
1223                PdfObject {
1224                    num: n,
1225                    ..Default::default()
1226                },
1227            );
1228            self.pdf.last_obj = n;
1229            // Observed: a space after the keyword is skipped.
1230            self.scan_optional_space();
1231            return;
1232        }
1233        let n = if self.scan_keyword("useobjnum") {
1234            self.scan_int();
1235            self.cur_val
1236        } else {
1237            self.pdf_alloc_obj(ObjKind::Obj)
1238        };
1239        let uncompressed = self.scan_keyword("uncompressed");
1240        let mut obj = PdfObject {
1241            num: n,
1242            uncompressed,
1243            defined: true,
1244            ..Default::default()
1245        };
1246        if self.scan_keyword("stream") {
1247            obj.stream = true;
1248            if self.scan_keyword("attr") {
1249                obj.attr = self.scan_pdf_string();
1250            }
1251        }
1252        if self.scan_keyword("file") {
1253            obj.file = true;
1254        }
1255        obj.data = self.scan_pdf_string();
1256        if immediate {
1257            self.pdf.obj_words += 5;
1258        }
1259        self.pdf_define_object(obj, immediate);
1260    }
1261
1262    /// Writes object `n` through the backend now.
1263    pub fn pdf_write_object(&mut self, n: i32) {
1264        let Some(obj) = self.pdf.objects.get(&n).cloned() else {
1265            return;
1266        };
1267        if obj.written || !obj.defined {
1268            return;
1269        }
1270        if let Some(o) = self.pdf.objects.get_mut(&n) {
1271            o.written = true;
1272        }
1273        self.pdf_write_object_data(&obj);
1274    }
1275
1276    /// Hands `obj` to the backend, or queues it while the backend is busy
1277    /// shipping a page. A file object prints `<<name>>` as LuaTeX does.
1278    fn pdf_write_object_data(&mut self, obj: &PdfObject) {
1279        let Some(obj) = self.pdf_object_payload(obj) else {
1280            return;
1281        };
1282        match self.backend.take() {
1283            Some(mut be) => {
1284                be.write_object(self, &obj);
1285                self.backend = Some(be);
1286            }
1287            None => self.pdf.pending.push(obj),
1288        }
1289    }
1290
1291    /// Takes object `n` (defined and not yet written) for writing with the
1292    /// current page (`\pdfextension refobj`).
1293    pub(crate) fn pdf_take_page_object(&mut self, n: i32) -> Option<PdfObject> {
1294        let obj = self.pdf.objects.get_mut(&n)?;
1295        if obj.written || !obj.defined {
1296            return None;
1297        }
1298        obj.written = true;
1299        let obj = obj.clone();
1300        self.pdf_object_payload(&obj)
1301    }
1302
1303    /// The object as written: a file object gets the file's contents
1304    /// (printing `<<name>>` as LuaTeX does).
1305    fn pdf_object_payload(&mut self, obj: &PdfObject) -> Option<PdfObject> {
1306        let mut obj = obj.clone();
1307        if obj.file {
1308            let name = String::from_utf8_lossy(&obj.data).into_owned();
1309            let path = self
1310                .kpse
1311                .as_ref()
1312                .and_then(|k| {
1313                    k.borrow_mut()
1314                        .find_file(&name, retex_kpath::FileFormat::Tex)
1315                })
1316                .unwrap_or_else(|| std::path::PathBuf::from(&name));
1317            match std::fs::read(&path) {
1318                Ok(d) => {
1319                    self.printer.print_str("<<");
1320                    self.printer.print_bytes(name.as_bytes());
1321                    self.printer.print_str(">>");
1322                    obj.data = d;
1323                    obj.file = false;
1324                }
1325                Err(_) => {
1326                    self.print_err("pdf backend: cannot open file for embedding");
1327                    self.help(&[]);
1328                    self.error();
1329                    return None;
1330                }
1331            }
1332        }
1333        Some(obj)
1334    }
1335
1336    /// A token list of whatsit data, for late expansion.
1337    pub fn whatsit_tokens(list: &TokenList) -> LateData {
1338        LateData::Tokens(list.clone())
1339    }
1340
1341    /// Works out what the backend needs at the end of the run, and prints
1342    /// LuaTeX's warnings about destinations and threads that were referred
1343    /// to but never defined.
1344    pub fn pdf_finish_document(&mut self) {
1345        let mut doc = DocItems::default();
1346        // Observed order: missing pages, then destinations (each in the
1347        // reverse order of allocation), then threads; the warnings are
1348        // separated by empty lines.
1349        let mut warnings = Vec::new();
1350        let mut pages: Vec<(i32, i32)> = self
1351            .pdf
1352            .page_refs
1353            .iter()
1354            .filter(|(p, _)| **p > self.total_pages as i32)
1355            .map(|(p, n)| (*n, *p))
1356            .collect();
1357        pages.sort_unstable_by(|a, b| b.cmp(a));
1358        for (_, p) in pages {
1359            warnings.push(format!("page {p} has been referenced but does not exist"));
1360        }
1361        let order = self.pdf.dest_order.clone();
1362        for id in order.iter().rev() {
1363            if let Some(d) = self.pdf.dests.get(id).copied()
1364                && !d.defined
1365            {
1366                warnings.push(format!("unreferenced destination with {}", id.describe()));
1367                doc.missing_dests.push(d.objnum);
1368            }
1369        }
1370        for (id, n) in self.pdf.threads.clone() {
1371            warnings.push(format!("unknown thread destination {}", id.describe()));
1372            let title = match id {
1373                DestId::Name(n) => n,
1374                DestId::Num(k) => k.to_string().into_bytes(),
1375            };
1376            doc.missing_threads.push((n, title));
1377        }
1378        for (i, w) in warnings.iter().enumerate() {
1379            if i > 0 {
1380                self.printer.print_ln();
1381            }
1382            self.pdf_warning(w);
1383        }
1384        let mut names: Vec<(Vec<u8>, i32)> = self
1385            .pdf
1386            .dests
1387            .iter()
1388            .filter_map(|(k, d)| match k {
1389                DestId::Name(n) => Some((n.clone(), d.objnum)),
1390                DestId::Num(_) => None,
1391            })
1392            .collect();
1393        names.sort();
1394        doc.dest_names = names;
1395        let toks = |e: &mut Engine, i: u32| {
1396            let list = e.eqtb.toks_param(i).clone();
1397            e.print_to_string(|e| e.show_token_list(&list, 0, list.len(), i64::MAX))
1398        };
1399        doc.pages_attr = toks(self, crate::backend::PAGES_ATTR);
1400        doc.trailer_id = toks(self, crate::backend::TRAILER_ID);
1401        self.pdf.doc = doc;
1402    }
1403}