retex-lua 1.3.0

Embedded Lua and the LuaTeX Lua API for retex
Documentation
//! The `vf` library (LuaTeX manual, "The `vf` library"): drawing into the
//! packet of the virtual character whose `lua` command is running, as
//! the packet's own commands do. Called outside such a command, the
//! functions do nothing.

use mlua::{Lua, MultiValue, Table, Value};
use retex_engine::fonts::PacketCommand;

use crate::pdflib::{MODES, check_integer, coerce_bytes};
use crate::with_engine;

/// Installs the global `vf` table.
pub fn install(lua: &Lua) -> mlua::Result<()> {
    let t = lua.create_table()?;
    simple(lua, &t, "push", PacketCommand::Push)?;
    simple(lua, &t, "pop", PacketCommand::Pop)?;
    t.set("nop", lua.create_function(|_, _: MultiValue| Ok(()))?)?;
    // Moves are fix_words scaled by the size of the virtual font
    // (observed), as in VF files.
    for name in ["right", "down"] {
        let f = crate::create_function_caller_errors(lua, move |lua, args: MultiValue| {
            let x = check_integer(lua, args.front(), 1, name)?;
            with_engine(lua, |e| {
                if let Some(x) = e.vf_scaled(x) {
                    let cmd = if name == "right" {
                        PacketCommand::Right(x)
                    } else {
                        PacketCommand::Down(x)
                    };
                    e.vf_command(&cmd);
                }
            })
        })?;
        t.set(name, f)?;
    }
    t.set(
        "rule",
        crate::create_function_caller_errors(lua, |lua, args: MultiValue| {
            let w = check_integer(lua, args.front(), 1, "rule")?;
            let h = check_integer(lua, args.get(1), 2, "rule")?;
            with_engine(lua, |e| {
                // Observed: width first, unlike the `rule` command.
                if let (Some(width), Some(height)) = (e.vf_scaled(w), e.vf_scaled(h)) {
                    e.vf_command(&PacketCommand::Rule { height, width });
                }
            })
        })?,
    )?;
    t.set(
        "char",
        crate::create_function_caller_errors(lua, |lua, args: MultiValue| {
            let c = check_integer(lua, args.front(), 1, "char")?;
            with_engine(lua, |e| {
                e.vf_command(&PacketCommand::Char(c as u32));
            })
        })?,
    )?;
    t.set(
        "fontid",
        crate::create_function_caller_errors(lua, |lua, args: MultiValue| {
            let f = check_integer(lua, args.front(), 1, "fontid")?;
            with_engine(lua, |e| {
                if f > 0 && e.font_exists(f as i32) {
                    e.vf_command(&PacketCommand::FontId(f as u32));
                }
            })
        })?,
    )?;
    t.set(
        "special",
        crate::create_function_caller_errors(lua, |lua, args: MultiValue| {
            let Some(s) = args
                .front()
                .map(|v| coerce_bytes(lua, v))
                .transpose()?
                .flatten()
            else {
                return Ok(());
            };
            with_engine(lua, |e| {
                e.vf_command(&PacketCommand::Special(s));
            })
        })?,
    )?;
    t.set(
        "pdf",
        crate::create_function_caller_errors(lua, |lua, args: MultiValue| {
            let args = args.into_vec();
            // `vf.pdf(text)` writes in origin mode, `vf.pdf(mode, text)`
            // in the named one.
            let (mode, text) = match args.as_slice() {
                [text] => (retex_engine::pdfext::literal_mode::ORIGIN, text),
                [Value::String(m), text, ..] => {
                    let m = m.as_bytes();
                    let mode = MODES
                        .iter()
                        .chain(&[("font", 5)])
                        .find(|(n, _)| n.as_bytes() == m.as_ref())
                        .map_or(retex_engine::pdfext::literal_mode::ORIGIN, |&(_, m)| m);
                    (mode, text)
                }
                _ => return Ok(()),
            };
            let Some(text) = coerce_bytes(lua, text)? else {
                return Ok(());
            };
            with_engine(lua, |e| {
                e.vf_command(&PacketCommand::Pdf(mode, text));
            })
        })?,
    )?;
    t.set(
        "node",
        crate::create_function_caller_errors(lua, |lua, args: MultiValue| {
            let Some(p) = args.front().and_then(crate::nodelib::to_ref) else {
                return Ok(());
            };
            with_engine(lua, |e| {
                if !e.nodes.is_free(p) {
                    e.vf_command(&PacketCommand::Node(p));
                }
            })
        })?,
    )?;
    // Images: retex has no `img` library.
    t.set("image", lua.create_function(|_, _: MultiValue| Ok(()))?)?;
    lua.globals().set("vf", t)
}

/// Installs `vf.<name>`, which runs `cmd`.
fn simple(lua: &Lua, t: &Table, name: &str, cmd: PacketCommand) -> mlua::Result<()> {
    let f = lua.create_function(move |lua, _: MultiValue| {
        with_engine(lua, |e| {
            e.vf_command(&cmd);
        })
    })?;
    t.set(name, f)
}