use std::collections::HashMap;
use std::rc::Rc;
use mlua::{Lua, Table, Value};
use retex_engine::fonts::{
CharMetrics, FontInfo, FontKind, LigKernStep, LigOp, PacketCommand, VirtualFont,
};
pub fn font_from_table(t: &Table) -> mlua::Result<FontInfo> {
let mut info = FontInfo::null();
info.name = t
.get::<Option<mlua::String>>("name")?
.map(|s| s.as_bytes().to_vec())
.unwrap_or_default();
info.size = t.get::<Option<i32>>("size")?.unwrap_or(655360);
info.design_size = t.get::<Option<i32>>("designsize")?.unwrap_or(655360);
info.checksum = t.get::<Option<i64>>("checksum")?.unwrap_or(0) as u32;
let mut params = vec![0; 8];
if let (Some(st), Some(sh), Some(sp)) = (
t.get::<Option<i32>>("stretch")?,
t.get::<Option<i32>>("shrink")?,
t.get::<Option<i32>>("step")?,
) && (st > 0 || sh > 0)
&& sp > 0
{
info.expansion = Some(retex_engine::expansion::FontExpansion {
stretch: st,
shrink: sh,
step: sp,
});
}
if let Some(p) = t.get::<Option<Table>>("parameters")? {
let names = [
"slant",
"space",
"space_stretch",
"space_shrink",
"x_height",
"quad",
"extra_space",
];
for (i, n) in names.iter().enumerate() {
if let Some(v) = p.get::<Option<i32>>(*n)? {
params[i + 1] = v;
} else if let Some(v) = p.get::<Option<i32>>(i as i64 + 1)? {
params[i + 1] = v;
}
}
}
info.params = params;
let mut chars: HashMap<_, _, retex_engine::hash::FastHash> = HashMap::default();
if let Some(cs) = t.get::<Option<Table>>("characters")? {
for pair in cs.pairs::<i64, Table>() {
let (c, ct) = pair?;
chars.insert(
c as u32,
CharMetrics {
width: ct.get::<Option<i32>>("width")?.unwrap_or(0),
height: ct.get::<Option<i32>>("height")?.unwrap_or(0),
depth: ct.get::<Option<i32>>("depth")?.unwrap_or(0),
italic: ct.get::<Option<i32>>("italic")?.unwrap_or(0),
index: ct.get::<Option<u32>>("index")?.unwrap_or(c as u32),
},
);
let mut steps = Vec::new();
if let Some(l) = ct.get::<Option<Table>>("ligatures")? {
for pair in l.pairs::<i64, Table>() {
let (next, lt) = pair?;
let chr = lt.get::<Option<u32>>("char")?.unwrap_or(0);
let op = lt.get::<Option<u8>>("type")?.unwrap_or(0);
steps.push(LigKernStep {
next: next as u32,
op: LigOp::Lig { op, chr },
});
}
}
if let Some(k) = ct.get::<Option<Table>>("kerns")? {
for pair in k.pairs::<i64, i32>() {
let (next, amount) = pair?;
steps.push(LigKernStep {
next: next as u32,
op: LigOp::Kern(amount),
});
}
}
if !steps.is_empty() {
steps.sort_by_key(|st| st.next);
info.lig_kern.insert(c as u32, steps.into_boxed_slice());
}
}
}
info.chars = chars;
if let Some(mc) = t.get::<Option<Table>>("MathConstants")? {
let mut m = retex_engine::fonts::MathConstants::new();
for pair in mc.pairs::<String, f64>() {
let (k, v) = pair?;
m.insert(k, v.round() as i32);
}
info.math_constants = Some(std::rc::Rc::new(m));
}
info.hyphen_char = t.get::<Option<i32>>("hyphenchar")?.unwrap_or(i32::MIN);
info.skew_char = t.get::<Option<i32>>("skewchar")?.unwrap_or(i32::MIN);
info.kind = FontKind::OpenType;
Ok(info)
}
pub fn attach_commands(lua: &Lua, t: &Table, f: u32) -> mlua::Result<()> {
let Some(cs) = t.get::<Option<Table>>("characters")? else {
return Ok(());
};
let mut packets: HashMap<u32, Box<[PacketCommand]>, retex_engine::hash::FastHash> =
HashMap::default();
for pair in cs.pairs::<i64, Table>() {
let (c, ct) = pair?;
if let Some(cmds) = ct.get::<Option<Table>>("commands")? {
packets.insert(c as u32, packet(lua, &cmds)?.into_boxed_slice());
}
}
if packets.is_empty() {
return Ok(());
}
let mut fonts = Vec::new();
match t.get::<Option<Table>>("fonts")? {
Some(ft) => {
for entry in ft.sequence_values::<Value>() {
let Value::Table(entry) = entry? else {
fonts.push(0);
continue;
};
fonts.push(local_font(lua, &entry, f)?);
}
}
None => fonts.push(f),
}
crate::with_engine(lua, |e| {
e.fonts.fonts[f as usize].vf = Some(Rc::new(VirtualFont { fonts, packets }));
})
}
fn local_font(lua: &Lua, entry: &Table, f: u32) -> mlua::Result<u32> {
if let Some(id) = entry.get::<Option<i64>>("id")? {
return crate::with_engine(lua, |e| match id {
0 => f,
_ if e.font_exists(id as i32) => id as u32,
_ => 0,
});
}
let Some(name) = entry.get::<Option<mlua::String>>("name")? else {
return Ok(0);
};
let name = name.as_bytes().to_vec();
let size = entry.get::<Option<i32>>("size")?.unwrap_or(655360);
let existing = crate::with_engine(lua, |e| {
(1..e.fonts.fonts.len()).find(|&g| {
let fi = &e.fonts.fonts[g];
fi.size == size && fi.name == name
})
})?;
if let Some(g) = existing {
return Ok(g as u32);
}
crate::with_engine(lua, |e| e.load_font(&name, size).unwrap_or(0))
}
fn packet(lua: &Lua, cmds: &Table) -> mlua::Result<Vec<PacketCommand>> {
let num = |v: Option<&Value>| -> i64 {
match v {
Some(Value::Integer(n)) => *n,
Some(Value::Number(x)) => *x as i64,
Some(v @ Value::String(_)) => lua.coerce_integer(v.clone()).ok().flatten().unwrap_or(0),
_ => 0,
}
};
let bytes = |v: Option<&Value>| -> Option<Vec<u8>> {
match v {
Some(Value::String(s)) => Some(s.as_bytes().to_vec()),
Some(v @ (Value::Integer(_) | Value::Number(_))) => lua
.coerce_string(v.clone())
.ok()
.flatten()
.map(|s| s.as_bytes().to_vec()),
_ => None,
}
};
let mut out = Vec::new();
for cmd in cmds.sequence_values::<Value>() {
let Value::Table(cmd) = cmd? else {
continue;
};
let items: Vec<Value> = cmd
.sequence_values::<Value>()
.collect::<mlua::Result<_>>()?;
let Some(Value::String(name)) = items.first() else {
continue;
};
let arg = |i: usize| items.get(i);
match name.as_bytes().as_ref() {
b"font" => out.push(PacketCommand::Font(num(arg(1)).wrapping_sub(1) as usize)),
b"char" => out.push(PacketCommand::Char(num(arg(1)) as u32)),
b"slot" => {
out.push(PacketCommand::Font(num(arg(1)).wrapping_sub(1) as usize));
out.push(PacketCommand::Char(num(arg(2)) as u32));
}
b"right" => out.push(PacketCommand::Right(num(arg(1)) as i32)),
b"down" => out.push(PacketCommand::Down(num(arg(1)) as i32)),
b"push" => out.push(PacketCommand::Push),
b"pop" => out.push(PacketCommand::Pop),
b"rule" => out.push(PacketCommand::Rule {
height: num(arg(1)) as i32,
width: num(arg(2)) as i32,
}),
b"special" => {
if let Some(s) = bytes(arg(1)) {
out.push(PacketCommand::Special(s));
}
}
b"pdf" => {
let (mode, text) = match (bytes(arg(1)), bytes(arg(2))) {
(Some(m), Some(text)) => {
let mode = crate::pdflib::MODES
.iter()
.chain(&[("font", 5)])
.find(|(n, _)| n.as_bytes() == m.as_slice())
.map(|&(_, m)| m);
match mode {
Some(mode) => (mode, text),
None => continue,
}
}
(Some(text), None) => (retex_engine::pdfext::literal_mode::ORIGIN, text),
_ => continue,
};
out.push(PacketCommand::Pdf(mode, text));
}
b"lua" => {
if let Some(v @ (Value::String(_) | Value::Function(_))) = arg(1) {
out.push(PacketCommand::Lua(crate::nodelib::lua_ref(lua, v.clone())?));
}
}
b"node" => {
if let Some(p) = arg(1).and_then(crate::nodelib::to_ref) {
out.push(PacketCommand::Node(p));
}
}
_ => {}
}
}
Ok(out)
}