use mlua::{Lua, Table, Value};
use crate::with_engine;
fn font_table(lua: &Lua, f: u32) -> mlua::Result<Option<Table>> {
struct Snap {
name: Vec<u8>,
size: i32,
dsize: i32,
params: Vec<i32>,
hyphen: i32,
skew: i32,
file: Option<(String, String)>,
math: Option<Vec<(&'static str, retex_font::texfont::MathValue)>>,
expansion: Option<retex_engine::expansion::FontExpansion>,
mode: Option<&'static str>,
}
let snap = with_engine(lua, |e| {
if !e.font_exists(f as i32) {
return None;
}
let fi = e.font(f);
let file = crate::luaotfload::loaded_font(e, f).map(|lf| {
(
lf.tex.path.to_string_lossy().into_owned(),
lf.tex.psname.clone(),
)
});
let math = crate::luaotfload::loaded_font(e, f).and_then(|lf| lf.tex.math.clone());
let mode = crate::luaotfload::loaded_font(e, f).map(|lf| match lf.request.mode {
retex_font::Mode::Harf => "plug",
retex_font::Mode::Base => "base",
retex_font::Mode::Node => "node",
});
Some(Snap {
name: fi.name.clone(),
size: fi.size,
dsize: fi.design_size,
params: fi.params.clone(),
hyphen: fi.hyphen_char,
skew: fi.skew_char,
file,
math,
expansion: fi.expansion,
mode,
})
})?;
let Some(s) = snap else {
return Ok(None);
};
let t = lua.create_table()?;
t.set("name", lua.create_string(&s.name)?)?;
t.set("size", s.size)?;
t.set("designsize", s.dsize)?;
t.set("hyphenchar", s.hyphen)?;
t.set("skewchar", s.skew)?;
if let Some(x) = s.expansion {
t.set("stretch", x.stretch)?;
t.set("shrink", x.shrink)?;
t.set("step", x.step)?;
}
let p = lua.create_table()?;
let names = [
"slant",
"space",
"space_stretch",
"space_shrink",
"x_height",
"quad",
"extra_space",
];
for (i, v) in s.params.iter().enumerate().skip(1) {
if let Some(n) = names.get(i - 1) {
p.set(*n, *v)?;
} else {
p.set(i, *v)?;
}
}
t.set("parameters", p)?;
if let Some(mode) = s.mode {
let props = lua.create_table()?;
props.set("mode", mode)?;
t.set("properties", props)?;
}
let meta = lua.create_table()?;
meta.set(
"__index",
lua.create_function(move |lua, (t, k): (Table, Value)| {
let Value::String(key) = &k else {
return Ok(Value::Nil);
};
let built = match key.as_bytes().as_ref() {
b"characters" => characters_table(lua, f)?,
b"resources" => match resources_table(lua, f)? {
Some(r) => r,
None => return Ok(Value::Nil),
},
_ => return Ok(Value::Nil),
};
t.raw_set(k, built.clone())?;
Ok(Value::Table(built))
})?,
)?;
t.set_metatable(Some(meta))?;
if let Some(m) = s.math {
let mc = lua.create_table()?;
for (k, v) in m {
match v {
retex_font::texfont::MathValue::Percent(p) => mc.set(k, p)?,
retex_font::texfont::MathValue::Scaled(x) => mc.set(k, x)?,
}
}
t.set("MathConstants", mc)?;
}
if let Some((file, ps)) = s.file {
t.set("filename", file)?;
t.set("psname", ps.clone())?;
t.set("fullname", ps)?;
t.set("format", "opentype")?;
}
Ok(Some(t))
}
fn resources_table(lua: &Lua, f: u32) -> mlua::Result<Option<Table>> {
let names = with_engine(lua, |e| {
let lf = crate::luaotfload::loaded_font(e, f)?;
let names: Vec<(String, u32)> = (0..lf.file.num_glyphs)
.filter_map(|g| Some((lf.file.glyph_name(g)?.to_owned(), lf.tex.code_of_glyph(g)?)))
.collect();
Some(names)
})?;
let Some(names) = names else {
return Ok(None);
};
let unicodes = lua.create_table()?;
for (name, code) in names {
unicodes.set(name, code)?;
}
let r = lua.create_table()?;
r.set("unicodes", unicodes)?;
Ok(Some(r))
}
fn characters_table(lua: &Lua, f: u32) -> mlua::Result<Table> {
let s = with_engine(lua, |e| {
let fi = e.font(f);
CharsSnap {
chars: fi.chars.iter().map(|(c, m)| (*c, *m)).collect(),
lig_kern: fi.lig_kern.clone(),
math_chars: fi.math_chars.clone(),
}
})?;
let chars = lua.create_table()?;
for (c, m) in s.chars {
let ct = lua.create_table()?;
ct.set("width", m.width)?;
ct.set("height", m.height)?;
ct.set("depth", m.depth)?;
ct.set("italic", m.italic)?;
ct.set("index", m.index)?;
if let Some(prog) = s.lig_kern.get(&c) {
let kerns = lua.create_table()?;
let ligs = lua.create_table()?;
for st in prog.iter() {
match st.op {
retex_engine::fonts::LigOp::Kern(k) => kerns.set(st.next, k)?,
retex_engine::fonts::LigOp::Lig { op, chr } => {
let l = lua.create_table()?;
l.set("char", chr)?;
l.set("type", op)?;
ligs.set(st.next, l)?;
}
}
}
if kerns.pairs::<Value, Value>().next().is_some() {
ct.set("kerns", kerns)?;
}
if ligs.pairs::<Value, Value>().next().is_some() {
ct.set("ligatures", ligs)?;
}
}
if let Some(mi) = s.math_chars.as_ref().and_then(|m| m.get(&c)) {
if let Some(n) = mi.next {
ct.set("next", n)?;
}
if let Some(t) = mi.top_accent {
ct.set("top_accent", t)?;
}
for (key, parts) in [("vert_variants", &mi.vert), ("horiz_variants", &mi.horiz)] {
if let Some(ps) = parts {
let pt = lua.create_table()?;
for (i, p) in ps.iter().enumerate() {
let e = lua.create_table()?;
e.set("glyph", p.code)?;
e.set("start", p.start)?;
e.set("end", p.end)?;
e.set("advance", p.advance)?;
if p.extender {
e.set("extender", 1)?;
}
pt.set(i + 1, e)?;
}
ct.set(key, pt)?;
}
}
}
chars.set(c, ct)?;
}
Ok(chars)
}
struct CharsSnap {
chars: Vec<(u32, retex_engine::fonts::CharMetrics)>,
lig_kern: retex_engine::fonts::LigKernMap,
math_chars:
Option<std::rc::Rc<std::collections::HashMap<u32, retex_engine::fonts::MathCharInfo>>>,
}
fn read_vf(lua: &Lua, name: &str, size: i32) -> mlua::Result<Value> {
use retex_font::vf::Command;
let data = with_engine(lua, |e| {
let path = e
.kpse
.as_ref()?
.borrow_mut()
.find_file(name, retex_kpath::FileFormat::Vf)?;
e.read_file_recorded(&path)
})?;
let Some(vf) = data.and_then(|d| retex_font::vf::Vf::parse(&d).ok()) else {
return Ok(Value::Nil);
};
let t = lua.create_table()?;
t.set("name", name)?;
t.set("checksum", vf.checksum as i32)?;
t.set("designsize", vf.design_size_sp())?;
t.set("size", size)?;
t.set("header", lua.create_string(&vf.comment)?)?;
let fonts = lua.create_table()?;
for (i, f) in vf.fonts.iter().enumerate() {
let ft = lua.create_table()?;
ft.set("name", f.name.as_str())?;
ft.set("size", vf.font_size(i, size))?;
fonts.set(i + 1, ft)?;
}
t.set("fonts", fonts)?;
let chars = lua.create_table()?;
for (&c, p) in &vf.packets {
let ct = lua.create_table()?;
ct.set("width", p.width)?;
let cmds = lua.create_table()?;
for (k, cmd) in vf.commands(c, size).unwrap_or_default().iter().enumerate() {
let e = lua.create_table()?;
match cmd {
Command::Font(i) => {
e.push("font")?;
e.push(i + 1)?;
}
Command::Char(ch) => {
e.push("char")?;
e.push(*ch)?;
}
Command::Rule { height, width } => {
e.push("rule")?;
e.push(*height)?;
e.push(*width)?;
}
Command::Right(x) => {
e.push("right")?;
e.push(*x)?;
}
Command::Down(y) => {
e.push("down")?;
e.push(*y)?;
}
Command::Push => e.push("push")?,
Command::Pop => e.push("pop")?,
Command::Special(s) => {
e.push("special")?;
e.push(lua.create_string(s)?)?;
}
}
cmds.set(k + 1, e)?;
}
ct.set("commands", cmds)?;
chars.set(c, ct)?;
}
t.set("characters", chars)?;
Ok(Value::Table(t))
}
pub fn install(lua: &Lua) -> mlua::Result<()> {
let t = lua.create_table()?;
t.set(
"getfont",
lua.create_function(|lua, f: i64| font_table(lua, f as u32))?,
)?;
t.set(
"getcopy",
lua.create_function(|lua, f: i64| font_table(lua, f as u32))?,
)?;
t.set(
"current",
lua.create_function(|lua, f: Option<i64>| {
let ok = with_engine(lua, |e| match f {
Some(f) if f >= 0 => match usize::try_from(f) {
Ok(i) if i < e.fonts.fonts.len() => {
e.eqtb.cur_font.val = i as u32;
true
}
_ => false,
},
_ => true,
})?;
if !ok {
return Err(mlua::Error::runtime("expected a valid font id"));
}
with_engine(lua, |e| i64::from(e.eqtb.cur_font.val))
})?,
)?;
t.set(
"nextid",
lua.create_function(|lua, _reserve: Option<bool>| {
with_engine(lua, |e| e.fonts.fonts.len() as i64)
})?,
)?;
t.set(
"max",
lua.create_function(|lua, ()| with_engine(lua, |e| e.fonts.fonts.len() as i64 - 1))?,
)?;
t.set(
"id",
lua.create_function(|lua, name: mlua::String| {
let n = name.as_bytes().to_vec();
with_engine(lua, |e| {
let cs = e.hash.lookup(&n)?;
let m = e.eqtb.meaning(cs);
(m.cmd == retex_engine::cmd::Cmd::SetFont).then_some(i64::from(m.chr))
})
.map(|o| o.unwrap_or(-1))
})?,
)?;
t.set(
"define",
lua.create_function(|lua, args: mlua::MultiValue| {
let tbl = args.into_iter().find_map(|v| match v {
Value::Table(t) => Some(t),
_ => None,
});
let Some(tbl) = tbl else {
return Err(mlua::Error::runtime("font.define: table expected"));
};
let info = crate::fonttable::font_from_table(&tbl)?;
let f = with_engine(lua, |e| {
e.nodes.lua_refs.font_defined(false);
e.register_font(info)
})?;
crate::fonttable::attach_commands(lua, &tbl, f)?;
Ok(i64::from(f))
})?,
)?;
t.set(
"read_vf",
lua.create_function(|lua, (name, size): (mlua::String, i64)| {
read_vf(lua, &name.to_str()?, size as i32)
})?,
)?;
t.set("frozen", lua.create_function(|_, _f: i64| Ok(false))?)?;
t.set(
"each",
lua.create_function(|lua, ()| {
let n = with_engine(lua, |e| e.fonts.fonts.len() as i64)?;
let state = lua.create_table()?;
state.set("i", 0)?;
state.set("n", n)?;
let iter = lua.create_function(|lua, st: Table| {
let i: i64 = st.get("i")?;
let n: i64 = st.get("n")?;
let i = i + 1;
st.set("i", i)?;
if i >= n {
return Ok((Value::Nil, Value::Nil));
}
let t = font_table(lua, i as u32)?;
Ok((Value::Integer(i), t.map_or(Value::Nil, Value::Table)))
})?;
Ok((iter, state))
})?,
)?;
let fonts = lua.create_table()?;
let meta = lua.create_table()?;
meta.set(
"__index",
lua.create_function(|lua, (_t, k): (Table, Value)| match k {
Value::Integer(i) if i >= 0 => {
Ok(font_table(lua, i as u32)?.map_or(Value::Nil, Value::Table))
}
_ => Ok(Value::Nil),
})?,
)?;
fonts.set_metatable(Some(meta))?;
t.set("fonts", fonts)?;
lua.globals().set("font", t)?;
Ok(())
}