retex-engine 1.3.0

The TeX typesetting engine for retex
Documentation
//! pgf's system layer for PDF output: the protocol of path and drawing
//! operators (`pgfsys.code.tex`, `pgfsysprotocol.code.tex`).
//!
//! - `\pgf@sys@bp{<dimen>}` appends the dimension in big points (times
//!   0.99627, as `\the` prints it) and a space to
//!   `\pgfsysprotocol@currentprotocol` (globally), in a group that leaves
//!   nothing else behind;
//! - `\pgfsysprotocol@literal{<text>}` sets `\pgfsysprotocol@temp` to
//!   `{<text> }` (locally), appends `<text> ` to the protocol, and unless
//!   `\ifpgfsysprotocol@buffered`, invokes it (`\pgfsys@invoke{…}`, which
//!   the native code puts back into the input) and empties it.
//!
//! Every path element of a TikZ picture goes through these. They decline
//! in math mode (where the group would be a math group), with
//! `\globaldefs` or a pending `\afterassignment`, for arguments other than
//! a decimal with `pt` (or a dimension register) and plain text, and when
//! the macros they run have other definitions.

use std::cell::RefCell;
use std::rc::Rc;

use super::Native;
use super::deps::{self, Deps};
use super::pgfdim as d;
use crate::Engine;
use crate::cmd::Cmd;
use crate::eqtb::{Loc, Meaning, Val};
use crate::maincontrol::ModeKind;
use crate::primitives_gen::assign_int as ip;
use crate::scan::REG_OFFSET;
use crate::token::{Macro, Token, TokenList};

/// `\pgf@sys@bp`.
pub(super) const BP: Native = Native {
    name: "pgf@sys@bp",
    fingerprints: &[(42, 0x2126_1980_64d9_9c6f), (34, 0xdde7_4792_5465_d252)],
    executes: true,
    run: run_bp,
};

/// `\pgfsysprotocol@literal`.
pub(super) const LITERAL: Native = Native {
    name: "pgfsysprotocol@literal",
    fingerprints: &[(10, 0x01eb_1c2f_2bfc_8691)],
    executes: true,
    run: run_literal,
};

const NAMES: &[&str] = &[
    "pgfsysprotocol@literalbuffered",
    "pgfsysprotocol@flushcurrentprotocol",
    "pgfsysprotocol@invokecurrentprotocol",
    "pgfsysprotocol@setcurrentprotocol",
    "pgfutil@g@addto@macro",
    "pgfutil@toks@",
    "pgfutil@empty",
    "space",
    "pgfsys@invoke",
    "Pgf@geT",
    "pgf@x",
];

const KNOWN: &[u64] = &[0xf914_c27d_e94d_695a];

/// The token count of pgf 3.1.12's `\pgf@sys@bp`, which converts with
/// `\pgf@sys@bp@correct` ([`bp_correct`]) instead of `0.99627`.
const BP_NEW_LEN: usize = 34;

/// What pgf 3.1.12's `\pgf@sys@bp@correct\pgf@x` runs.
const CORRECT_NAMES: &[&str] = &[
    "pgf@sys@bp@correct",
    "pgf@sys@bp@correct@",
    "number",
    "dimexpr",
    "numexpr",
    "the",
    "if",
    "else",
    "fi",
    "relax",
];

const CORRECT_KNOWN: &[u64] = &[0x17df_ed31_7a05_e84b];

thread_local! {
    static DEPS: RefCell<Deps> = RefCell::new(Deps::new("pgfsys", NAMES, KNOWN));
    static CORRECT_DEPS: RefCell<Deps> =
        RefCell::new(Deps::new("pgfsys bp correction", CORRECT_NAMES, CORRECT_KNOWN));
}

/// pgf 3.1.12's
/// `\pgf@x=\dimexpr\the\numexpr(2*<n>\if<sign>--\else+\fi1)*400/803\relax sp`
/// for `<n>` = `\number\pgf@x` (`<sign>` its first character): e-TeX's
/// `\numexpr` computes the product exactly and rounds the quotient half
/// away from zero.
fn bp_correct(x: i32) -> i32 {
    let n = i64::from(x);
    let a = 2 * n + if n < 0 { -1 } else { 1 };
    let p = a * 400;
    let q = (2 * p.abs() + 803) / (2 * 803);
    (if p < 0 { -q } else { q }) as i32
}

fn why(reason: &str) -> bool {
    if super::debug() {
        eprintln!("[native] pgfsys declines ({reason})");
    }
    false
}

/// Common checks; the protocol macro's id and body.
fn protocol(e: &Engine) -> Option<(crate::hash::CsId, Vec<Token>)> {
    if e.eqtb.int(ip::GLOBALDEFS) != 0
        || e.after_token.0 != 0
        || !matches!(e.mode.abs_kind(), ModeKind::Vertical | ModeKind::Horizontal)
    {
        return None;
    }
    if !DEPS.with(|d| d.borrow_mut().check(e)) {
        why("dependencies");
        return None;
    }
    let cs = e.hash.lookup(b"pgfsysprotocol@currentprotocol")?;
    let m = e.eqtb.meaning(cs);
    if m.cmd != Cmd::Call || m.mac.as_ref()?.protected {
        return None;
    }
    let body = deps::plain_body(e, cs)?;
    Some((cs, body.to_vec()))
}

fn macro_meaning(body: Vec<Token>) -> Meaning {
    let mut t = Vec::with_capacity(body.len() + 1);
    t.push(Token::END_MATCH);
    t.extend(body);
    Meaning {
        cmd: Cmd::Call,
        chr: 0,
        mac: Some(Rc::new(Macro::new(false, Rc::from(t)))),
    }
}

/// The value of `\pgf@x=#1\relax` for the forms pgf passes: a decimal
/// followed by `pt`, or one dimension register.
fn dimension(e: &Engine, arg: &[Token]) -> Option<i32> {
    // `\the<register>`: its value, printed and read back.
    if let [the, reg] = arg
        && the.is_cs()
        && reg.is_cs()
    {
        let m = e.eqtb.meaning(the.cs_id());
        if m.cmd == Cmd::The && m.chr == 0 && m.mac.is_none() {
            return dimension(e, &arg[1..]);
        }
        return None;
    }
    if let [t] = arg
        && t.is_cs()
    {
        let m = e.eqtb.meaning(t.cs_id());
        if m.cmd == Cmd::AssignDimen && m.chr >= REG_OFFSET && m.mac.is_none() {
            return Some(e.eqtb.dimen[(m.chr - REG_OFFSET) as usize].val);
        }
        return None;
    }
    let mut s = Vec::with_capacity(arg.len());
    for t in arg {
        if t.is_cs() || t.chr() > 0x7F {
            return None;
        }
        let c = t.cmd_code();
        if c != Cmd::OtherChar as u32 && c != Cmd::Letter as u32 {
            return None;
        }
        s.push(t.chr() as u8);
    }
    if s.len() < 3 {
        return None;
    }
    let (num, unit) = s.split_at(s.len() - 2);
    let unit = d::Unit::from_name(unit[0], unit[1])?;
    if num
        .iter()
        .any(|c| !(c.is_ascii_digit() || *c == b'.' || *c == b'-' || *c == b'+'))
    {
        return None;
    }
    // The digits must be of category 12 (TeX's digits); `pt` may be either.
    if arg[..num.len()]
        .iter()
        .any(|t| t.cmd_code() != Cmd::OtherChar as u32)
    {
        return None;
    }
    d::scan_unit(num, unit).ok()
}

fn run_bp(e: &mut Engine, args: &[TokenList]) -> bool {
    let Some((cs, mut body)) = protocol(e) else {
        return false;
    };
    let Some(x) = dimension(e, &args[0]) else {
        if super::debug() {
            let shown: String = args[0]
                .iter()
                .map(|t| {
                    if t.is_cs() {
                        format!("\\{} ", String::from_utf8_lossy(e.hash.name(t.cs_id())))
                    } else {
                        char::from_u32(t.chr()).unwrap_or('?').to_string()
                    }
                })
                .collect();
            eprintln!("[native] pgfsys bp argument: {shown}");
        }
        return why("bp argument");
    };
    let new_formula = e
        .hash
        .lookup(b"pgf@sys@bp")
        .and_then(|cs| e.eqtb.meaning(cs).mac.as_ref().map(|m| m.tokens.len()))
        == Some(BP_NEW_LEN);
    let x = if new_formula {
        if !CORRECT_DEPS.with(|d| d.borrow_mut().check(e)) {
            return why("bp correction");
        }
        bp_correct(x)
    } else {
        let Ok(x) = d::scan_factor(b"0.99627", x) else {
            return why("bp overflow");
        };
        x
    };
    body.extend(d::print(x).iter().map(|&c| Token::other(u32::from(c))));
    body.push(Token::SPACE);
    e.geq_define(Loc::Cs(cs), Val::Meaning(macro_meaning(body)));
    if super::debug() {
        eprintln!("[native] pgfsys bp native");
    }
    true
}

fn run_literal(e: &mut Engine, args: &[TokenList]) -> bool {
    let Some((cs, mut body)) = protocol(e) else {
        return false;
    };
    let text = &args[0];
    if text.iter().any(|t| {
        t.is_cs()
            || !(t.cmd_code() == Cmd::OtherChar as u32
                || t.cmd_code() == Cmd::Letter as u32
                || (t.cmd_code() == Cmd::Spacer as u32 && t.chr() == u32::from(b' ')))
    }) {
        return why("literal text");
    }
    let lookup = |n: &[u8]| e.hash.lookup(n);
    let (Some(temp), Some(buffered), Some(invoke), Some(empty)) = (
        lookup(b"pgfsysprotocol@temp"),
        lookup(b"ifpgfsysprotocol@buffered"),
        lookup(b"pgfsys@invoke"),
        lookup(b"pgfutil@empty"),
    ) else {
        return false;
    };
    let bm = e.eqtb.meaning(buffered);
    let buffered = match (bm.cmd, bm.chr, bm.mac.is_none()) {
        (Cmd::IfTest, 14, true) => true,
        (Cmd::IfTest, 15, true) => false,
        _ => return why("buffered flag"),
    };
    let mut t = vec![Token::LEFT_BRACE];
    t.extend_from_slice(text);
    t.push(Token::SPACE);
    t.push(Token::RIGHT_BRACE);
    e.eq_define(Loc::Cs(temp), Val::Meaning(macro_meaning(t)));
    body.extend_from_slice(text);
    body.push(Token::SPACE);
    if buffered {
        e.geq_define(Loc::Cs(cs), Val::Meaning(macro_meaning(body)));
    } else {
        let em = e.eqtb.meaning(empty).clone();
        e.geq_define(Loc::Cs(cs), Val::Meaning(em));
        let mut ins = Vec::with_capacity(body.len() + 3);
        ins.push(Token::cs(invoke));
        ins.push(Token::LEFT_BRACE);
        ins.extend(body);
        ins.push(Token::RIGHT_BRACE);
        e.native_insert(ins);
    }
    if super::debug() {
        eprintln!("[native] pgfsys literal native");
    }
    true
}