retex-lua 1.1.0

Embedded Lua and the LuaTeX Lua API for retex
Documentation
//! The `callback` library (LuaTeX manual ยง9): registration only. The
//! engine queries registered callbacks through [`crate::LuaTexHost`].

use mlua::{Function, Lua, Table, Value};

/// Callback names LuaTeX knows, as `callback.list()` returns them in
/// LuaHBTeX 1.22.
pub const KNOWN: &[&str] = &[
    "append_to_vlist_filter",
    "build_page_insert",
    "buildpage_filter",
    "call_edit",
    "contribute_filter",
    "define_font",
    "find_cidmap_file",
    "find_data_file",
    "find_enc_file",
    "find_font_file",
    "find_format_file",
    "find_image_file",
    "find_map_file",
    "find_opentype_file",
    "find_output_file",
    "find_pk_file",
    "find_read_file",
    "find_truetype_file",
    "find_type1_file",
    "find_vf_file",
    "find_write_file",
    "finish_pdffile",
    "finish_pdfpage",
    "finish_synctex",
    "font_descriptor_objnum_provider",
    "glyph_info",
    "glyph_not_found",
    "glyph_stream_provider",
    "hpack_filter",
    "hpack_quality",
    "hyphenate",
    "input_level_string",
    "insert_local_par",
    "kerning",
    "ligaturing",
    "linebreak_filter",
    "make_extensible",
    "mlist_to_hlist",
    "new_graf",
    "open_read_file",
    "page_order_index",
    "post_linebreak_filter",
    "pre_dump",
    "pre_linebreak_filter",
    "pre_output_filter",
    "process_input_buffer",
    "process_jobname",
    "process_output_buffer",
    "process_pdf_image_content",
    "process_rule",
    "provide_charproc_data",
    "read_cidmap_file",
    "read_data_file",
    "read_enc_file",
    "read_font_file",
    "read_map_file",
    "read_opentype_file",
    "read_pk_file",
    "read_truetype_file",
    "read_type1_file",
    "read_vf_file",
    "show_error_hook",
    "show_error_message",
    "show_ignored_error_message",
    "show_lua_error_hook",
    "show_warning_message",
    "start_file",
    "start_page_number",
    "start_run",
    "stop_file",
    "stop_page_number",
    "stop_run",
    "vpack_filter",
    "vpack_quality",
    "wrapup_run",
];

/// Installs `callback`.
pub fn install(lua: &Lua) -> mlua::Result<()> {
    let registry = lua.create_table()?;
    lua.set_named_registry_value("retex.callbacks", registry)?;
    lua.set_app_data(Hot::default());
    let t = lua.create_table()?;
    t.set(
        "register",
        lua.create_function(|lua, (name, f): (String, Value)| {
            if !KNOWN.contains(&name.as_str()) {
                return Ok((
                    Value::Nil,
                    Value::String(lua.create_string("No such callback exists.")?),
                ));
            }
            let reg: Table = lua.named_registry_value("retex.callbacks")?;
            if let Some(i) = HOT.iter().position(|n| *n == name)
                && let Some(mut hot) = lua.app_data_mut::<Hot>()
            {
                let bit = 1u32 << i;
                hot.function &= !bit;
                hot.disabled &= !bit;
                match f {
                    Value::Function(_) => hot.function |= bit,
                    Value::Boolean(false) => hot.disabled |= bit,
                    _ => {}
                }
            }
            match f {
                Value::Function(_) | Value::Boolean(false) => reg.set(name.as_str(), f)?,
                _ => reg.set(name.as_str(), Value::Nil)?,
            }
            let id = KNOWN.iter().position(|n| *n == name).unwrap_or(0) as i64;
            Ok((Value::Integer(id), Value::Nil))
        })?,
    )?;
    t.set(
        "find",
        lua.create_function(|lua, name: String| {
            let reg: Table = lua.named_registry_value("retex.callbacks")?;
            reg.get::<Value>(name)
        })?,
    )?;
    t.set(
        "list",
        lua.create_function(|lua, ()| {
            let reg: Table = lua.named_registry_value("retex.callbacks")?;
            let out = lua.create_table()?;
            for name in KNOWN {
                let set = matches!(reg.get::<Value>(*name)?, Value::Function(_));
                out.set(*name, set)?;
            }
            Ok(out)
        })?,
    )?;
    t.set(
        "known",
        lua.create_function(|lua, ()| {
            let out = lua.create_table()?;
            for name in KNOWN {
                out.set(*name, true)?;
            }
            Ok(out)
        })?,
    )?;
    lua.globals().set("callback", t)?;
    Ok(())
}

/// Callbacks the engine asks about for every list or paragraph: whether
/// they are set is kept in bit masks, which is cheaper than the registry.
const HOT: &[&str] = &[
    "hyphenate",
    "ligaturing",
    "kerning",
    "buildpage_filter",
    "contribute_filter",
    "hpack_quality",
    "vpack_quality",
    "linebreak_filter",
];

/// Which [`HOT`] callbacks hold a function, and which `false`.
#[derive(Default)]
struct Hot {
    function: u32,
    disabled: u32,
}

/// For a callback of [`HOT`]: whether a function is registered, and
/// whether `false` is.
pub fn hot(lua: &Lua, name: &str) -> Option<(bool, bool)> {
    let i = HOT.iter().position(|n| *n == name)?;
    let hot = lua.app_data_ref::<Hot>()?;
    Some((hot.function >> i & 1 == 1, hot.disabled >> i & 1 == 1))
}

/// The function registered for callback `name`, if any.
pub fn get(lua: &Lua, name: &str) -> Option<Function> {
    let reg: Table = lua.named_registry_value("retex.callbacks").ok()?;
    reg.get::<Function>(name).ok()
}