retex-engine 1.3.0-alpha.2

The TeX typesetting engine for retex
Documentation
//! Backend variables and feedback (`\pdfvariable`, `\pdffeedback`,
//! `\dvivariable`, `\dvifeedback`).
//!
//! `\pdfvariable name` expands to an anonymous token standing for an
//! internal parameter; assignments and `\the` then work on it like on any
//! parameter. The parameter indices are those LuaTeX reports through
//! `\meaning` (e.g. `[unknown command code! (88, 137)]` for
//! `compresslevel`).

use std::rc::Rc;

use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::Meaning;
use crate::token::Token;

/// Kinds of backend variables.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VarKind {
    /// An integer parameter.
    Int,
    /// A dimension parameter.
    Dimen,
    /// A token-list parameter.
    Toks,
}

/// `\pdfvariable horigin` (index of the dimension parameter).
pub const H_ORIGIN: u32 = 28;
/// `\pdfvariable vorigin` (index of the dimension parameter).
pub const V_ORIGIN: u32 = 29;
/// `\pdfvariable compresslevel`.
pub const COMPRESS_LEVEL: u32 = 137;
/// `\pdfvariable decimaldigits`.
pub const DECIMAL_DIGITS: u32 = 138;
/// `\pdfvariable objcompresslevel`.
pub const OBJ_COMPRESS_LEVEL: u32 = 152;
/// `\pdfvariable minorversion`.
pub const MINOR_VERSION: u32 = 143;

/// `\pdfvariable majorversion` (index of the integer parameter).
pub const MAJOR_VERSION: u32 = 142;
/// `\pdfvariable pkresolution` (index of the integer parameter).
pub const PK_RESOLUTION: u32 = 140;
/// `\pdfvariable pkfixeddpi`.
pub const PK_FIXED_DPI: u32 = 155;
/// `\pdfvariable pageattr` (index of the token-list parameter).
pub const PAGE_ATTR: u32 = 11;
/// `\pdfvariable pageresources`.
pub const PAGE_RESOURCES: u32 = 12;
/// `\pdfvariable pagesattr`.
pub const PAGES_ATTR: u32 = 13;
/// `\pdfvariable trailerid`.
pub const TRAILER_ID: u32 = 17;

/// The PDF backend variables: name, kind and parameter index.
pub const PDF_VARIABLES: &[(&str, VarKind, u32)] = &[
    ("compresslevel", VarKind::Int, 137),
    ("decimaldigits", VarKind::Int, 138),
    ("imageresolution", VarKind::Int, 139),
    ("pkresolution", VarKind::Int, 140),
    ("uniqueresname", VarKind::Int, 141),
    ("majorversion", VarKind::Int, 142),
    ("minorversion", VarKind::Int, 143),
    ("pagebox", VarKind::Int, 144),
    ("inclusionerrorlevel", VarKind::Int, 145),
    ("ignoreunknownimages", VarKind::Int, 146),
    ("gamma", VarKind::Int, 147),
    ("imageapplygamma", VarKind::Int, 148),
    ("imagegamma", VarKind::Int, 149),
    ("imagehicolor", VarKind::Int, 150),
    ("imageaddfilename", VarKind::Int, 151),
    ("objcompresslevel", VarKind::Int, 152),
    ("inclusioncopyfonts", VarKind::Int, 153),
    ("gentounicode", VarKind::Int, 154),
    ("pkfixeddpi", VarKind::Int, 155),
    ("suppressoptionalinfo", VarKind::Int, 156),
    ("omitcidset", VarKind::Int, 157),
    ("recompress", VarKind::Int, 158),
    ("omitcharset", VarKind::Int, 159),
    ("omitinfodict", VarKind::Int, 160),
    ("omitmediabox", VarKind::Int, 161),
    ("omitprocset", VarKind::Int, 163),
    ("ptexprefix", VarKind::Int, 164),
    ("horigin", VarKind::Dimen, 28),
    ("vorigin", VarKind::Dimen, 29),
    ("threadmargin", VarKind::Dimen, 30),
    ("destmargin", VarKind::Dimen, 31),
    ("linkmargin", VarKind::Dimen, 32),
    ("xformmargin", VarKind::Dimen, 33),
    ("pageattr", VarKind::Toks, 11),
    ("pageresources", VarKind::Toks, 12),
    ("pagesattr", VarKind::Toks, 13),
    ("xformattr", VarKind::Toks, 14),
    ("xformresources", VarKind::Toks, 15),
    ("pkmode", VarKind::Toks, 16),
    ("trailerid", VarKind::Toks, 17),
];

impl Engine {
    /// Expands `\pdfvariable name` to its internal token.
    pub fn expand_backend_variable(&mut self) {
        let found = PDF_VARIABLES
            .iter()
            .find(|(name, _, _)| self.scan_keyword(name))
            .copied();
        let Some((name, kind, index)) = found else {
            return;
        };
        let cs = match self.backend_cs.get(name) {
            Some(&cs) => cs,
            None => {
                let (label, cmd) = match kind {
                    VarKind::Int => ("[internal backend integer]", Cmd::AssignInt),
                    VarKind::Dimen => ("[internal backend dimension]", Cmd::AssignDimen),
                    VarKind::Toks => ("[internal backend tokenlist]", Cmd::AssignToks),
                };
                let cs = self.hash.new_internal(label.as_bytes());
                self.eqtb.ensure_cs(cs);
                self.eqtb.cs[cs.0 as usize].val = Meaning::prim(cmd, index);
                self.eqtb.cs[cs.0 as usize].level = crate::eqtb::LEVEL_ONE;
                self.backend_cs.insert(name, cs);
                cs
            }
        };
        self.cur_tok = Token::cs(cs);
        self.back_input();
    }

    /// Expands `\pdffeedback name`.
    pub fn expand_backend_feedback(&mut self) {
        if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) <= 0 {
            // Observed: an error in DVI mode; the keyword is not read.
            self.print_err("unexpected use of \\pdffeedback");
            self.help(&[]);
            self.error();
            return;
        }
        const KEYS: &[&str] = &[
            "version",
            "revision",
            "lastlink",
            "retval",
            "lastobj",
            "lastannot",
            "xformname",
            "creationdate",
            "fontname",
            "fontobjnum",
            "fontsize",
            "pageref",
            "colorstackinit",
        ];
        let key = KEYS.iter().find(|k| self.scan_keyword(k)).copied();
        let text: Vec<u8> = match key {
            Some("version") => b"140".to_vec(),
            Some("lastobj") => self.pdf.last_obj.to_string().into_bytes(),
            Some("lastannot") => self.pdf.last_annot.to_string().into_bytes(),
            Some("lastlink") => self.pdf.last_link.to_string().into_bytes(),
            Some("revision") => b"0".to_vec(),
            Some("retval") => self.pdf.retval.to_string().into_bytes(),
            Some("creationdate") => self.pdf_creation_date().into_bytes(),
            Some(k @ ("fontname" | "fontobjnum" | "fontsize")) => {
                self.scan_font_ident();
                let f = self.cur_val.max(0) as u32;
                if f == 0 {
                    // A fatal error, as in LuaTeX, which names fontobjnum
                    // 'objnum' (observed).
                    let k = if k == "fontobjnum" { "objnum" } else { k };
                    self.pdf_fatal(&format!("invalid font identifier when asking '{k}'"));
                }
                match k {
                    "fontname" => self.pdf_font(f).0.to_string().into_bytes(),
                    "fontobjnum" => self.pdf_font(f).1.to_string().into_bytes(),
                    _ => {
                        let mut out = String::new();
                        retex_arith::print_scaled(&mut out, self.font(f).size);
                        out.push_str("pt");
                        out.into_bytes()
                    }
                }
            }
            Some("xformname") => {
                // The name number of a saved box resource (observed: its
                // index); an unknown one is fatal, as in LuaTeX.
                self.scan_int();
                let n = self.cur_val;
                if n <= 0 || n as usize > self.box_resources.len() {
                    self.pdf_fatal("cannot find referenced object");
                } else {
                    n.to_string().into_bytes()
                }
            }
            Some("pageref") => {
                self.scan_int();
                let page = self.cur_val;
                if page <= 0 {
                    self.pdf_fatal("invalid page number when asking 'pageref'");
                } else {
                    self.pdf_page_ref(page).to_string().into_bytes()
                }
            }
            Some("colorstackinit") => {
                let page = self.scan_keyword("page");
                let direct = self.scan_keyword("direct");
                let toks = self.scan_toks_expanded();
                let init =
                    self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
                self.pdf_new_colorstack(
                    init,
                    page,
                    if direct {
                        crate::pdfext::literal_mode::DIRECT
                    } else {
                        crate::pdfext::literal_mode::ORIGIN
                    },
                )
                .to_string()
                .into_bytes()
            }
            Some(_) => b"0".to_vec(),
            None => Vec::new(),
        };
        let toks = crate::the::str_toks(&text);
        self.ins_list(Rc::from(toks));
    }

    /// The creation date in PDF format, as `\pdffeedback creationdate`.
    pub fn pdf_creation_date(&self) -> String {
        self.start_time.pdf_date()
    }
}