1use std::cell::RefCell;
18use std::collections::HashMap;
19use std::rc::Rc;
20use std::sync::Arc;
21
22use mlua::{Lua, Value};
23use retex_engine::Engine;
24use retex_engine::fonts::{CharMetrics, FontData, FontInfo, FontKind};
25use retex_engine::nodes::NodeRef;
26use retex_font::LoadedFont;
27
28use crate::with_engine;
29
30#[derive(Default)]
32pub struct FontState {
33 loader: Option<retex_font::FontLoader>,
34 ids: HashMap<usize, u32>,
39}
40
41const MAIN_LUA: &str = r#"
44local native = ...
45local lb = luatexbase
46local function noop(...) return ... end
47local function log(cat, msg)
48 texio.write_nl("log", "luaotfload | " .. cat .. " : " .. msg)
49end
50luaotfload = luaotfload or {}
51luaotfload.define_font = native.define_font
52luaotfload.node_processor = native.node_processor
53luaotfload.version = luaotfload.version or "3.29"
54-- Letterspacing options (microtype replaces keepligature).
55luaotfload.letterspace = luaotfload.letterspace or
56 { keepligature = true, keeptogether = false, keepwordspacing = false }
57luaotfload.features = luaotfload.features or { defaults = {}, global = {} }
58local aux = luaotfload.aux or {}
59luaotfload.aux = aux
60for _, k in ipairs{"provides_script", "provides_language", "provides_feature",
61 "get_math_dimension", "font_has_glyph", "slot_of_name", "gid_of_name"} do
62 aux[k] = native["aux_" .. k]
63end
64aux.sprint_math_dimension = function(f, name)
65 tex.sprint(-2, tostring(aux.get_math_dimension(f, name)))
66end
67-- The parts of the fontloader's `fonts` table that packages touch.
68fonts = fonts or {}
69fonts.names = fonts.names or {}
70fonts.names.set_location_precedence = fonts.names.set_location_precedence or function() end
71fonts.hashes = fonts.hashes or {}
72fonts.hashes.identifiers = fonts.hashes.identifiers or setmetatable({}, {
73 __index = function(_, id) if type(id) == "number" then return font.getfont(id) end end })
74luaotfload.main = function()
75 lb.provides_module({ name = "luaotfload", date = "2024-12-03", version = "3.29",
76 description = "Lua based OpenType font support" })
77 lb.provides_module({ name = "lualibs", date = "2023-07-13", version = "2.76",
78 description = "ConTeXt Lua standard libraries." })
79 lb.provides_module({ name = "lualibs-extended", date = "2023-07-13", version = "2.76",
80 description = "ConTeXt Lua libraries -- extended collection." })
81 local cache = native.cache_dir()
82 log("conf", 'Root cache directory is "' .. cache .. '".')
83 local loader = kpse.find_file("fontloader-2023-12-28.lua", "lua") or "fontloader-2023-12-28.lua"
84 log("init", 'Loading fontloader "fontloader-2023-12-28.lua" from kpse-resolved path "' .. loader .. '".')
85 lb.new_attribute("luaotfload@noligature")
86 log("init", "Context OpenType loader version 3.134")
87 lb.add_to_callback("pre_linebreak_filter", native.node_processor, "luaotfload.node_processor")
88 lb.add_to_callback("hpack_filter", native.node_processor, "luaotfload.node_processor")
89 lb.add_to_callback("glyph_stream_provider", noop, "luaotfload.glyph_stream")
90 lb.add_to_callback("define_font", native.define_font, "luaotfload.define_font")
91 lb.new_attribute("luaotfload_color_attribute")
92 log("conf", 'Root cache directory is "' .. cache .. '".')
93 lb.add_to_callback("find_opentype_file", noop, "luaotfload.harf.strip_prefix")
94 lb.add_to_callback("find_truetype_file", noop, "luaotfload.harf.strip_prefix")
95 lb.remove_from_callback("glyph_stream_provider", "luaotfload.glyph_stream")
96 lb.add_to_callback("glyph_stream_provider", noop, "luaotfload.harf.glyphstream")
97 lb.add_to_callback("post_linebreak_filter", noop, "luaotfload.harf.finalize_vlist")
98 lb.add_to_callback("hpack_filter", noop, "luaotfload.harf.finalize_hlist")
99 lb.add_to_callback("wrapup_run", noop, "luaotfload.cleanup_files")
100 lb.add_to_callback("finish_pdffile", noop, "luaotfload.harf.finalize_unicode")
101 -- Box displays show the character as UTF-8 (observed: "^^@" for 0
102 -- and ^^^^^^ notation beyond Unicode).
103 lb.add_to_callback("glyph_info", function(n)
104 local c = n.char
105 if c == 0 then return "^^@" end
106 if c > 0x10FFFF then return string.format("^^^^^^%06x", c) end
107 return utf8.char(c)
108 end, "luaotfload.glyphinfo")
109 lb.new_attribute("luaotfload.letterspace_done")
110 lb.create_callback("luaotfload.patch_font", "simple", noop)
111 lb.create_callback("luaotfload.patch_font_unsafe", "simple", noop)
112 lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_sscale_dimens")
113 lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_font_index")
114 lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.patch_cambria_domh")
115 lb.add_to_callback("luaotfload.patch_font_unsafe", noop, "luaotfload.aux.fixup_fontdata")
116 lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_capheight")
117 lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.aux.set_xheight")
118 lb.add_to_callback("luaotfload.patch_font", noop, "luaotfload.rewrite_fontname")
119 lb.create_callback("luaotfload.parse_color", "exclusive", noop)
120 return true
121end
122return luaotfload
123"#;
124
125pub fn install(lua: &Lua) -> mlua::Result<()> {
127 lua.set_app_data(Rc::new(RefCell::new(FontState::default())));
128 let native = lua.create_table()?;
129 native.set(
130 "define_font",
131 lua.create_function(|lua, (name, size, _id): (mlua::String, i64, Option<i64>)| {
132 let name = name.to_str()?.to_owned();
133 let t0 = std::time::Instant::now();
134 let r = define_font(lua, &name, size as i32);
135 retex_engine::timing::FONTS.with(|a| a.add(t0));
136 r
137 })?,
138 )?;
139 native.set(
140 "node_processor",
141 lua.create_function(|lua, (head, _group): (Value, Value)| {
142 let h = match &head {
143 Value::UserData(u) => u
144 .borrow::<crate::nodelib::LNode>()
145 .ok()
146 .map(|n| NodeRef(n.0)),
147 _ => None,
148 };
149 if let Some(h) = h {
150 let nh = with_engine(lua, |e| process_list(e, h))?;
151 if nh != h {
152 return Ok(Value::UserData(
153 lua.create_userdata(crate::nodelib::LNode(nh.0))?,
154 ));
155 }
156 }
157 Ok(Value::Boolean(true))
158 })?,
159 )?;
160 native.set(
161 "cache_dir",
162 lua.create_function(|lua, ()| {
163 with_engine(lua, |e| {
164 e.kpse
165 .as_ref()
166 .and_then(|k| k.borrow().var_value("TEXMFVAR"))
167 .map(|v| format!("{v}/luatex-cache/generic/names"))
168 .unwrap_or_default()
169 })
170 })?,
171 )?;
172 crate::otfaux::install(lua, &native)?;
173 let loader = lua.create_function(move |lua, ()| {
174 let f = lua
175 .load(MAIN_LUA)
176 .set_name("=luaotfload-main")
177 .into_function()?;
178 let native: mlua::Table = lua.named_registry_value("retex.luaotfload.native")?;
179 f.call::<Value>(native)
180 })?;
181 lua.set_named_registry_value("retex.luaotfload.native", native)?;
182 let package: mlua::Table = lua.globals().get("package")?;
183 let preload: mlua::Table = package.get("preload")?;
184 preload.set("luaotfload-main", loader)?;
185 Ok(())
186}
187
188fn define_font(lua: &Lua, name: &str, size: i32) -> mlua::Result<Value> {
191 let state = lua
192 .app_data_ref::<Rc<RefCell<FontState>>>()
193 .map(|s| s.clone())
194 .ok_or_else(|| mlua::Error::runtime("font state missing"))?;
195 if let Ok(req) = retex_font::FontRequest::parse(name)
198 && let retex_font::FontSource::Any(base) = &req.source
199 {
200 let id = with_engine(lua, |e| {
201 e.load_tfm_font(base.as_bytes(), size, name.as_bytes())
202 })?;
203 if let Some(i) = id {
204 return Ok(Value::Integer(i64::from(i)));
205 }
206 }
207 let loaded: Result<Arc<LoadedFont>, String> = with_engine(lua, |e| {
208 let mut st = state.borrow_mut();
209 if st.loader.is_none() {
210 let k = e
211 .kpse
212 .clone()
213 .ok_or_else(|| "kpathsea unavailable".to_owned())?;
214 st.loader = Some(retex_font::FontLoader::new(k));
215 }
216 st.loader
217 .as_mut()
218 .expect("loader initialized")
219 .load(name, size)
220 .map_err(|e| e.to_string())
221 })?;
222 let Ok(font) = loaded else {
223 let plain = name.strip_prefix("file:").unwrap_or(name);
225 let plain = plain.split(':').next().unwrap_or(plain).to_owned();
226 let id = with_engine(lua, |e| {
227 e.load_tfm_font(plain.as_bytes(), size, name.as_bytes())
228 })?;
229 return Ok(id.map_or(Value::Nil, |i| Value::Integer(i64::from(i))));
230 };
231 let key = Arc::as_ptr(&font) as usize;
232 if let Some(&id) = state.borrow().ids.get(&key) {
233 return Ok(Value::Integer(i64::from(id)));
234 }
235 let id = with_engine(lua, |e| {
236 let tex = &font.tex;
237 let mut params = vec![0; tex.params.len() + 1];
238 for (i, p) in tex.params.iter().enumerate() {
239 params[i + 1] = *p;
240 }
241 let mut chars = HashMap::with_capacity_and_hasher(
242 tex.num_chars(),
243 retex_engine::hash::FastHash::default(),
244 );
245 for (code, m) in tex.chars() {
246 chars.insert(
247 code,
248 CharMetrics {
249 width: m.width,
250 height: m.height,
251 depth: m.depth,
252 italic: m.italic,
253 index: u32::from(m.glyph),
254 },
255 );
256 }
257 let info = FontInfo {
258 name: tex.name.clone().into_bytes(),
259 size: tex.size,
260 design_size: tex.design_size,
261 checksum: 0,
262 params,
263 chars,
264 hyphen_char: i32::MIN,
265 skew_char: i32::MIN,
266 kind: FontKind::OpenType,
267 handle: 0,
268 char_codes: Default::default(),
269 ignore_ligatures: 0,
270 data: Some(FontData(Rc::new(font.clone()))),
271 lig_kern: Default::default(),
272 right_boundary: None,
273 expansion: None,
274 size_spec: i32::MIN,
275 vf: None,
276 math_chars: {
277 let mc = tex.math_chars(&font.file);
278 (!mc.is_empty()).then(|| Rc::new(convert_math_chars(mc)))
279 },
280 math_constants: tex.math.as_ref().map(|m| {
281 Rc::new(
282 m.iter()
283 .map(|(k, v)| ((*k).to_owned(), v.to_sp()))
284 .collect(),
285 )
286 }),
287 };
288 e.register_font(info)
289 })?;
290 state.borrow_mut().ids.insert(key, id);
291 Ok(Value::Integer(i64::from(id)))
292}
293
294fn convert_math_chars(
297 mc: HashMap<u32, retex_font::texfont::MathChar>,
298) -> HashMap<u32, retex_engine::fonts::MathCharInfo> {
299 use retex_engine::fonts::{ExtPart, MathCharInfo};
300 let parts = |v: &Option<Vec<retex_font::texfont::MathPart>>| {
301 v.as_ref().map(|ps| {
302 ps.iter()
303 .map(|p| ExtPart {
304 code: p.code,
305 start: retex_font::texfont::round_sp(p.start),
306 end: retex_font::texfont::round_sp(p.end),
307 advance: retex_font::texfont::round_sp(p.advance),
308 extender: p.extender,
309 })
310 .collect::<Rc<[ExtPart]>>()
311 })
312 };
313 mc.into_iter()
314 .map(|(c, m)| {
315 (
316 c,
317 MathCharInfo {
318 next: m.next,
319 top_accent: m.top_accent.map(retex_font::texfont::round_sp),
320 vert: parts(&m.vert),
321 horiz: parts(&m.horiz),
322 },
323 )
324 })
325 .collect()
326}
327
328pub fn loaded_font(e: &Engine, f: u32) -> Option<Arc<LoadedFont>> {
330 let data = e.font(f).data.as_ref()?;
331 data.0.downcast_ref::<Arc<LoadedFont>>().cloned()
332}
333
334pub use crate::otfnodes::process_list;