use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use mlua::{Lua, Value};
use retex_engine::Engine;
use retex_engine::fonts::{CharMetrics, FontData, FontInfo, FontKind};
use retex_engine::nodes::NodeRef;
use retex_font::LoadedFont;
use crate::with_engine;
#[derive(Default)]
pub struct FontState {
loader: Option<retex_font::FontLoader>,
ids: HashMap<usize, u32>,
}
const MAIN_LUA: &str = r#"
local native = ...
local lb = luatexbase
local function noop(...) return ... end
local function log(cat, msg)
texio.write_nl("log", "luaotfload | " .. cat .. " : " .. msg)
end
luaotfload = luaotfload or {}
luaotfload.define_font = native.define_font
luaotfload.node_processor = native.node_processor
luaotfload.version = luaotfload.version or "3.29"
-- Letterspacing options (microtype replaces keepligature).
luaotfload.letterspace = luaotfload.letterspace or
{ keepligature = true, keeptogether = false, keepwordspacing = false }
luaotfload.features = luaotfload.features or { defaults = {}, global = {} }
local aux = luaotfload.aux or {}
luaotfload.aux = aux
for _, k in ipairs{"provides_script", "provides_language", "provides_feature",
"get_math_dimension", "font_has_glyph", "slot_of_name", "gid_of_name"} do
aux[k] = native["aux_" .. k]
end
aux.sprint_math_dimension = function(f, name)
tex.sprint(-2, tostring(aux.get_math_dimension(f, name)))
end
-- The parts of the fontloader's `fonts` table that packages touch.
fonts = fonts or {}
fonts.names = fonts.names or {}
fonts.names.set_location_precedence = fonts.names.set_location_precedence or function() end
fonts.hashes = fonts.hashes or {}
fonts.hashes.identifiers = fonts.hashes.identifiers or setmetatable({}, {
__index = function(_, id) if type(id) == "number" then return font.getfont(id) end end })
luaotfload.main = function()
lb.provides_module({ name = "luaotfload", date = "2024-12-03", version = "3.29",
description = "Lua based OpenType font support" })
lb.provides_module({ name = "lualibs", date = "2023-07-13", version = "2.76",
description = "ConTeXt Lua standard libraries." })
lb.provides_module({ name = "lualibs-extended", date = "2023-07-13", version = "2.76",
description = "ConTeXt Lua libraries -- extended collection." })
local cache = native.cache_dir()
log("conf", 'Root cache directory is "' .. cache .. '".')
local loader = kpse.find_file("fontloader-2023-12-28.lua", "lua") or "fontloader-2023-12-28.lua"
log("init", 'Loading fontloader "fontloader-2023-12-28.lua" from kpse-resolved path "' .. loader .. '".')
lb.new_attribute("luaotfload@noligature")
log("init", "Context OpenType loader version 3.134")
lb.add_to_callback("pre_linebreak_filter", native.node_processor, "luaotfload.node_processor")
lb.add_to_callback("hpack_filter", native.node_processor, "luaotfload.node_processor")
lb.add_to_callback("glyph_stream_provider", noop, "luaotfload.glyph_stream")
lb.add_to_callback("define_font", native.define_font, "luaotfload.define_font")
lb.new_attribute("luaotfload_color_attribute")
log("conf", 'Root cache directory is "' .. cache .. '".')
lb.add_to_callback("find_opentype_file", noop, "luaotfload.harf.strip_prefix")
lb.add_to_callback("find_truetype_file", noop, "luaotfload.harf.strip_prefix")
lb.remove_from_callback("glyph_stream_provider", "luaotfload.glyph_stream")
lb.add_to_callback("glyph_stream_provider", noop, "luaotfload.harf.glyphstream")
lb.add_to_callback("post_linebreak_filter", noop, "luaotfload.harf.finalize_vlist")
lb.add_to_callback("hpack_filter", noop, "luaotfload.harf.finalize_hlist")
lb.add_to_callback("wrapup_run", noop, "luaotfload.cleanup_files")
lb.add_to_callback("finish_pdffile", noop, "luaotfload.harf.finalize_unicode")
-- Box displays show the character as UTF-8 (observed: "^^@" for 0
-- and ^^^^^^ notation beyond Unicode).
lb.add_to_callback("glyph_info", function(n)
local c = n.char
if c == 0 then return "^^@" end
if c > 0x10FFFF then return string.format("^^^^^^%06x", c) end
return utf8.char(c)
end, "luaotfload.glyphinfo")
lb.new_attribute("luaotfload.letterspace_done")
lb.create_callback("luaotfload.patch_font", "simple", noop)
lb.create_callback("luaotfload.patch_font_unsafe", "simple", noop)
lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_sscale_dimens")
lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_font_index")
lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.patch_cambria_domh")
lb.add_to_callback("luaotfload.patch_font_unsafe", noop, "luaotfload.aux.fixup_fontdata")
lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_capheight")
lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_xheight")
lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.rewrite_fontname")
lb.create_callback("luaotfload.parse_color", "exclusive", noop)
return true
end
return luaotfload
"#;
pub fn install(lua: &Lua) -> mlua::Result<()> {
lua.set_app_data(Rc::new(RefCell::new(FontState::default())));
let native = lua.create_table()?;
native.set(
"define_font",
lua.create_function(|lua, (name, size, _id): (mlua::String, i64, Option<i64>)| {
let name = name.to_str()?.to_owned();
let t0 = std::time::Instant::now();
let r = define_font(lua, &name, size as i32);
retex_engine::timing::FONTS.with(|a| a.add(t0));
r
})?,
)?;
native.set(
"node_processor",
lua.create_function(|lua, (head, _group): (Value, Value)| {
let h = match &head {
Value::UserData(u) => u
.borrow::<crate::nodelib::LNode>()
.ok()
.map(|n| NodeRef(n.0)),
_ => None,
};
if let Some(h) = h {
let nh = with_engine(lua, |e| process_list(e, h))?;
if nh != h {
return Ok(Value::UserData(
lua.create_userdata(crate::nodelib::LNode(nh.0))?,
));
}
}
Ok(Value::Boolean(true))
})?,
)?;
native.set(
"cache_dir",
lua.create_function(|lua, ()| {
with_engine(lua, |e| {
e.kpse
.as_ref()
.and_then(|k| k.borrow().var_value("TEXMFVAR"))
.map(|v| format!("{v}/luatex-cache/generic/names"))
.unwrap_or_default()
})
})?,
)?;
crate::otfaux::install(lua, &native)?;
let loader = lua.create_function(move |lua, ()| {
let f = lua
.load(MAIN_LUA)
.set_name("=luaotfload-main")
.into_function()?;
let native: mlua::Table = lua.named_registry_value("retex.luaotfload.native")?;
f.call::<Value>(native)
})?;
lua.set_named_registry_value("retex.luaotfload.native", native)?;
let package: mlua::Table = lua.globals().get("package")?;
let preload: mlua::Table = package.get("preload")?;
preload.set("luaotfload-main", loader)?;
Ok(())
}
fn define_font(lua: &Lua, name: &str, size: i32) -> mlua::Result<Value> {
let state = lua
.app_data_ref::<Rc<RefCell<FontState>>>()
.map(|s| s.clone())
.ok_or_else(|| mlua::Error::runtime("font state missing"))?;
if let Ok(req) = retex_font::FontRequest::parse(name)
&& let retex_font::FontSource::Any(base) = &req.source
{
let id = with_engine(lua, |e| {
e.load_tfm_font(base.as_bytes(), size, name.as_bytes())
})?;
if let Some(i) = id {
return Ok(Value::Integer(i64::from(i)));
}
}
let loaded: Result<Arc<LoadedFont>, String> = with_engine(lua, |e| {
let mut st = state.borrow_mut();
if st.loader.is_none() {
let k = e
.kpse
.clone()
.ok_or_else(|| "kpathsea unavailable".to_owned())?;
st.loader = Some(retex_font::FontLoader::new(k));
}
st.loader
.as_mut()
.expect("loader initialized")
.load(name, size)
.map_err(|e| e.to_string())
})?;
let Ok(font) = loaded else {
let plain = name.strip_prefix("file:").unwrap_or(name);
let plain = plain.split(':').next().unwrap_or(plain).to_owned();
let id = with_engine(lua, |e| {
e.load_tfm_font(plain.as_bytes(), size, name.as_bytes())
})?;
return Ok(id.map_or(Value::Nil, |i| Value::Integer(i64::from(i))));
};
let key = Arc::as_ptr(&font) as usize;
if let Some(&id) = state.borrow().ids.get(&key) {
return Ok(Value::Integer(i64::from(id)));
}
let id = with_engine(lua, |e| {
let tex = &font.tex;
let mut params = vec![0; tex.params.len() + 1];
for (i, p) in tex.params.iter().enumerate() {
params[i + 1] = *p;
}
let mut chars = HashMap::with_capacity_and_hasher(
tex.num_chars(),
retex_engine::hash::FastHash::default(),
);
for (code, m) in tex.chars() {
chars.insert(
code,
CharMetrics {
width: m.width,
height: m.height,
depth: m.depth,
italic: m.italic,
index: u32::from(m.glyph),
},
);
}
let info = FontInfo {
name: tex.name.clone().into_bytes(),
size: tex.size,
design_size: tex.design_size,
checksum: 0,
params,
chars,
hyphen_char: i32::MIN,
skew_char: i32::MIN,
kind: FontKind::OpenType,
handle: 0,
char_codes: Default::default(),
ignore_ligatures: 0,
data: Some(FontData(Rc::new(font.clone()))),
lig_kern: Default::default(),
right_boundary: None,
expansion: None,
size_spec: i32::MIN,
vf: None,
math_chars: {
let mc = tex.math_chars(&font.file);
(!mc.is_empty()).then(|| Rc::new(convert_math_chars(mc)))
},
math_constants: tex.math.as_ref().map(|m| {
Rc::new(
m.iter()
.map(|(k, v)| ((*k).to_owned(), v.to_sp()))
.collect(),
)
}),
};
e.register_font(info)
})?;
state.borrow_mut().ids.insert(key, id);
Ok(Value::Integer(i64::from(id)))
}
fn convert_math_chars(
mc: HashMap<u32, retex_font::texfont::MathChar>,
) -> HashMap<u32, retex_engine::fonts::MathCharInfo> {
use retex_engine::fonts::{ExtPart, MathCharInfo};
let parts = |v: &Option<Vec<retex_font::texfont::MathPart>>| {
v.as_ref().map(|ps| {
ps.iter()
.map(|p| ExtPart {
code: p.code,
start: retex_font::texfont::round_sp(p.start),
end: retex_font::texfont::round_sp(p.end),
advance: retex_font::texfont::round_sp(p.advance),
extender: p.extender,
})
.collect::<Rc<[ExtPart]>>()
})
};
mc.into_iter()
.map(|(c, m)| {
(
c,
MathCharInfo {
next: m.next,
top_accent: m.top_accent.map(retex_font::texfont::round_sp),
vert: parts(&m.vert),
horiz: parts(&m.horiz),
},
)
})
.collect()
}
pub fn loaded_font(e: &Engine, f: u32) -> Option<Arc<LoadedFont>> {
let data = e.font(f).data.as_ref()?;
data.0.downcast_ref::<Arc<LoadedFont>>().cloned()
}
pub use crate::otfnodes::process_list;