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    /// The current rectangle before transformation and margin.
331    pub raw: [i32; 4],
332    /// The transformation (`\pdfextension setmatrix`) where it started.
333    pub matrix: Option<[f64; 6]>,
334}
335
336/// An annotation's rectangle (sp, page coordinates) and text.
337pub type AnnotBody = ([i32; 4], Vec<u8>);
338
339/// The interactive items of the page being shipped, handed to the
340/// backend when the page ends.
341#[derive(Clone, Debug, Default)]
342pub struct PageItems {
343    /// Annotations: object number and, unless written by other means
344    /// (`pdf.registerannot`), rectangle and text.
345    pub annots: Vec<(i32, Option<AnnotBody>)>,
346    /// Link rectangles, in the order they were started.
347    pub links: Vec<LinkPiece>,
348    /// Destinations: object number, whether named, view, and for a
349    /// structure destination the structure element's object.
350    pub dests: Vec<(i32, bool, DestPos, Option<i32>)>,
351    /// Objects to write with the page (`\pdfextension refobj`).
352    pub objects: Vec<PdfObject>,
353    /// Page attributes: the TeX and the Lua text.
354    pub attr: (Vec<u8>, Vec<u8>),
355    /// Page resources: the TeX and the Lua text.
356    pub resources: (Vec<u8>, Vec<u8>),
357}
358
359/// Document-level items computed at the end of the run for the backend.
360#[derive(Clone, Debug, Default)]
361pub struct DocItems {
362    /// Destinations referred to but never defined, in writing order.
363    pub missing_dests: Vec<i32>,
364    /// Threads referred to but never defined: object and title.
365    pub missing_threads: Vec<(i32, Vec<u8>)>,
366    /// Named destinations sorted by name.
367    pub dest_names: Vec<(Vec<u8>, i32)>,
368    /// `\pdfvariable pagesattr` at the end of the run.
369    pub pages_attr: Vec<u8>,
370    /// `\pdfvariable trailerid` at the end of the run.
371    pub trailer_id: Vec<u8>,
372}
373
374/// A destination known by its identifier.
375#[derive(Clone, Copy, Debug, Default)]
376pub struct DestEntry {
377    /// The object number.
378    pub objnum: i32,
379    /// Whether `\pdfextension dest` was shipped for it.
380    pub defined: bool,
381}
382
383/// Document-level PDF state.
384#[derive(Clone, Debug, Default)]
385pub struct PdfState {
386    /// Images written while shipping the current page, in order of first
387    /// use: their file names are printed at the end of the page.
388    pub page_images: Vec<u32>,
389    /// Objects by PDF object number.
390    pub objects: std::collections::BTreeMap<i32, PdfObject>,
391    /// `\pdfextension catalog` text.
392    pub catalog: Vec<u8>,
393    /// The catalog's open action (object number).
394    pub open_action: Option<i32>,
395    /// `\pdfextension info` text.
396    pub info: Vec<u8>,
397    /// `\pdfextension names` text.
398    pub names: Vec<u8>,
399    /// `\pdfextension trailer` text.
400    pub trailer: Vec<u8>,
401    /// Outline entries in order.
402    pub outlines: Vec<Outline>,
403    /// `\pdfextension glyphtounicode` pairs.
404    pub glyph_to_unicode: Vec<(Vec<u8>, Vec<u8>)>,
405    /// Colour stacks; stack 0 is the predefined colour stack.
406    pub colorstacks: Vec<ColorStack>,
407    /// `\pdffeedback lastobj`.
408    pub last_obj: i32,
409    /// `\pdffeedback lastannot`.
410    pub last_annot: i32,
411    /// `\pdffeedback lastlink`.
412    pub last_link: i32,
413    /// The highest object number allocated so far, shared with the PDF
414    /// writer.
415    pub counter: Rc<Cell<u32>>,
416    /// The kind of each allocated object number.
417    pub kinds: HashMap<i32, ObjKind>,
418    /// Page object numbers by page number (forward references included).
419    pub page_refs: HashMap<i32, i32>,
420    /// PDF fonts (resource name number, object number) by font, see
421    /// [`Engine::pdf_font`].
422    pub fonts: HashMap<u32, (u32, i32)>,
423    /// `\pdfextension fontattr` and `pdf.setfontattributes` texts by font.
424    pub font_attrs: HashMap<u32, Vec<u8>>,
425    /// Map files and map lines (`pdf.mapfile`, `pdf.mapline`,
426    /// `\pdfextension mapfile`/`mapline`), in order.
427    pub map_entries: Vec<Vec<u8>>,
428    /// Texts set from Lua.
429    pub lua: LuaPdfTexts,
430    /// `\pdffeedback retval`.
431    pub retval: i32,
432    /// The position (PDF coordinates, sp) of the last `\latelua`, for
433    /// `pdf.getpos`.
434    pub pos: (i32, i32),
435    /// The `\latelua` being executed, if any.
436    pub late: Option<LateLuaContext>,
437    /// Annotations registered from Lua for the page being shipped.
438    pub page_annots: Vec<i32>,
439    /// Whether `pdf.obj` opened the output file (in DVI mode LuaTeX then
440    /// leaves an empty DVI file when no page is shipped out).
441    pub output_opened: bool,
442    /// Destinations by identifier.
443    pub dests: HashMap<DestId, DestEntry>,
444    /// Destination identifiers in the order their numbers were allocated.
445    pub dest_order: Vec<DestId>,
446    /// Structure destinations by identifier (`\pdfextension dest struct`).
447    pub struct_dests: HashMap<DestId, DestEntry>,
448    /// Threads referred to by actions: identifier and object number.
449    pub threads: Vec<(DestId, i32)>,
450    /// The interactive items of the page being shipped.
451    pub page: PageItems,
452    /// Objects to write as soon as the backend is available again (they
453    /// were written during shipout, while the backend is busy).
454    pub pending: Vec<PdfObject>,
455    /// Document-level items for the backend, computed at the end.
456    pub doc: DocItems,
457    /// A fatal backend error occurred: no PDF is written.
458    pub fatal: bool,
459    /// Objects written into object streams and the number of streams.
460    pub objstm_stats: (u32, u32),
461    /// Whether a stream (form, image or stream object) was written
462    /// immediately: with no pages, LuaTeX then fails at the end
463    /// ("already written content discarded").
464    pub stream_written: bool,
465    /// Words of extra memory LuaTeX reports for objects (see
466    /// [`Engine::pdf_define_object`]).
467    pub obj_words: usize,
468    /// Whether the font map note (`{…/pdftex.map}`) was printed.
469    pub map_note_done: bool,
470    /// Links started but not ended yet, innermost last.
471    pub active_links: Vec<ActiveLink>,
472}
473
474impl PdfState {
475    /// The highest object number allocated so far.
476    pub fn max_obj(&self) -> i32 {
477        self.counter.get() as i32
478    }
479
480    /// The number of named destinations (for the PDF statistics).
481    pub fn named_dests(&self) -> usize {
482        self.dests
483            .keys()
484            .filter(|k| matches!(k, DestId::Name(_)))
485            .count()
486    }
487}
488
489impl Engine {
490    /// Scans a balanced text (expanded) and returns its characters.
491    fn scan_pdf_string(&mut self) -> Vec<u8> {
492        let toks = self.scan_toks_expanded();
493        self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
494    }
495
496    /// Allocates a PDF object number of kind `kind`.
497    pub fn pdf_alloc_obj(&mut self, kind: ObjKind) -> i32 {
498        let n = self.pdf.counter.get() + 1;
499        self.pdf.counter.set(n);
500        let n = n as i32;
501        self.pdf.kinds.insert(n, kind);
502        n
503    }
504
505    /// The kind of object number `n`, if it was allocated.
506    pub fn pdf_obj_kind(&self, n: i32) -> Option<ObjKind> {
507        self.pdf.kinds.get(&n).copied()
508    }
509
510    /// The object number of page `page` (counting from 1), allocated on
511    /// first request (`pdf.getpageref`, `\pdffeedback pageref`).
512    pub fn pdf_page_ref(&mut self, page: i32) -> i32 {
513        if let Some(&n) = self.pdf.page_refs.get(&page) {
514            return n;
515        }
516        let n = self.pdf_alloc_obj(ObjKind::Page);
517        self.pdf.page_refs.insert(page, n);
518        n
519    }
520
521    /// The object number of destination `id`, allocated on first use.
522    pub fn pdf_dest_obj(&mut self, id: &DestId) -> i32 {
523        if let Some(d) = self.pdf.dests.get(id) {
524            return d.objnum;
525        }
526        let n = self.pdf_alloc_obj(ObjKind::Other);
527        self.pdf.dests.insert(
528            id.clone(),
529            DestEntry {
530                objnum: n,
531                defined: false,
532            },
533        );
534        self.pdf.dest_order.push(id.clone());
535        n
536    }
537
538    /// The object number of structure destination `id`, allocated on
539    /// first use.
540    pub fn pdf_struct_dest_obj(&mut self, id: &DestId) -> i32 {
541        if let Some(d) = self.pdf.struct_dests.get(id) {
542            return d.objnum;
543        }
544        let n = self.pdf_alloc_obj(ObjKind::Other);
545        self.pdf.struct_dests.insert(
546            id.clone(),
547            DestEntry {
548                objnum: n,
549                defined: false,
550            },
551        );
552        n
553    }
554
555    /// The object number of thread `id`, allocated on first use.
556    fn pdf_thread_obj(&mut self, id: DestId) -> i32 {
557        if let Some((_, n)) = self.pdf.threads.iter().find(|(t, _)| *t == id) {
558            return *n;
559        }
560        let n = self.pdf_alloc_obj(ObjKind::Other);
561        self.pdf.threads.push((id, n));
562        n
563    }
564
565    /// The PDF font of font `f`: the number in its resource name `/F<n>`
566    /// and its object number. Fonts with the same name share one PDF font.
567    /// It is created when first asked for (by `\pdffeedback fontname` or
568    /// `fontobjnum`, or their Lua counterparts), named after the font
569    /// asked for, and gets an object number then (observed: asking for
570    /// `cmr10 at 12pt` before `cmr10` names both after the former).
571    pub fn pdf_font(&mut self, f: u32) -> (u32, i32) {
572        if let Some(&r) = self.pdf.fonts.get(&f) {
573            return r;
574        }
575        let name = self.font(f).name.clone();
576        let shared = self
577            .pdf
578            .fonts
579            .iter()
580            .filter(|(g, _)| self.font_exists(**g as i32) && self.font(**g).name == name)
581            .map(|(_, r)| *r)
582            .min();
583        let r = match shared {
584            Some(r) => r,
585            None => (f, self.pdf_alloc_obj(ObjKind::Font)),
586        };
587        self.pdf.fonts.insert(f, r);
588        r
589    }
590
591    /// Makes sure the predefined colour stack 0 exists.
592    pub fn pdf_ensure_colorstacks(&mut self) {
593        if self.pdf.colorstacks.is_empty() {
594            self.pdf.colorstacks.push(ColorStack {
595                page: true,
596                mode: literal_mode::DIRECT,
597                init: b"0 g 0 G".to_vec(),
598            });
599        }
600    }
601
602    /// Defines a new colour stack and returns its number; stack 0 is the
603    /// predefined one (`\pdffeedback colorstackinit`, `pdf.newcolorstack`).
604    pub fn pdf_new_colorstack(&mut self, init: Vec<u8>, page: bool, mode: u8) -> i32 {
605        self.pdf_ensure_colorstacks();
606        self.pdf.colorstacks.push(ColorStack { page, mode, init });
607        (self.pdf.colorstacks.len() - 1) as i32
608    }
609
610    /// LuaTeX's checks of the object of `\pdfextension refobj` in PDF
611    /// mode (observed): it must be a defined `obj` object.
612    fn pdf_check_refobj(&mut self, n: i32) {
613        if n == 0 {
614            // Observed: object 0 counts as a font.
615            self.pdf_fatal("referenced object has wrong type font; should be obj");
616        }
617        match self.pdf_obj_kind(n) {
618            None => self.pdf_fatal("cannot find referenced object"),
619            Some(ObjKind::Obj) => {
620                if !self.pdf.objects.get(&n).is_some_and(|o| o.defined) {
621                    self.pdf_fatal("invalid object");
622                }
623            }
624            Some(k) => {
625                let msg = format!(
626                    "referenced object has wrong type {}; should be obj",
627                    k.name()
628                );
629                self.pdf_fatal(&msg);
630            }
631        }
632    }
633
634    /// Appends a `pdf_refobj` whatsit for object `n` to the current list
635    /// (`\pdfextension refobj`, `pdf.refobj` in `\directlua`).
636    pub fn pdf_append_refobj(&mut self, n: i32) {
637        self.append_whatsit(whatsit_sub::PDF_REFOBJ, Whatsit::PdfRefobj { objnum: n });
638    }
639
640    /// Stores object `obj`, makes it `\pdffeedback lastobj`, and writes it
641    /// now if `immediate`.
642    ///
643    /// Observed: LuaTeX's "words of extra memory" grow by 5 for each
644    /// object from `\pdfextension obj` (immediate or not) and from
645    /// `pdf.obj`, but not from `pdf.immediateobj` or a reservation.
646    pub fn pdf_define_object(&mut self, obj: PdfObject, immediate: bool) {
647        if !immediate {
648            self.pdf.obj_words += 5;
649        }
650        let n = obj.num;
651        self.pdf.last_obj = n;
652        self.pdf.stream_written |= immediate && obj.stream;
653        self.pdf.objects.insert(n, obj);
654        if immediate {
655            self.pdf_write_object(n);
656        }
657    }
658
659    /// Appends a whatsit to the current list.
660    fn append_whatsit(&mut self, subtype: u16, w: Whatsit) {
661        // Observed: like every node, it carries the current attributes.
662        let p = self.new_node(id::WHATSIT, subtype, NodeData::Whatsit(Box::new(w)));
663        self.tail_append(p);
664    }
665
666    /// Scans `width`, `height`, `depth` keywords (`scan_rule_spec` for
667    /// annotations); unspecified dimensions are running.
668    fn scan_pdf_rule_spec(&mut self) -> [i32; 3] {
669        let mut d = [RUNNING; 3];
670        loop {
671            let i = if self.scan_keyword("width") {
672                0
673            } else if self.scan_keyword("height") {
674                1
675            } else if self.scan_keyword("depth") {
676                2
677            } else {
678                return d;
679            };
680            self.scan_normal_dimen();
681            d[i] = self.cur_val;
682        }
683    }
684
685    /// Reports a fatal backend error the way LuaTeX does and ends the run
686    /// without writing the PDF file.
687    pub fn pdf_fatal(&mut self, msg: &str) -> ! {
688        self.pdf_fatal_in("pdf backend", msg)
689    }
690
691    /// [`Engine::pdf_fatal`] with another category.
692    pub fn pdf_fatal_in(&mut self, category: &str, msg: &str) -> ! {
693        self.normalize_selector();
694        self.printer.print_nl("! error:  (");
695        self.printer.print_str(category);
696        self.printer.print_str("): ");
697        self.printer.print_str(msg);
698        self.printer
699            .print_nl("!  ==> Fatal error occurred, no output PDF file produced!");
700        self.printer.update_terminal();
701        // Observed: LuaTeX stops right there; the log ends without a
702        // newline and the PDF file is gone.
703        self.pdf.fatal = true;
704        self.err.history = crate::error::History::FatalErrorStop;
705        let _ = std::fs::remove_file(self.output_path("pdf"));
706        self.printer.close_log();
707        std::panic::resume_unwind(Box::new(crate::error::JumpOut));
708    }
709
710    /// Prints a backend warning on a line of its own
711    /// (`warning  (pdf backend): …`).
712    pub fn pdf_warning(&mut self, msg: &str) {
713        self.printer.print_nl("warning  (pdf backend): ");
714        self.printer.print_str(msg);
715        self.printer.print_ln();
716    }
717
718    /// Scans an action specification (`user {…}` | `goto …` | `thread …`).
719    fn scan_pdf_action(&mut self) -> Option<PdfAction> {
720        if self.scan_keyword("user") {
721            return Some(PdfAction::User(self.scan_pdf_string()));
722        }
723        let goto = if self.scan_keyword("goto") {
724            true
725        } else if self.scan_keyword("thread") {
726            false
727        } else {
728            self.pdf_fatal("action type missing");
729        };
730        let file = if self.scan_keyword("file") {
731            Some(self.scan_pdf_string())
732        } else {
733            None
734        };
735        // LuaTeX 1.22: `goto struct name {…}` or `struct num n` names the
736        // structure destination of the link.
737        let structure = if goto && self.scan_keyword("struct") {
738            if self.scan_keyword("name") {
739                Some(ActionTarget::Name(self.scan_pdf_string()))
740            } else if self.scan_keyword("num") {
741                self.scan_int();
742                Some(ActionTarget::Num(self.cur_val))
743            } else {
744                self.pdf_fatal("identifier type missing");
745            }
746        } else {
747            None
748        };
749        let target = if goto && self.scan_keyword("page") {
750            self.scan_int();
751            let page = self.cur_val;
752            ActionTarget::Page(page, self.scan_pdf_string())
753        } else if self.scan_keyword("name") {
754            ActionTarget::Name(self.scan_pdf_string())
755        } else if self.scan_keyword("num") {
756            if goto && file.is_some() {
757                self.pdf_fatal("'goto' option cannot be used with both 'file' and 'num'");
758            }
759            self.scan_int();
760            ActionTarget::Num(self.cur_val)
761        } else {
762            self.pdf_fatal("identifier type missing");
763        };
764        let new_window = if self.scan_keyword("newwindow") {
765            Some(true)
766        } else if self.scan_keyword("nonewwindow") {
767            Some(false)
768        } else {
769            None
770        };
771        if new_window.is_some() && !(goto && file.is_some()) {
772            self.pdf_fatal(
773                "'newwindow' or 'nonewwindow' must be used with 'goto' and 'file' option",
774            );
775        }
776        Some(if goto {
777            PdfAction::Goto {
778                file,
779                structure,
780                target,
781                new_window,
782            }
783        } else {
784            PdfAction::Thread { file, target }
785        })
786    }
787
788    /// The PDF text of an action (a dictionary, or the user text),
789    /// allocating the objects of the destinations, pages and threads it
790    /// refers to.
791    pub fn pdf_action_text(&mut self, action: &PdfAction) -> Vec<u8> {
792        fn string(out: &mut Vec<u8>, s: &[u8]) {
793            out.push(b'(');
794            out.extend_from_slice(s);
795            out.push(b')');
796        }
797        let mut d = b"<< ".to_vec();
798        match action {
799            PdfAction::User(u) => return u.clone(),
800            PdfAction::Goto {
801                file: None,
802                target,
803                structure,
804                ..
805            } => {
806                d.extend_from_slice(b"/S /GoTo /D ");
807                match target {
808                    ActionTarget::Name(n) => {
809                        self.pdf_dest_obj(&DestId::Name(n.clone()));
810                        string(&mut d, n);
811                    }
812                    ActionTarget::Num(n) => {
813                        let o = self.pdf_dest_obj(&DestId::Num(*n));
814                        d.extend_from_slice(format!("{o} 0 R").as_bytes());
815                    }
816                    ActionTarget::Page(p, spec) => {
817                        let o = self.pdf_page_ref((*p).max(1));
818                        d.extend_from_slice(format!("[ {o} 0 R ").as_bytes());
819                        d.extend_from_slice(spec);
820                        d.extend_from_slice(b" ]");
821                    }
822                }
823                // Observed: ` /SD n 0 R` after the destination, numbered
824                // after it.
825                let sd = structure.as_ref().map(|s| {
826                    let id = match s {
827                        ActionTarget::Name(n) => DestId::Name(n.clone()),
828                        ActionTarget::Num(n) => DestId::Num(*n),
829                        ActionTarget::Page(..) => DestId::Num(0),
830                    };
831                    self.pdf_struct_dest_obj(&id)
832                });
833                if let Some(o) = sd {
834                    d.extend_from_slice(format!(" /SD {o} 0 R").as_bytes());
835                }
836            }
837            PdfAction::Goto {
838                file: Some(f),
839                target,
840                new_window,
841                ..
842            } => {
843                d.extend_from_slice(b"/F ");
844                string(&mut d, f);
845                d.push(b' ');
846                match new_window {
847                    Some(true) => d.extend_from_slice(b" /NewWindow true"),
848                    Some(false) => d.extend_from_slice(b" /NewWindow false"),
849                    None => {}
850                }
851                match target {
852                    ActionTarget::Name(n) => {
853                        d.extend_from_slice(b" /S /GoToR /D ");
854                        string(&mut d, n);
855                    }
856                    ActionTarget::Page(p, spec) => {
857                        // Observed: /GoTo (not /GoToR), page counted from 0.
858                        d.extend_from_slice(format!(" /S /GoTo /D [{} ", p - 1).as_bytes());
859                        d.extend_from_slice(spec);
860                        d.extend_from_slice(b" ]");
861                    }
862                    ActionTarget::Num(_) => {}
863                }
864            }
865            PdfAction::Thread { file, target } => {
866                if let Some(f) = file {
867                    // Observed: the identifier is dropped.
868                    d.extend_from_slice(b"/F ");
869                    string(&mut d, f);
870                    d.extend_from_slice(b"  /S /Thread");
871                } else {
872                    d.extend_from_slice(b"/S /Thread /D ");
873                    match target {
874                        ActionTarget::Name(n) => {
875                            self.pdf_thread_obj(DestId::Name(n.clone()));
876                            string(&mut d, n);
877                        }
878                        ActionTarget::Num(n) => {
879                            let o = self.pdf_thread_obj(DestId::Num(*n));
880                            d.extend_from_slice(format!("{o} 0 R").as_bytes());
881                        }
882                        ActionTarget::Page(..) => {}
883                    }
884                }
885            }
886        }
887        d.extend_from_slice(b" >>");
888        d
889    }
890
891    /// Writes `body` as object `n` now (actions and titles of outlines,
892    /// the open action).
893    fn pdf_write_raw(&mut self, n: i32, body: Vec<u8>) {
894        let obj = PdfObject {
895            num: n,
896            data: body,
897            defined: true,
898            ..Default::default()
899        };
900        self.pdf_write_object_data(&obj);
901    }
902
903    /// Writes an action object for `action` now and returns its number;
904    /// user actions are written between newlines.
905    fn pdf_write_action(&mut self, action: &PdfAction) -> i32 {
906        let n = self.pdf_alloc_obj(ObjKind::Other);
907        if let Some(mut be) = self.backend.take() {
908            be.begin_object(self);
909            self.backend = Some(be);
910        }
911        let body = match action {
912            PdfAction::User(u) => {
913                let mut b = b"\n".to_vec();
914                b.extend_from_slice(u);
915                b.push(b'\n');
916                b
917            }
918            _ => self.pdf_action_text(action),
919        };
920        self.pdf_write_raw(n, body);
921        n
922    }
923
924    /// Executes `\pdfextension` (`immediate` when prefixed by
925    /// `\immediate`).
926    pub fn pdf_extension(&mut self, immediate: bool) {
927        if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) <= 0 {
928            // Observed: in DVI mode LuaTeX ignores `\pdfextension` without
929            // reading any keyword.
930            return;
931        }
932        if self.scan_keyword("literal") {
933            let late = self.scan_keyword("shipout");
934            let mode = if self.scan_keyword("direct") {
935                literal_mode::DIRECT
936            } else if self.scan_keyword("page") {
937                literal_mode::PAGE
938            } else if self.scan_keyword("raw") {
939                literal_mode::RAW
940            } else {
941                literal_mode::ORIGIN
942            };
943            let data = if late {
944                LateData::Tokens(self.scan_general_text())
945            } else {
946                LateData::Bytes(self.scan_pdf_string())
947            };
948            self.append_whatsit(
949                if late {
950                    whatsit_sub::PDF_LATE_LITERAL
951                } else {
952                    whatsit_sub::PDF_LITERAL
953                },
954                Whatsit::PdfLiteral { mode, data, late },
955            );
956        } else if self.scan_keyword("lateliteral") {
957            let data = LateData::Tokens(self.scan_general_text());
958            self.append_whatsit(
959                whatsit_sub::PDF_LATE_LITERAL,
960                Whatsit::PdfLiteral {
961                    mode: literal_mode::ORIGIN,
962                    data,
963                    late: true,
964                },
965            );
966        } else if self.scan_keyword("save") {
967            self.append_whatsit(whatsit_sub::PDF_SAVE, Whatsit::PdfSave);
968        } else if self.scan_keyword("restore") {
969            self.append_whatsit(whatsit_sub::PDF_RESTORE, Whatsit::PdfRestore);
970        } else if self.scan_keyword("setmatrix") {
971            let data = self.scan_pdf_string();
972            self.append_whatsit(whatsit_sub::PDF_SETMATRIX, Whatsit::PdfSetmatrix { data });
973        } else if self.scan_keyword("colorstack") {
974            self.scan_int();
975            let stack = self.cur_val;
976            let (cmd, data) = if self.scan_keyword("set") {
977                (colorstack_cmd::SET, self.scan_pdf_string())
978            } else if self.scan_keyword("push") {
979                (colorstack_cmd::PUSH, self.scan_pdf_string())
980            } else if self.scan_keyword("pop") {
981                (colorstack_cmd::POP, Vec::new())
982            } else if self.scan_keyword("current") {
983                (colorstack_cmd::CURRENT, Vec::new())
984            } else {
985                self.print_err("Color stack action is missing");
986                self.help(&[
987                    "The expected actions for \\pdfextension colorstack:",
988                    "    set, push, pop, current",
989                ]);
990                self.error();
991                return;
992            };
993            self.append_whatsit(
994                whatsit_sub::PDF_COLORSTACK,
995                Whatsit::PdfColorstack { stack, cmd, data },
996            );
997        } else if self.scan_keyword("obj") {
998            self.pdf_obj(immediate);
999        } else if self.scan_keyword("refobj") {
1000            self.scan_int();
1001            let n = self.cur_val;
1002            if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
1003                self.pdf_check_refobj(n);
1004            }
1005            self.pdf_append_refobj(n);
1006        } else if self.scan_keyword("annot") {
1007            let objnum = if self.scan_keyword("reserveobjnum") {
1008                let n = self.pdf_alloc_obj(ObjKind::Annot);
1009                self.pdf.last_annot = n;
1010                self.scan_optional_space();
1011                return;
1012            } else if self.scan_keyword("useobjnum") {
1013                self.scan_int();
1014                self.cur_val
1015            } else {
1016                self.pdf_alloc_obj(ObjKind::Annot)
1017            };
1018            self.pdf.last_annot = objnum;
1019            let dims = self.scan_pdf_rule_spec();
1020            let data = self.scan_pdf_string();
1021            self.append_whatsit(
1022                whatsit_sub::PDF_ANNOT,
1023                Whatsit::PdfAnnot { dims, objnum, data },
1024            );
1025        } else if self.scan_keyword("startlink") {
1026            if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
1027                self.pdf_fatal("startlink cannot be used in vertical mode");
1028            }
1029            let dims = self.scan_pdf_rule_spec();
1030            let mut attr = Vec::new();
1031            if self.scan_keyword("attr") {
1032                attr = self.scan_pdf_string();
1033            }
1034            let Some(action) = self.scan_pdf_action() else {
1035                return;
1036            };
1037            let objnum = self.pdf_alloc_obj(ObjKind::Link);
1038            self.pdf.last_link = objnum;
1039            self.append_whatsit(
1040                whatsit_sub::PDF_START_LINK,
1041                Whatsit::PdfStartLink {
1042                    dims,
1043                    objnum,
1044                    attr,
1045                    action,
1046                },
1047            );
1048        } else if self.scan_keyword("endlink") {
1049            if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
1050                self.pdf_fatal("\\pdfextension endlink cannot be used in vertical mode");
1051            }
1052            self.append_whatsit(whatsit_sub::PDF_END_LINK, Whatsit::PdfEndLink);
1053        } else if self.scan_keyword("dest") {
1054            self.pdf_dest_cmd();
1055        } else if self.scan_keyword("outline") {
1056            self.pdf_outline_cmd();
1057        } else if self.scan_keyword("glyphtounicode") {
1058            let a = self.scan_pdf_string();
1059            let b = self.scan_pdf_string();
1060            self.pdf.glyph_to_unicode.push((a, b));
1061        } else if self.scan_keyword("catalog") {
1062            let s = self.scan_pdf_string();
1063            self.pdf.catalog.extend_from_slice(&s);
1064            if self.scan_keyword("openaction") {
1065                if self.pdf.open_action.is_some() {
1066                    self.pdf_fatal("duplicate of openaction");
1067                }
1068                if let Some(a) = self.scan_pdf_action() {
1069                    let n = self.pdf_write_action(&a);
1070                    self.pdf.open_action = Some(n);
1071                }
1072            }
1073        } else if self.scan_keyword("info") {
1074            let s = self.scan_pdf_string();
1075            self.pdf.info.extend_from_slice(&s);
1076        } else if self.scan_keyword("names") {
1077            let s = self.scan_pdf_string();
1078            self.pdf.names.extend_from_slice(&s);
1079        } else if self.scan_keyword("trailer") {
1080            let s = self.scan_pdf_string();
1081            self.pdf.trailer.extend_from_slice(&s);
1082        } else if self.scan_keyword("fontattr") {
1083            self.scan_font_ident();
1084            let f = self.cur_val as u32;
1085            let s = self.scan_pdf_string();
1086            self.pdf.font_attrs.insert(f, s);
1087        } else if self.scan_keyword("mapfile") {
1088            let s = self.scan_pdf_string();
1089            // Observed: the map file is read at once (`{path}` in the log),
1090            // after the default one for `+` and `=`, and the default one is
1091            // then not announced at the first page. (Reading a file twice
1092            // warns about each duplicate entry; that is not reproduced.)
1093            if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
1094                let text = String::from_utf8_lossy(&s).trim().to_owned();
1095                let prefix = text.chars().next().filter(|c| matches!(c, '+' | '-' | '='));
1096                let name = text.trim_start_matches(['+', '-', '=']);
1097                if prefix != Some('-') {
1098                    if prefix.is_some() && !self.pdf.map_note_done {
1099                        self.print_map_file_note("pdftex.map");
1100                    }
1101                    self.print_map_file_note(name);
1102                    self.pdf.map_note_done = true;
1103                }
1104            }
1105            self.pdf.map_entries.push(s);
1106        } else if self.scan_keyword("mapline") {
1107            let s = self.scan_pdf_string();
1108            self.pdf.map_entries.push(s);
1109        } else if self.scan_keyword("includechars") {
1110            let _ = self.scan_pdf_string();
1111        } else if self.scan_keyword("linkstate") {
1112            self.scan_int();
1113            let state = self.cur_val;
1114            self.append_whatsit(
1115                crate::nodes::whatsit_sub::PDF_LINK_STATE,
1116                Whatsit::PdfLinkState { state },
1117            );
1118        } else if self.scan_keyword("startthread") || self.scan_keyword("thread") {
1119            // Article threads are not written yet; the syntax is accepted.
1120            let _ = self.scan_pdf_rule_spec();
1121            if self.scan_keyword("attr") {
1122                let _ = self.scan_pdf_string();
1123            }
1124            if self.scan_keyword("num") {
1125                self.scan_int();
1126            } else if self.scan_keyword("name") {
1127                let _ = self.scan_pdf_string();
1128            }
1129        } else if self.scan_keyword("endthread") {
1130        } else {
1131            // LuaTeX 1.22: lower case, no help text.
1132            self.print_err("unexpected use of \\pdfextension");
1133            self.help(&[]);
1134            self.error();
1135        }
1136    }
1137
1138    /// `\pdfextension dest …`.
1139    fn pdf_dest_cmd(&mut self) {
1140        let structure = if self.scan_keyword("struct") {
1141            self.scan_int();
1142            Some(self.cur_val)
1143        } else {
1144            None
1145        };
1146        let id = if self.scan_keyword("num") {
1147            self.scan_int();
1148            DestId::Num(self.cur_val)
1149        } else if self.scan_keyword("name") {
1150            DestId::Name(self.scan_pdf_string())
1151        } else {
1152            self.pdf_fatal("identifier type missing");
1153        };
1154        let mut view = DestView {
1155            kind: DestKind::Xyz,
1156            zoom: 0,
1157            dims: [RUNNING; 3],
1158        };
1159        if self.scan_keyword("xyz") {
1160            if self.scan_keyword("zoom") {
1161                self.scan_int();
1162                view.zoom = self.cur_val;
1163            }
1164        } else if self.scan_keyword("fitbh") {
1165            view.kind = DestKind::FitBH;
1166        } else if self.scan_keyword("fitbv") {
1167            view.kind = DestKind::FitBV;
1168        } else if self.scan_keyword("fitb") {
1169            view.kind = DestKind::FitB;
1170        } else if self.scan_keyword("fith") {
1171            view.kind = DestKind::FitH;
1172        } else if self.scan_keyword("fitv") {
1173            view.kind = DestKind::FitV;
1174        } else if self.scan_keyword("fitr") {
1175            view.kind = DestKind::FitR;
1176            view.dims = self.scan_pdf_rule_spec();
1177        } else if self.scan_keyword("fit") {
1178            view.kind = DestKind::Fit;
1179        } else {
1180            self.pdf_fatal("destination type missing");
1181        }
1182        self.append_whatsit(
1183            whatsit_sub::PDF_DEST,
1184            Whatsit::PdfDest {
1185                id,
1186                view,
1187                structure,
1188            },
1189        );
1190    }
1191
1192    /// `\pdfextension outline …`.
1193    fn pdf_outline_cmd(&mut self) {
1194        let mut o = Outline::default();
1195        if self.scan_keyword("attr") {
1196            o.attr = self.scan_pdf_string();
1197        }
1198        if self.scan_keyword("useobjnum") {
1199            self.scan_int();
1200            o.action = self.cur_val;
1201        } else {
1202            let Some(a) = self.scan_pdf_action() else {
1203                return;
1204            };
1205            o.action = self.pdf_write_action(&a);
1206        }
1207        if self.scan_keyword("count") {
1208            self.scan_int();
1209            o.count = self.cur_val;
1210        }
1211        let title = self.scan_pdf_string();
1212        o.objnum = self.pdf_alloc_obj(ObjKind::Other);
1213        o.title = self.pdf_alloc_obj(ObjKind::Other);
1214        let mut body = b"(".to_vec();
1215        body.extend_from_slice(&title);
1216        body.extend_from_slice(b")\n");
1217        self.pdf_write_raw(o.title, body);
1218        self.pdf.outlines.push(o);
1219    }
1220
1221    /// `\pdfextension obj …`.
1222    fn pdf_obj(&mut self, immediate: bool) {
1223        if self.scan_keyword("reserveobjnum") {
1224            let n = self.pdf_alloc_obj(ObjKind::Obj);
1225            self.pdf.objects.insert(
1226                n,
1227                PdfObject {
1228                    num: n,
1229                    ..Default::default()
1230                },
1231            );
1232            self.pdf.last_obj = n;
1233            // Observed: a space after the keyword is skipped.
1234            self.scan_optional_space();
1235            return;
1236        }
1237        let n = if self.scan_keyword("useobjnum") {
1238            self.scan_int();
1239            self.cur_val
1240        } else {
1241            self.pdf_alloc_obj(ObjKind::Obj)
1242        };
1243        let uncompressed = self.scan_keyword("uncompressed");
1244        let mut obj = PdfObject {
1245            num: n,
1246            uncompressed,
1247            defined: true,
1248            ..Default::default()
1249        };
1250        if self.scan_keyword("stream") {
1251            obj.stream = true;
1252            if self.scan_keyword("attr") {
1253                obj.attr = self.scan_pdf_string();
1254            }
1255        }
1256        if self.scan_keyword("file") {
1257            obj.file = true;
1258        }
1259        obj.data = self.scan_pdf_string();
1260        if immediate {
1261            self.pdf.obj_words += 5;
1262        }
1263        self.pdf_define_object(obj, immediate);
1264    }
1265
1266    /// Writes object `n` through the backend now.
1267    pub fn pdf_write_object(&mut self, n: i32) {
1268        let Some(obj) = self.pdf.objects.get(&n).cloned() else {
1269            return;
1270        };
1271        if obj.written || !obj.defined {
1272            return;
1273        }
1274        if let Some(o) = self.pdf.objects.get_mut(&n) {
1275            o.written = true;
1276        }
1277        self.pdf_write_object_data(&obj);
1278    }
1279
1280    /// Hands `obj` to the backend, or queues it while the backend is busy
1281    /// shipping a page. A file object prints `<<name>>` as LuaTeX does.
1282    fn pdf_write_object_data(&mut self, obj: &PdfObject) {
1283        let Some(obj) = self.pdf_object_payload(obj) else {
1284            return;
1285        };
1286        match self.backend.take() {
1287            Some(mut be) => {
1288                be.write_object(self, &obj);
1289                self.backend = Some(be);
1290            }
1291            None => self.pdf.pending.push(obj),
1292        }
1293    }
1294
1295    /// Takes object `n` (defined and not yet written) for writing with the
1296    /// current page (`\pdfextension refobj`).
1297    pub(crate) fn pdf_take_page_object(&mut self, n: i32) -> Option<PdfObject> {
1298        let obj = self.pdf.objects.get_mut(&n)?;
1299        if obj.written || !obj.defined {
1300            return None;
1301        }
1302        obj.written = true;
1303        let obj = obj.clone();
1304        self.pdf_object_payload(&obj)
1305    }
1306
1307    /// The object as written: a file object gets the file's contents
1308    /// (printing `<<name>>` as LuaTeX does).
1309    fn pdf_object_payload(&mut self, obj: &PdfObject) -> Option<PdfObject> {
1310        let mut obj = obj.clone();
1311        if obj.file {
1312            let name = String::from_utf8_lossy(&obj.data).into_owned();
1313            let path = self
1314                .kpse
1315                .as_ref()
1316                .and_then(|k| {
1317                    k.borrow_mut()
1318                        .find_file(&name, retex_kpath::FileFormat::Tex)
1319                })
1320                .unwrap_or_else(|| std::path::PathBuf::from(&name));
1321            match std::fs::read(&path) {
1322                Ok(d) => {
1323                    self.printer.print_str("<<");
1324                    self.printer.print_bytes(name.as_bytes());
1325                    self.printer.print_str(">>");
1326                    obj.data = d;
1327                    obj.file = false;
1328                }
1329                Err(_) => {
1330                    self.print_err("pdf backend: cannot open file for embedding");
1331                    self.help(&[]);
1332                    self.error();
1333                    return None;
1334                }
1335            }
1336        }
1337        Some(obj)
1338    }
1339
1340    /// A token list of whatsit data, for late expansion.
1341    pub fn whatsit_tokens(list: &TokenList) -> LateData {
1342        LateData::Tokens(list.clone())
1343    }
1344
1345    /// Works out what the backend needs at the end of the run, and prints
1346    /// LuaTeX's warnings about destinations and threads that were referred
1347    /// to but never defined.
1348    pub fn pdf_finish_document(&mut self) {
1349        let mut doc = DocItems::default();
1350        // Observed order: missing pages, then destinations (each in the
1351        // reverse order of allocation), then threads; the warnings are
1352        // separated by empty lines.
1353        let mut warnings = Vec::new();
1354        let mut pages: Vec<(i32, i32)> = self
1355            .pdf
1356            .page_refs
1357            .iter()
1358            .filter(|(p, _)| **p > self.total_pages as i32)
1359            .map(|(p, n)| (*n, *p))
1360            .collect();
1361        pages.sort_unstable_by(|a, b| b.cmp(a));
1362        for (_, p) in pages {
1363            warnings.push(format!("page {p} has been referenced but does not exist"));
1364        }
1365        let order = self.pdf.dest_order.clone();
1366        for id in order.iter().rev() {
1367            if let Some(d) = self.pdf.dests.get(id).copied()
1368                && !d.defined
1369            {
1370                warnings.push(format!("unreferenced destination with {}", id.describe()));
1371                doc.missing_dests.push(d.objnum);
1372            }
1373        }
1374        for (id, n) in self.pdf.threads.clone() {
1375            warnings.push(format!("unknown thread destination {}", id.describe()));
1376            let title = match id {
1377                DestId::Name(n) => n,
1378                DestId::Num(k) => k.to_string().into_bytes(),
1379            };
1380            doc.missing_threads.push((n, title));
1381        }
1382        for (i, w) in warnings.iter().enumerate() {
1383            if i > 0 {
1384                self.printer.print_ln();
1385            }
1386            self.pdf_warning(w);
1387        }
1388        let mut names: Vec<(Vec<u8>, i32)> = self
1389            .pdf
1390            .dests
1391            .iter()
1392            .filter_map(|(k, d)| match k {
1393                DestId::Name(n) => Some((n.clone(), d.objnum)),
1394                DestId::Num(_) => None,
1395            })
1396            .collect();
1397        names.sort();
1398        doc.dest_names = names;
1399        let toks = |e: &mut Engine, i: u32| {
1400            let list = e.eqtb.toks_param(i).clone();
1401            e.print_to_string(|e| e.show_token_list(&list, 0, list.len(), i64::MAX))
1402        };
1403        doc.pages_attr = toks(self, crate::backend::PAGES_ATTR);
1404        doc.trailer_id = toks(self, crate::backend::TRAILER_ID);
1405        self.pdf.doc = doc;
1406    }
1407}