Skip to main content

retex_engine/
backend.rs

1//! Backend variables and feedback (`\pdfvariable`, `\pdffeedback`,
2//! `\dvivariable`, `\dvifeedback`).
3//!
4//! `\pdfvariable name` expands to an anonymous token standing for an
5//! internal parameter; assignments and `\the` then work on it like on any
6//! parameter. The parameter indices are those LuaTeX reports through
7//! `\meaning` (e.g. `[unknown command code! (88, 137)]` for
8//! `compresslevel`).
9
10use std::rc::Rc;
11
12use crate::Engine;
13use crate::cmd::Cmd;
14use crate::eqtb::Meaning;
15use crate::token::Token;
16
17/// Kinds of backend variables.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum VarKind {
20    /// An integer parameter.
21    Int,
22    /// A dimension parameter.
23    Dimen,
24    /// A token-list parameter.
25    Toks,
26}
27
28/// `\pdfvariable horigin` (index of the dimension parameter).
29pub const H_ORIGIN: u32 = 28;
30/// `\pdfvariable vorigin` (index of the dimension parameter).
31pub const V_ORIGIN: u32 = 29;
32/// `\pdfvariable compresslevel`.
33pub const COMPRESS_LEVEL: u32 = 137;
34/// `\pdfvariable decimaldigits`.
35pub const DECIMAL_DIGITS: u32 = 138;
36/// `\pdfvariable objcompresslevel`.
37pub const OBJ_COMPRESS_LEVEL: u32 = 152;
38/// `\pdfvariable minorversion`.
39pub const MINOR_VERSION: u32 = 143;
40
41/// `\pdfvariable majorversion` (index of the integer parameter).
42pub const MAJOR_VERSION: u32 = 142;
43/// `\pdfvariable pkresolution` (index of the integer parameter).
44pub const PK_RESOLUTION: u32 = 140;
45/// `\pdfvariable pkfixeddpi`.
46pub const PK_FIXED_DPI: u32 = 155;
47/// `\pdfvariable pageattr` (index of the token-list parameter).
48pub const PAGE_ATTR: u32 = 11;
49/// `\pdfvariable pageresources`.
50pub const PAGE_RESOURCES: u32 = 12;
51/// `\pdfvariable pagesattr`.
52pub const PAGES_ATTR: u32 = 13;
53/// `\pdfvariable trailerid`.
54pub const TRAILER_ID: u32 = 17;
55
56/// The PDF backend variables: name, kind and parameter index.
57pub const PDF_VARIABLES: &[(&str, VarKind, u32)] = &[
58    ("compresslevel", VarKind::Int, 137),
59    ("decimaldigits", VarKind::Int, 138),
60    ("imageresolution", VarKind::Int, 139),
61    ("pkresolution", VarKind::Int, 140),
62    ("uniqueresname", VarKind::Int, 141),
63    ("majorversion", VarKind::Int, 142),
64    ("minorversion", VarKind::Int, 143),
65    ("pagebox", VarKind::Int, 144),
66    ("inclusionerrorlevel", VarKind::Int, 145),
67    ("ignoreunknownimages", VarKind::Int, 146),
68    ("gamma", VarKind::Int, 147),
69    ("imageapplygamma", VarKind::Int, 148),
70    ("imagegamma", VarKind::Int, 149),
71    ("imagehicolor", VarKind::Int, 150),
72    ("imageaddfilename", VarKind::Int, 151),
73    ("objcompresslevel", VarKind::Int, 152),
74    ("inclusioncopyfonts", VarKind::Int, 153),
75    ("gentounicode", VarKind::Int, 154),
76    ("pkfixeddpi", VarKind::Int, 155),
77    ("suppressoptionalinfo", VarKind::Int, 156),
78    ("omitcidset", VarKind::Int, 157),
79    ("recompress", VarKind::Int, 158),
80    ("omitcharset", VarKind::Int, 159),
81    ("omitinfodict", VarKind::Int, 160),
82    ("omitmediabox", VarKind::Int, 161),
83    ("omitprocset", VarKind::Int, 163),
84    ("ptexprefix", VarKind::Int, 164),
85    ("horigin", VarKind::Dimen, 28),
86    ("vorigin", VarKind::Dimen, 29),
87    ("threadmargin", VarKind::Dimen, 30),
88    ("destmargin", VarKind::Dimen, 31),
89    ("linkmargin", VarKind::Dimen, 32),
90    ("xformmargin", VarKind::Dimen, 33),
91    ("pageattr", VarKind::Toks, 11),
92    ("pageresources", VarKind::Toks, 12),
93    ("pagesattr", VarKind::Toks, 13),
94    ("xformattr", VarKind::Toks, 14),
95    ("xformresources", VarKind::Toks, 15),
96    ("pkmode", VarKind::Toks, 16),
97    ("trailerid", VarKind::Toks, 17),
98];
99
100impl Engine {
101    /// Expands `\pdfvariable name` to its internal token.
102    pub fn expand_backend_variable(&mut self) {
103        let found = PDF_VARIABLES
104            .iter()
105            .find(|(name, _, _)| self.scan_keyword(name))
106            .copied();
107        let Some((name, kind, index)) = found else {
108            return;
109        };
110        let cs = match self.backend_cs.get(name) {
111            Some(&cs) => cs,
112            None => {
113                let (label, cmd) = match kind {
114                    VarKind::Int => ("[internal backend integer]", Cmd::AssignInt),
115                    VarKind::Dimen => ("[internal backend dimension]", Cmd::AssignDimen),
116                    VarKind::Toks => ("[internal backend tokenlist]", Cmd::AssignToks),
117                };
118                let cs = self.hash.new_internal(label.as_bytes());
119                self.eqtb.ensure_cs(cs);
120                self.eqtb.cs[cs.0 as usize].val = Meaning::prim(cmd, index);
121                self.eqtb.cs[cs.0 as usize].level = crate::eqtb::LEVEL_ONE;
122                self.backend_cs.insert(name, cs);
123                cs
124            }
125        };
126        self.cur_tok = Token::cs(cs);
127        self.back_input();
128    }
129
130    /// Expands `\pdffeedback name`.
131    pub fn expand_backend_feedback(&mut self) {
132        if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) <= 0 {
133            // Observed: an error in DVI mode; the keyword is not read.
134            self.print_err("unexpected use of \\pdffeedback");
135            self.help(&[]);
136            self.error();
137            return;
138        }
139        const KEYS: &[&str] = &[
140            "version",
141            "revision",
142            "lastlink",
143            "retval",
144            "lastobj",
145            "lastannot",
146            "xformname",
147            "creationdate",
148            "fontname",
149            "fontobjnum",
150            "fontsize",
151            "pageref",
152            "colorstackinit",
153        ];
154        let key = KEYS.iter().find(|k| self.scan_keyword(k)).copied();
155        let text: Vec<u8> = match key {
156            Some("version") => b"140".to_vec(),
157            Some("lastobj") => self.pdf.last_obj.to_string().into_bytes(),
158            Some("lastannot") => self.pdf.last_annot.to_string().into_bytes(),
159            Some("lastlink") => self.pdf.last_link.to_string().into_bytes(),
160            Some("revision") => b"0".to_vec(),
161            Some("retval") => self.pdf.retval.to_string().into_bytes(),
162            Some("creationdate") => self.pdf_creation_date().into_bytes(),
163            Some(k @ ("fontname" | "fontobjnum" | "fontsize")) => {
164                self.scan_font_ident();
165                let f = self.cur_val.max(0) as u32;
166                if f == 0 {
167                    // A fatal error, as in LuaTeX, which names fontobjnum
168                    // 'objnum' (observed).
169                    let k = if k == "fontobjnum" { "objnum" } else { k };
170                    self.pdf_fatal(&format!("invalid font identifier when asking '{k}'"));
171                }
172                match k {
173                    "fontname" => self.pdf_font(f).0.to_string().into_bytes(),
174                    "fontobjnum" => self.pdf_font(f).1.to_string().into_bytes(),
175                    _ => {
176                        let mut out = String::new();
177                        retex_arith::print_scaled(&mut out, self.font(f).size);
178                        out.push_str("pt");
179                        out.into_bytes()
180                    }
181                }
182            }
183            Some("xformname") => {
184                // The name number of a saved box resource (observed: its
185                // index); an unknown one is fatal, as in LuaTeX.
186                self.scan_int();
187                let n = self.cur_val;
188                if n <= 0 || n as usize > self.box_resources.len() {
189                    self.pdf_fatal("cannot find referenced object");
190                } else {
191                    n.to_string().into_bytes()
192                }
193            }
194            Some("pageref") => {
195                self.scan_int();
196                let page = self.cur_val;
197                if page <= 0 {
198                    self.pdf_fatal("invalid page number when asking 'pageref'");
199                } else {
200                    self.pdf_page_ref(page).to_string().into_bytes()
201                }
202            }
203            Some("colorstackinit") => {
204                let page = self.scan_keyword("page");
205                let direct = self.scan_keyword("direct");
206                let toks = self.scan_toks_expanded();
207                let init =
208                    self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
209                self.pdf_new_colorstack(
210                    init,
211                    page,
212                    if direct {
213                        crate::pdfext::literal_mode::DIRECT
214                    } else {
215                        crate::pdfext::literal_mode::ORIGIN
216                    },
217                )
218                .to_string()
219                .into_bytes()
220            }
221            Some(_) => b"0".to_vec(),
222            None => Vec::new(),
223        };
224        let toks = crate::the::str_toks(&text);
225        self.ins_list(Rc::from(toks));
226    }
227
228    /// The creation date in PDF format, as `\pdffeedback creationdate`.
229    pub fn pdf_creation_date(&self) -> String {
230        self.start_time.pdf_date()
231    }
232}